init for test

This commit is contained in:
cnsilvan 2019-12-11 11:30:32 +08:00
parent e208e0b9a3
commit 273466b67c
12 changed files with 1294 additions and 0 deletions

32
app.go Normal file
View File

@ -0,0 +1,32 @@
package main
import (
"config"
"fmt"
"host"
"proxy"
"version"
)
func main() {
fmt.Printf(`
## ## ## ## ## ## ## ## ## ## ## ## ##
## ## ## ## ## ## ## ## ## ## ## ## ##
## ## ## ## ## ## ## ## ## ## ##
## ## ## ## ## ## ## ## ## ## ## ## ##
## ## ## ## ## ## ## ## ## ## ##
## ## ## ## ## ## ## ## ## ## ## ## ##
## ## ## ## ## #### ## ## ## ## ## ## ## ## ## ## ## ## ## ##
%s`+" by cnsilvanhttps://github.com/cnsilvan/UnblockNeteaseMusic \n", version.Version)
fmt.Println("--------------------Version--------------------")
fmt.Println(version.FullVersion())
if config.ValidParams() {
fmt.Println("--------------------Config--------------------")
fmt.Println("port=", *config.Port)
fmt.Println("source=", *config.Source)
if host.InitHosts() == nil {
proxy.InitProxy()
}
}
}

33
build.sh Executable file
View File

@ -0,0 +1,33 @@
CurrentVersion=0.1.0
Project=UnblockNeteaseMusic
Path="version"
GitCommit=$(git rev-parse --short HEAD || echo unsupported)
GoVersion=$(go version)
BuildTime=$(date "+%Y-%m-%d %H:%M:%S")
platforms=("darwin/amd64")
buildGo() {
GOOS=$1
GOARCH=$2
output_name=$Project
if [ $GOOS = "windows" ]; then
output_name+='.exe'
fi
echo "Building($GOOS/$GOARCH)..."
TargetDir=bin/$GOOS/$GOARCH
env GOOS=$GOOS GOARCH=$GOARCH go build -ldflags "-X '$Path.Version=$CurrentVersion' -X '$Path.BuildTime=$BuildTime' -X '$Path.GoVersion=$GoVersion' -X '$Path.GitCommit=$GitCommit' -w -s" -o $TargetDir/$output_name
if [ $? -ne 0 ]; then
echo 'An error has occurred! Aborting the script execution...'
exit 1
fi
}
for platform in "${platforms[@]}"; do
platform_split=(${platform//\// })
buildGo ${platform_split[0]} ${platform_split[1]}
done
echo "--------------------------------------------"
echo "Version:" $CurrentVersion
echo "Git commit:" $GitCommit
echo "Go version:" $GoVersion
echo "Build Time:" $BuildTime
echo "Build Finish"
echo "--------------------------------------------"

48
src/config/config.go Normal file
View File

@ -0,0 +1,48 @@
package config
import (
"flag"
"fmt"
"regexp"
"strings"
)
var (
Port = flag.String("p", "80:443", "specify server port,such as : \"80:443\"")
Source = flag.String("o", "kuwo:kugou", "specify server source,such as : \"kuwo:kugou\"")
)
func ValidParams() bool {
flag.Parse()
if flag.NArg() > 0 {
fmt.Println("--------------------Invalid Params------------------------")
fmt.Printf("Invalid params=%s, num=%d\n", flag.Args(), flag.NArg())
for i := 0; i < flag.NArg(); i++ {
fmt.Printf("arg[%d]=%s\n", i, flag.Arg(i))
}
}
fmt.Println("--------------------Port------------------------")
ports := strings.Split(*Port, ":")
if len(ports) < 1 {
fmt.Printf("port param invalid: %v \n", *Port)
return false
}
for _, p := range ports {
fmt.Println(p)
if m, _ := regexp.MatchString("^\\d+$", p); !m {
fmt.Printf("port param invalid: %v \n", *Port)
return false
}
}
fmt.Println("--------------------Source------------------------")
sources := strings.Split(*Source, ":")
if len(sources) < 1 {
fmt.Printf("source param invalid: %v \n", *Source)
return false
}
for _, p := range sources {
fmt.Println(p)
}
return true
}

232
src/host/host.go Normal file
View File

@ -0,0 +1,232 @@
package host
import (
"bufio"
"fmt"
"io"
"net"
"os"
"path/filepath"
"runtime"
"strings"
)
var (
ProxyIp = "127.0.0.1"
ProxyDomain = map[string]string{
"music.163.com": "59.111.181.38",
"interface.music.163.com": "59.111.181.38",
}
//ProxyDomain = map[string]string{
// "music.163.com": "59.111.181.35",
// "interface.music.163.com": "59.111.181.35",
// "interface3.music.163.com": "59.111.181.35",
// "apm.music.163.com": "59.111.181.35",
// "apm3.music.163.com": "59.111.181.35",
// "music.httpdns.c.163.com": "59.111.181.35",
// "httpdns.n.netease.com": "59.111.179.213",
//}
)
func getWinSystemDir() string {
dir := ""
if runtime.GOOS == "windows" {
dir = os.Getenv("windir")
}
return dir
}
func fileExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
//if os.IsNotExist(err) {
// return false, nil
//}
return false, err
}
func appendToFile(fileName string, content string) error {
f, err := os.OpenFile(fileName, os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
fmt.Println("appendToFile cacheFileList.yml file create failed. err: " + err.Error())
} else {
defer f.Close()
//n, _ := f.Seek(0, io.SeekEnd)
//_, err = f.WriteAt([]byte(content), n)
_, err = f.WriteString(content)
if err != nil {
fmt.Println("appendToFile write file fail:", err)
return err
}
}
return err
}
func restoreHost(hostPath string) error {
return nil
}
func appendToHost(hostPath string) error {
content := " \n# UnblockNetEaseMusicGo\n"
for domain, _ := range ProxyDomain {
content += ProxyIp + " " + domain + "\n"
}
return appendToFile(hostPath, content)
}
func backupHost(hostPath string) (bool, error) {
containsProxyDomain := false
host, err := os.Open(hostPath)
if err != nil {
fmt.Println("open file fail:", err)
return containsProxyDomain, err
}
defer host.Close()
gBackup, err := os.Create(hostPath + ".gBackup")
if err != nil {
fmt.Println("Open write file fail:", err)
return containsProxyDomain, err
}
defer gBackup.Close()
br := bufio.NewReader(host)
for {
line, _, err := br.ReadLine()
if err == io.EOF {
break
}
if err != nil {
fmt.Println("read err:", err)
return containsProxyDomain, err
}
newLine := string(line)
if !containsProxyDomain {
if strings.Contains(strings.ToUpper(newLine), strings.ToUpper("UnblockNetEaseMusic")) {
containsProxyDomain = true
fmt.Println("Found UnblockNetEaseMusic Line")
}
for domain, _ := range ProxyDomain {
if strings.Contains(newLine, domain) {
containsProxyDomain = true
fmt.Println("Found ProxyDomain Line")
}
}
}
_, err = gBackup.WriteString(newLine + "\n")
if err != nil {
fmt.Println("write to file fail:", err)
return containsProxyDomain, err
}
}
return containsProxyDomain, nil
}
// Exclude UnblockNetEaseMusic related host
func excludeRelatedHost(hostPath string) error {
host, err := os.Create(hostPath)
if err != nil {
fmt.Println("open file fail:", err)
return err
}
defer host.Close()
gBackup, err := os.Open(hostPath + ".gBackup")
if err != nil {
fmt.Println("Open write file fail:", err)
return err
}
defer gBackup.Close()
br := bufio.NewReader(gBackup)
for {
line, _, err := br.ReadLine()
if err == io.EOF {
break
}
if err != nil {
fmt.Println("read err:", err)
return err
}
newLine := string(line)
needWrite := true
for domain, _ := range ProxyDomain {
if strings.Contains(newLine, domain) {
needWrite = false
break
}
}
if needWrite && strings.Contains(strings.ToUpper(newLine), strings.ToUpper("UnblockNetEaseMusic")) {
needWrite = false
}
if needWrite && len(strings.TrimSpace(newLine)) == 0 {
needWrite = false
}
if needWrite {
_, err = host.WriteString(newLine + "\n")
if err != nil {
fmt.Println("write to file fail:", err)
return err
}
}
}
return nil
}
func resolveIp(domain string) (ip string, err error) {
return "", nil
}
func resolveIps() error {
for domain, _ := range ProxyDomain {
rAddr, err := net.ResolveIPAddr("ip", domain)
if err != nil {
fmt.Printf("Fail to resolve %s, %s\n", domain, err)
return err
}
if len(rAddr.IP) == 0 {
fmt.Printf("Fail to resolve %s,IP nil\n", domain)
return fmt.Errorf("Fail to resolve %s,Ip length==0 \n", domain)
}
ProxyDomain[domain] = rAddr.IP.String()
}
return nil
}
func getHostsPath() (string, error) {
hostsPath := "/etc/hosts"
if runtime.GOOS == "windows" {
hostsPath = getWinSystemDir()
hostsPath = filepath.Join(hostsPath, "system32", "drivers", "etc", "hosts")
} else {
hostsPath = filepath.Join(hostsPath)
}
if exist, err := fileExists(hostsPath); !exist {
fmt.Println("Not Found Host File", hostsPath)
return hostsPath, err
}
return hostsPath, nil
}
func InitHosts() error {
fmt.Println("-------------------Init Host-----------------------")
hostsPath, err := getHostsPath()
if err == nil {
containsProxyDomain := false
containsProxyDomain, err = backupHost(hostsPath)
if err == nil {
if containsProxyDomain {
if err = excludeRelatedHost(hostsPath); err == nil {
err = resolveIps()
if err != nil {
return err
}
fmt.Println("ProxyDomain:", ProxyDomain)
}
} else {
err = resolveIps()
if err != nil {
return err
}
fmt.Println("ProxyDomain:", ProxyDomain)
}
if err = appendToHost(hostsPath); err == nil {
}
}
}
return err
}

81
src/network/network.go Normal file
View File

@ -0,0 +1,81 @@
package network
import (
"bytes"
"compress/gzip"
"crypto/tls"
"fmt"
"io/ioutil"
"net/http"
"strings"
)
func Get(urlAddress string, host string, header map[string]string, tlsVerifyServerName bool) (*http.Response, error) {
req, err := http.NewRequest(http.MethodGet, urlAddress, nil)
var resp *http.Response
if err != nil {
fmt.Printf("NewRequest fail:%v\n", err)
return resp, nil
}
req.URL.RawQuery = req.URL.Query().Encode()
c := http.Client{}
if len(host) > 0 {
req.Host = host
req.Header.Set("host", host)
if tlsVerifyServerName {
tr := http.Transport{
TLSClientConfig: &tls.Config{ServerName: req.Host},
}
c = http.Client{
Transport: &tr,
}
}
}
if header != nil {
for key, value := range header {
req.Header.Set(key, value)
}
}
req.Header.Set("accept", "application/json, text/plain, */*")
req.Header.Set("accept-encoding", "gzip, deflate")
req.Header.Set("accept-language", "zh-CN,zh;q=0.9")
req.Header.Set("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36")
resp, err = c.Do(req)
if err != nil {
fmt.Printf("http.Client.Do fail:%v\n", err)
return resp, err
}
return resp, err
}
func GetResponseBody(resp *http.Response, keepBody bool) ([]byte, error) {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("read body fail")
return body, err
}
resp.Body.Close()
if keepBody {
bodyHold := ioutil.NopCloser(bytes.NewBuffer(body))
resp.Body = bodyHold
}
encode := resp.Header.Get("Content-Encoding")
enableGzip := false
if len(encode) > 0 && (strings.Contains(encode, "gzip") || strings.Contains(encode, "deflate")) {
enableGzip = true
}
if enableGzip {
resp.Header.Del("Content-Encoding")
r, err := gzip.NewReader(bytes.NewReader(body))
if err != nil {
fmt.Println("read gzip body fail")
return body, err
}
defer r.Close()
body, err = ioutil.ReadAll(r)
if err != nil {
fmt.Println("read body fail")
return body, err
}
}
return body, err
}

View File

@ -0,0 +1,113 @@
package crypto
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"io"
)
// =================== CBC ======================
func AesEncryptCBC(origData []byte, key []byte) (encrypted []byte) {
// 分组秘钥
// NewCipher该函数限制了输入k的长度必须为16, 24或者32
block, _ := aes.NewCipher(key)
blockSize := block.BlockSize() // 获取秘钥块的长度
origData = pkcs5Padding(origData, blockSize) // 补全码
blockMode := cipher.NewCBCEncrypter(block, key[:blockSize]) // 加密模式
encrypted = make([]byte, len(origData)) // 创建数组
blockMode.CryptBlocks(encrypted, origData) // 加密
return encrypted
}
func AesDecryptCBC(encrypted []byte, key []byte) (decrypted []byte) {
block, _ := aes.NewCipher(key) // 分组秘钥
blockSize := block.BlockSize() // 获取秘钥块的长度
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) // 加密模式
decrypted = make([]byte, len(encrypted)) // 创建数组
blockMode.CryptBlocks(decrypted, encrypted) // 解密
decrypted = pkcs5UnPadding(decrypted) // 去除补全码
return decrypted
}
func pkcs5Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func pkcs5UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
// =================== ECB ======================
func AesEncryptECB(origData []byte, key []byte) (encrypted []byte) {
cipher, _ := aes.NewCipher(generateKey(key))
length := (len(origData) + aes.BlockSize) / aes.BlockSize
plain := make([]byte, length*aes.BlockSize)
copy(plain, origData)
pad := byte(len(plain) - len(origData))
for i := len(origData); i < len(plain); i++ {
plain[i] = pad
}
encrypted = make([]byte, len(plain))
// 分组分块加密
for bs, be := 0, cipher.BlockSize(); bs <= len(origData); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
cipher.Encrypt(encrypted[bs:be], plain[bs:be])
}
return encrypted
}
func AesDecryptECB(encrypted []byte, key []byte) (decrypted []byte) {
cipher, _ := aes.NewCipher(generateKey(key))
decrypted = make([]byte, len(encrypted))
//
for bs, be := 0, cipher.BlockSize(); bs < len(encrypted); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
cipher.Decrypt(decrypted[bs:be], encrypted[bs:be])
}
trim := 0
if len(decrypted) > 0 {
trim = len(decrypted) - int(decrypted[len(decrypted)-1])
}
return decrypted[:trim]
}
func generateKey(key []byte) (genKey []byte) {
genKey = make([]byte, 16)
copy(genKey, key)
for i := 16; i < len(key); {
for j := 0; j < 16 && i < len(key); j, i = j+1, i+1 {
genKey[j] ^= key[i]
}
}
return genKey
}
// =================== CFB ======================
func AesEncryptCFB(origData []byte, key []byte) (encrypted []byte) {
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
encrypted = make([]byte, aes.BlockSize+len(origData))
iv := encrypted[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
panic(err)
}
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(encrypted[aes.BlockSize:], origData)
return encrypted
}
func AesDecryptCFB(encrypted []byte, key []byte) (decrypted []byte) {
block, _ := aes.NewCipher(key)
if len(encrypted) < aes.BlockSize {
panic("ciphertext too short")
}
iv := encrypted[:aes.BlockSize]
encrypted = encrypted[aes.BlockSize:]
stream := cipher.NewCFBDecrypter(block, iv)
stream.XORKeyStream(encrypted, encrypted)
return encrypted
}

238
src/processor/processor.go Normal file
View File

@ -0,0 +1,238 @@
package processor
import (
"bytes"
"compress/gzip"
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"processor/crypto"
"provider"
"regexp"
"strings"
"utils"
)
var (
eApiKey = "e82ckenh8dichen8"
linuxApiKey = "rFgB&h#%2?^eDg:Q"
)
type Netease struct {
Path string
Params string
JsonBody map[string]interface{}
}
type MapType = map[string]interface{}
type SliceType = []interface{}
func DecodeRequestBody(request *http.Request) *Netease {
netease := &Netease{Path: request.RequestURI}
requestBody, _ := ioutil.ReadAll(request.Body)
//fmt.Println(string(requestBody))
requestHold := ioutil.NopCloser(bytes.NewBuffer(requestBody))
request.Body = requestHold
pad := make([]byte, 0)
if matched, _ := regexp.Match("/%0 +$/", requestBody); matched {
pad = requestBody
}
if netease.Path == "/api/linux/forward" {
requestBodyH := make([]byte, len(requestBody))
length, _ := hex.Decode(requestBodyH, requestBody[8:len(requestBody)-len(pad)])
decryptECBBytes := crypto.AesDecryptECB(requestBodyH[:length], []byte(linuxApiKey))
var result MapType
d := json.NewDecoder(bytes.NewReader(decryptECBBytes))
d.UseNumber()
d.Decode(&result)
if utils.Exist("url", result) && utils.Exist("path", result["url"].(MapType)) {
netease.Path = result["url"].(MapType)["path"].(string)
}
netease.Params = result["params"].(string)
fmt.Println("forward")
//fmt.Printf("path:%s \nparams:%s\n", netease.Path, netease.Params)
} else {
requestBodyH := make([]byte, len(requestBody))
length, _ := hex.Decode(requestBodyH, requestBody[7:len(requestBody)-len(pad)])
decryptECBBytes := crypto.AesDecryptECB(requestBodyH[:length], []byte(eApiKey))
decryptString := string(decryptECBBytes)
data := strings.Split(decryptString, "-36cd479b6b5-")
netease.Path = data[0]
netease.Params = data[1]
//fmt.Printf("path:%s \nparams:%s\n", netease.Path, netease.Params)
}
return netease
}
func DecodeResponseBody(response *http.Response, netease *Netease) {
if response.StatusCode == 200 {
encode := response.Header.Get("Content-Encoding")
enableGzip := false
if len(encode) > 0 && (strings.Contains(encode, "gzip") || strings.Contains(encode, "deflate")) {
enableGzip = true
}
body, _ := ioutil.ReadAll(response.Body)
if len(body) > 0 {
decryptECBBytes := body
if enableGzip {
r, _ := gzip.NewReader(bytes.NewReader(decryptECBBytes))
defer r.Close()
decryptECBBytes, _ = ioutil.ReadAll(r)
}
decryptECBBytes = crypto.AesDecryptECB(decryptECBBytes, []byte(eApiKey))
result := utils.ParseJson(decryptECBBytes)
modified := false
if strings.Contains(netease.Path, "manipulate") {
} else if strings.EqualFold(netease.Path, "/api/song/like") {
} else if strings.Contains(netease.Path, "url") {
fmt.Println(netease.Path)
if value, ok := result["data"]; ok {
switch value.(type) {
case SliceType:
if strings.Contains(netease.Path, "download") {
for index, data := range value.(SliceType) {
if index == 0 {
modified = searchGreySong(data.(MapType)) || modified
break
}
}
} else {
modified = searchGreySongs(value.(SliceType)) || modified
}
case MapType:
modified = searchGreySong(value.(MapType)) || modified
default:
}
}
modifiedJson, _ := json.Marshal(result)
fmt.Println(string(modifiedJson))
}
if processMapJson(result) || modified {
response.Header.Del("transfer-encoding")
response.Header.Del("content-encoding")
response.Header.Del("content-length")
netease.JsonBody = result
fmt.Println("NeedRepackage")
modifiedJson, _ := json.Marshal(result)
//fmt.Println(string(modifiedJson))
encryptedJson := crypto.AesEncryptECB(modifiedJson, []byte(eApiKey))
response.Body = ioutil.NopCloser(bytes.NewBuffer(encryptedJson))
} else {
//fmt.Println(string(body))
responseHold := ioutil.NopCloser(bytes.NewBuffer(body))
response.Body = responseHold
}
} else {
responseHold := ioutil.NopCloser(bytes.NewBuffer(body))
response.Body = responseHold
}
} else {
//body, _ := ioutil.ReadAll(response.Body)
//responseHold := ioutil.NopCloser(bytes.NewBuffer(body))
//response.Body = responseHold
//fmt.Println(string(body))
}
}
func searchGreySongs(data SliceType) bool {
modified := false
for _, value := range data {
switch value.(type) {
case MapType:
modified = searchGreySong(value.(MapType)) || modified
}
}
return modified
}
func searchGreySong(data MapType) bool {
modified := false
if data["url"] == nil {
data["flag"] = 0
song := provider.Find(data["id"].(json.Number).String())
//modifiedJson, _ := json.Marshal(song)
//fmt.Println(string(modifiedJson))
if song.Size > 0 {
modified = true
if song.Br == 999000 {
data["type"] = "flac"
} else {
data["type"] = "mp3"
}
data["url"] = song.Url
if len(song.Md5) > 0 {
data["md5"] = song.Md5
} else {
h := md5.New()
h.Write([]byte(song.Url))
data["md5"] = hex.EncodeToString(h.Sum(nil))
}
if song.Br > 0 {
data["br"] = song.Br
} else {
data["br"] = 128000
}
data["size"] = song.Size
data["freeTrialInfo"] = nil
data["code"] = 200
}
}
return modified
}
func processSliceJson(jsonSlice SliceType) bool {
needModify := false
for _, value := range jsonSlice {
switch value.(type) {
case MapType:
needModify = processMapJson(value.(MapType)) || needModify
case SliceType:
needModify = processSliceJson(value.(SliceType)) || needModify
default:
//fmt.Printf("index(%T):%v\n", index, index)
//fmt.Printf("value(%T):%v\n", value, value)
}
}
return needModify
}
func processMapJson(jsonMap MapType) bool {
needModify := false
if utils.Exists([]string{"st", "subp", "pl", "dl"}, jsonMap) {
if v, _ := jsonMap["st"]; v.(json.Number).String() != "0" {
//open grep song
jsonMap["st"] = 0
needModify = true
}
if v, _ := jsonMap["subp"]; v.(json.Number).String() != "1" {
jsonMap["subp"] = 1
needModify = true
}
if v, _ := jsonMap["pl"]; v.(json.Number).String() == "0" {
jsonMap["pl"] = 320000
needModify = true
}
if v, _ := jsonMap["dl"]; v.(json.Number).String() == "0" {
jsonMap["dl"] = 320000
needModify = true
}
}
for _, value := range jsonMap {
switch value.(type) {
case MapType:
needModify = processMapJson(value.(MapType)) || needModify
case SliceType:
needModify = processSliceJson(value.(SliceType)) || needModify
default:
//if key == "fee" && value.(json.Number).String() != "0" {
// jsonMap[key] = 0
// needModify = true
//}
}
}
return needModify
}

69
src/provider/kuwo/kuwo.go Normal file
View File

@ -0,0 +1,69 @@
package kuwo
import (
"fmt"
"net/url"
"network"
"strings"
"utils"
)
func SearchSong(key map[string]interface{}) string {
keyword := key["keyword"].(string)
token := getToken(keyword)
header := make(map[string]string, 3)
header["referer"] = "http://www.kuwo.cn/search/list?key=" + url.QueryEscape(keyword)
header["csrf"] = token
header["cookie"] = "kw_token=" + token
resp, err := network.Get("http://www.kuwo.cn/api/www/search/searchMusicBykeyWord?key="+keyword+"&pn=1&rn=30", "kuwo.cn", header, false)
if err != nil {
fmt.Println(err)
return ""
}
body, err := network.GetResponseBody(resp, false)
if err != nil {
fmt.Println(err)
return ""
}
result := utils.ParseJson(body)
var musicId = ""
if result["data"] != nil && result["data"].(map[string]interface{}) != nil && len(result["data"].(map[string]interface{})["list"].([]interface{})) > 0 {
matched := result["data"].(map[string]interface{})["list"].([]interface{})[0]
if matched != nil && matched.(map[string]interface{})["musicrid"] != nil {
musicrid := matched.(map[string]interface{})["musicrid"].(string)
musicSlice := strings.Split(musicrid, "_")
musicId = musicSlice[len(musicSlice)-1]
}
}
if len(musicId) > 0 {
resp, err = network.Get("http://antiserver.kuwo.cn/anti.s?type=convert_url&format=mp3&response=url&rid=MUSIC_"+musicId, "antiserver.kuwo.cn", nil, false)
if err != nil {
fmt.Println(err)
return ""
}
body, err = network.GetResponseBody(resp, false)
address := string(body)
if strings.Index(address, "http") == 0 {
return address
}
}
return ""
}
func getToken(keyword string) string {
var token = ""
resp, err := network.Get("http://kuwo.cn/search/list?key="+keyword, "kuwo.cn", nil, false)
if err != nil {
fmt.Println(err)
return token
}
defer resp.Body.Close()
cookies := resp.Header.Get("set-cookie")
if strings.Contains(cookies, "kw_token") {
cookies = utils.ReplaceAll(cookies, ";.*", "")
splitSlice := strings.Split(cookies, "=")
token = splitSlice[len(splitSlice)-1]
}
return token
}

201
src/provider/provider.go Normal file
View File

@ -0,0 +1,201 @@
package provider
import (
"bytes"
"encoding/json"
"fmt"
"host"
"network"
"provider/kuwo"
"strconv"
"strings"
"utils"
)
type Song struct {
Size int64
Br int
Url string
Md5 string
}
type MapType = map[string]interface{}
type SliceType = []interface{}
var Cache = make(map[string]Song)
func Find(id string) Song {
fmt.Println("find song info,id:", id)
if song,ok:=Cache[id];ok{
fmt.Println("hit cache:", utils.ToJson(song))
return song
}
var songT Song
resp, err := network.Get("https://"+host.ProxyDomain["music.163.com"]+"/api/song/detail?ids=["+id+"]", "music.163.com", nil, true)
if err != nil {
return songT
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
body, err2 := network.GetResponseBody(resp, false)
if err2 != nil {
fmt.Println("GetResponseBody fail")
return songT
}
var oJson MapType
d := json.NewDecoder(bytes.NewReader(body))
d.UseNumber()
d.Decode(&oJson)
if oJson["songs"] != nil {
song := oJson["songs"].(SliceType)[0]
var modifiedJson = make(MapType, 6)
var artists []string
switch song.(type) {
case MapType:
modifiedJson["id"] = song.(MapType)["id"]
modifiedJson["name"] = song.(MapType)["name"]
modifiedJson["alias"] = song.(MapType)["alias"]
modifiedJson["duration"] = song.(MapType)["duration"]
modifiedJson["album"] = make(MapType, 2)
modifiedJson["album"].(MapType)["id"] = song.(MapType)["album"].(MapType)["id"]
modifiedJson["album"].(MapType)["name"] = song.(MapType)["album"].(MapType)["name"]
switch song.(MapType)["artists"].(type) {
case SliceType:
length := len(song.(MapType)["artists"].(SliceType))
modifiedJson["artists"] = make(SliceType, length)
artists = make([]string, length)
for index, value := range song.(MapType)["artists"].(SliceType) {
if modifiedJson["artists"].(SliceType)[index] == nil {
modifiedJson["artists"].(SliceType)[index] = make(MapType, 2)
}
modifiedJson["artists"].(SliceType)[index].(MapType)["id"] = value.(MapType)["id"]
modifiedJson["artists"].(SliceType)[index].(MapType)["name"] = value.(MapType)["name"]
artists[index] = value.(MapType)["name"].(string)
}
}
default:
}
if modifiedJson["name"] != nil {
modifiedJson["name"] = utils.ReplaceAll(modifiedJson["name"].(string), `\s*cover[:\s][^]+`, "")
modifiedJson["name"] = utils.ReplaceAll(modifiedJson["name"].(string), `\(\s*cover[:\s][^\)]+\)`, "")
}
modifiedJson["keyword"] = modifiedJson["name"].(string) + " - " + strings.Join(artists, " / ")
songUrl := searchSong(modifiedJson)
songS := processSong(songUrl)
if songS.Size > 0 {
fmt.Println(utils.ToJson(songS))
Cache[id] = songS
return songS
}
fmt.Println(utils.ToJson(modifiedJson))
return songT
} else {
return songT
}
} else {
return songT
}
}
func searchSong(key MapType) string {
//cache after
return kuwo.SearchSong(key)
}
func processSong(songUrl string) Song {
var song Song
if len(songUrl) > 0 {
header := make(map[string]string, 1)
header["range"] = "bytes=0-8191"
resp, err := network.Get(songUrl, "", header, false)
if err != nil {
fmt.Println("processSong fail:", err)
return song
}
if resp.StatusCode > 199 && resp.StatusCode < 300 {
if strings.Contains(songUrl, "qq.com") {
song.Md5 = resp.Header.Get("server-md5")
} else if strings.Contains(songUrl, "xiami.net") || strings.Contains(songUrl, "qianqian.com") {
song.Md5 = strings.ToLower(utils.ReplaceAll(resp.Header.Get("etag"), `/"/g`, ""))
//.replace(/"/g, '').toLowerCase()
}
size := resp.Header.Get("content-range")
if len(size) > 0 {
sizeSlice := strings.Split(size, "/")
if len(sizeSlice) > 0 {
size = sizeSlice[len(sizeSlice)-1]
}
} else {
size = resp.Header.Get("content-length")
if len(size) < 1 {
size = "0"
}
}
song.Size, _ = strconv.ParseInt(size, 10, 64)
song.Url = songUrl
if resp.Header.Get("content-length") == "8192" {
body, err := network.GetResponseBody(resp, false)
if err != nil {
fmt.Println("song GetResponseBody error:", err)
return song
}
bitrate := decodeBitrate(body)
if bitrate > 0 && bitrate < 500 {
song.Br = bitrate * 1000
}
}
//song.url = response.url.href
}
}
return song
}
func decodeBitrate(data []byte) int {
bitRateMap := map[int]map[int][]int{
0: {
3: {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 500},
2: {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 500},
1: {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 500},
},
3: {
3: {0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 500},
2: {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 500},
1: {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 500},
},
2: {
3: {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 500},
2: {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 500},
1: {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 500},
},
}
var pointer = 0
if strings.EqualFold(string(data[0:4]), "fLaC") {
return 999
}
if strings.EqualFold(string(data[0:3]), "ID3") {
pointer = 6
var size = 0
for index, value := range data[pointer : pointer+4] {
size = size + int((value&0x7f)<<(7*(3-index)))
}
pointer = 10 + size
}
header := data[pointer : pointer+4]
// https://www.allegro.cc/forums/thread/591512/674023
if len(header) == 4 &&
header[0] == 0xff &&
((header[1]>>5)&0x7) == 0x7 &&
((header[1]>>1)&0x3) != 0 &&
((header[2]>>4)&0xf) != 0xf &&
((header[2]>>2)&0x3) != 0x3 {
version := (header[1] >> 3) & 0x3
layer := (header[1] >> 1) & 0x3
bitrate := header[2] >> 4
return bitRateMap[int(version)][int(layer)][int(bitrate)]
}
return 0
}

163
src/proxy/proxy.go Normal file
View File

@ -0,0 +1,163 @@
package proxy
import (
"crypto/tls"
"fmt"
"host"
"net"
"net/http"
"net/http/httputil"
"net/url"
"path/filepath"
"processor"
"strings"
"time"
"utils"
)
type HttpHandler struct{}
var (
///api/song/enhance/player/url
///eapi/mlivestream/entrance/playlist/get
Path = []string{"/eapi/batch",
"/eapi/album/privilege",
"/eapi/cloudsearch/pc",
"/eapi/playlist/privilege",
"/eapi/song/enhance/player/url",
"/eapi/v1/playlist/manipulate/tracks", //download music
}
)
func InitProxy() {
fmt.Println("-------------------Init Proxy-----------------------")
//tlsBytes("server.crt", "server.key")
go startTlsServer(":443", "./server.crt", "./server.key", &HttpHandler{})
startServer(":80", &HttpHandler{})
}
func (h *HttpHandler) ServeHTTP(resp http.ResponseWriter, request *http.Request) {
fmt.Println(request.Host)
fmt.Println(request.URL.String())
fmt.Println(request.RequestURI)
if proxyDomain, ok := host.ProxyDomain[request.Host]; ok {
scheme := "http://"
if request.TLS != nil {
scheme = "https://"
}
requestURI := request.RequestURI
request.Header.Del("x-napm-retry")
request.Header.Add("X-Real-IP", "118.88.88.88")
if strings.Contains(requestURI, "http") {
requestURI = strings.ReplaceAll(requestURI, scheme+request.Host, "")
}
remote, err := url.Parse(scheme + proxyDomain + requestURI)
if err != nil {
panic(err)
}
proxy := httputil.NewSingleHostReverseProxy(remote)
needTransport := false
for _, path := range Path {
if strings.Contains(request.RequestURI, path) {
needTransport = true
break
}
}
if needTransport && request.Method == http.MethodPost {
proxy.Transport = &capturedTransport{}
fmt.Printf("Transport:%s(%s)\n", remote, request.Host)
} else {
proxy.Transport = &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig:
&tls.Config{ServerName: request.Host},
}
fmt.Printf("Direct:%s(%s)\n", remote, request.Host)
}
proxy.ServeHTTP(resp, request)
} else {
scheme := "http://"
if request.TLS != nil {
scheme = "https://"
}
requestURI := request.RequestURI
request.Header.Del("x-napm-retry")
request.Header.Add("X-Real-IP", "118.88.88.88")
if !strings.Contains(requestURI, "http") {
requestURI = scheme + request.Host + requestURI
}
request.Header.Del("x-napm-retry")
request.Header.Add("X-Real-IP", "118.88.88.88")
remote, err := url.Parse(requestURI)
if err != nil {
panic(err)
}
proxy := httputil.NewSingleHostReverseProxy(remote)
fmt.Printf("Direct:%s\n", remote)
proxy.ServeHTTP(resp, request)
}
}
type capturedTransport struct {
// Uncomment this if you want to capture the transport
// CapturedTransport http.RoundTripper
}
func (t *capturedTransport) RoundTrip(request *http.Request) (*http.Response, error) {
netease := processor.DecodeRequestBody(request)
response, err := http.DefaultTransport.RoundTrip(request)
if err != nil {
fmt.Println(err)
return response, err
}
processor.DecodeResponseBody(response, netease)
return response, err
}
func startTlsServer(addr, certFile, keyFile string, handler http.Handler) {
fmt.Printf("starting TLS Server %s\n", addr)
currentPath, error := utils.GetCurrentPath()
if error != nil {
fmt.Println(error)
currentPath = ""
}
//fmt.Println(currentPath)
certFile, _ = filepath.Abs(currentPath + certFile)
keyFile, _ = filepath.Abs(currentPath + keyFile)
s := &http.Server{
Addr: addr,
Handler: handler,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
err := s.ListenAndServeTLS(certFile, keyFile)
if err != nil {
panic(err)
}
}
func startServer(addr string, handler http.Handler) {
fmt.Printf("starting Server %s\n", addr)
s := &http.Server{
Addr: addr,
Handler: handler,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
err := s.ListenAndServe()
if err != nil {
panic(err)
}
}

64
src/utils/utils.go Normal file
View File

@ -0,0 +1,64 @@
package utils
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
)
func ReplaceAll(str string, expr string, replaceStr string) string {
reg := regexp.MustCompile(expr)
str = reg.ReplaceAllString(str, replaceStr)
return str
}
func ParseJson(data []byte) map[string]interface{} {
var result map[string]interface{}
d := json.NewDecoder(bytes.NewReader(data))
d.UseNumber()
d.Decode(&result)
return result
}
func ToJson(object interface{}) string {
json, err := json.Marshal(object)
if err != nil {
fmt.Println("ToJson Error",err)
return "{}"
}
return string(json)
}
func Exists(keys []string, h map[string]interface{}) bool {
for _, key := range keys {
if !Exist(key, h) {
return false
}
}
return true
}
func Exist(key string, h map[string]interface{}) bool {
_, ok := h[key]
return ok
}
func GetCurrentPath() (string, error) {
file, err := exec.LookPath(os.Args[0])
if err != nil {
return "", err
}
path, err := filepath.Abs(file)
if err != nil {
return "", err
}
i := strings.LastIndex(path, "/")
if i < 0 {
i = strings.LastIndex(path, "\\")
}
if i < 0 {
return "", errors.New(`error: Can't find "/" or "\".`)
}
return string(path[0 : i+1]), nil
}

20
src/version/version.go Normal file
View File

@ -0,0 +1,20 @@
package version
import "fmt"
var (
Version string
//will be overwritten automatically by the build system
GitCommit string
GoVersion string
BuildTime string
)
func FullVersion() string {
return fmt.Sprintf("Version: %6s \nGit commit: %6s \nGo version: %6s \nBuild time: %6s \n",
Version, GitCommit, GoVersion, BuildTime)
}
func AppVersion() string {
return Version
}