add source from qq;enable local vip;unlock sound effects;

This commit is contained in:
tofuliang 2021-10-02 00:38:18 +08:00
parent 322a29f784
commit 2df2740609
10 changed files with 592 additions and 102 deletions

3
app.go
View File

@ -42,6 +42,9 @@ func main() {
log.Println("searchLimit=", *config.SearchLimit)
log.Println("blockUpdate=", *config.BlockUpdate)
log.Println("BlockAds=", *config.BlockAds)
log.Println("EnableLocalVip=", *config.EnableLocalVip)
log.Println("UnlockSoundEffects=", *config.UnlockSoundEffects)
log.Println("QQCookieFile=", *config.QQCookieFile)
if host.InitHosts() == nil {
//go func() {
// // // terminal: $ go tool pprof -http=:8081 http://localhost:6060/debug/pprof/heap

View File

@ -47,6 +47,7 @@ const (
KuWoTag PlatformIdTag = "90000"
MiGuTag PlatformIdTag = "90001"
KuGouTag PlatformIdTag = "90002"
QQTag PlatformIdTag = "90003"
)
type SearchOrderBy int32

View File

@ -15,19 +15,22 @@ import (
)
var (
Port = flag.Int("p", 80, "specify server port,such as : \"80\"")
TLSPort = flag.Int("sp", 443, "specify server tls port,such as : \"443\"")
Source = flag.String("o", "kuwo", "specify server source,such as : \"kuwo\"")
CertFile = flag.String("c", "./server.crt", "specify server cert,such as : \"server.crt\"")
KeyFile = flag.String("k", "./server.key", "specify server cert key ,such as : \"server.key\"")
LogFile = flag.String("l", "", "specify log file ,such as : \"/var/log/unblockNeteaseMusic.log\"")
Mode = flag.Int("m", 1, "specify running mode1:hosts ,such as : \"1\"")
V = flag.Bool("v", false, "display version info")
EndPoint = flag.Bool("e", false, "enable replace song url")
ForceBestQuality = flag.Bool("b", false, "force the best music quality")
SearchLimit = flag.Int("sl", 0, "specify the number of songs searched on other platforms(the range is 0 to 3) ,such as : \"1\"")
BlockUpdate = flag.Bool("bu", false, "block version update message")
BlockAds = flag.Bool("ba", false, "block advertising requests")
Port = flag.Int("p", 80, "specify server port,such as : \"80\"")
TLSPort = flag.Int("sp", 443, "specify server tls port,such as : \"443\"")
Source = flag.String("o", "kuwo", "specify server source,such as : \"kuwo\"")
CertFile = flag.String("c", "./server.crt", "specify server cert,such as : \"server.crt\"")
KeyFile = flag.String("k", "./server.key", "specify server cert key ,such as : \"server.key\"")
LogFile = flag.String("l", "", "specify log file ,such as : \"/var/log/unblockNeteaseMusic.log\"")
Mode = flag.Int("m", 1, "specify running mode1:hosts ,such as : \"1\"")
V = flag.Bool("v", false, "display version info")
EndPoint = flag.Bool("e", false, "enable replace song url")
ForceBestQuality = flag.Bool("b", false, "force the best music quality")
SearchLimit = flag.Int("sl", 0, "specify the number of songs searched on other platforms(the range is 0 to 3) ,such as : \"1\"")
BlockUpdate = flag.Bool("bu", false, "block version update message")
BlockAds = flag.Bool("ba", false, "block advertising requests")
EnableLocalVip = flag.Bool("lv", false, "enable local vip")
UnlockSoundEffects = flag.Bool("sef", false, "unlock SoundEffects")
QQCookieFile = flag.String("qc", "./qq.cookie", "specify cookies file ,such as : \"qq.cookie\"")
)
func ValidParams() bool {
@ -62,7 +65,7 @@ func ValidParams() bool {
log.Println(err)
currentPath = ""
}
//log.Println(currentPath)
// log.Println(currentPath)
certFile, _ := filepath.Abs(*CertFile)
keyFile, _ := filepath.Abs(*KeyFile)
_, err = os.Open(certFile)
@ -80,8 +83,8 @@ func ValidParams() bool {
logFilePath, _ := filepath.Abs(*LogFile)
logFile, logErr := os.OpenFile(logFilePath, os.O_CREATE|os.O_RDWR|os.O_SYNC|os.O_APPEND, 0666)
if logErr != nil {
//log.Println("Fail to find unblockNeteaseMusic.log start Failed")
//panic(logErr)
// log.Println("Fail to find unblockNeteaseMusic.log start Failed")
// panic(logErr)
logFilePath, _ = filepath.Abs(currentPath + *LogFile)
} else {
logFile.Close()
@ -98,7 +101,7 @@ func ValidParams() bool {
if err != nil {
panic(err)
}
if (fileInfo.Size() >> 20) > 2 { //2M
if (fileInfo.Size() >> 20) > 2 { // 2M
logFile.Seek(0, io.SeekStart)
logFile.Truncate(0)
}

114
cookiestxt/cookiestxt.go Normal file
View File

@ -0,0 +1,114 @@
// Copyright 2017 Meng Zhuo.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package cookiestxt implement parser of cookies txt format that commonly supported by
// curl / wget / aria2c / chrome / firefox
//
// see http://www.cookiecentral.com/faq/#3.5 for more detail
package cookiestxt
import (
"bufio"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"
)
const (
// http://www.cookiecentral.com/faq/#3.5
// The domain that created AND that can read the variable.
domainIdx = iota
// A TRUE/FALSE value indicating if all machines within a given domain can access the variable. This value is set automatically by the browser, depending on the value you set for domain.
flagIdx
// The path within the domain that the variable is valid for.
pathIdx
// A TRUE/FALSE value indicating if a secure connection with the domain is needed to access the variable.
secureIdx
// The UNIX time that the variable will expire on. UNIX time is defined as the number of seconds since Jan 1, 1970 00:00:00 GMT.
expirationIdx
// The name of the variable.
nameIdx
// The value of the variable.
valueIdx
)
const (
httpOnlyPrefix = "#HttpOnly_"
fieldsCount = 6
)
// Parse cookie txt file format from input stream
func Parse(rd io.Reader) (cl []*http.Cookie, err error) {
scanner := bufio.NewScanner(rd)
var line int
for scanner.Scan() {
line++
trimed := strings.TrimSpace(scanner.Text())
if len(trimed) < fieldsCount {
continue
}
if trimed[0] == '#' && !strings.HasPrefix(trimed, httpOnlyPrefix) {
// comment
continue
}
var c *http.Cookie
c, err = ParseLine(scanner.Text())
if err != nil {
fmt.Errorf("cookiestxt line:%d, err:%s", line, err)
continue
}
cl = append(cl, c)
line++
}
err = scanner.Err()
return
}
// ParseLine parse single cookie from one line
func ParseLine(raw string) (c *http.Cookie, err error) {
f := strings.Fields(raw)
fl := len(f)
if fl < fieldsCount {
err = fmt.Errorf("expecting fields=6, got=%d", fl)
return
}
value := ""
if fl > 6 {
value = f[valueIdx]
}
c = &http.Cookie{
Raw: raw,
Name: f[nameIdx],
Value: value,
Path: f[pathIdx],
MaxAge: 0,
Secure: parseBool(f[secureIdx]),
}
var ts int64
ts, err = strconv.ParseInt(f[expirationIdx], 10, 64)
if err != nil {
return
}
c.Expires = time.Unix(ts, 0)
c.Domain = f[domainIdx]
if strings.HasPrefix(c.Domain, httpOnlyPrefix) {
c.HttpOnly = true
c.Domain = c.Domain[len(httpOnlyPrefix):]
}
return
}
func parseBool(input string) bool {
return input == "TRUE"
}

1
go.mod
View File

@ -3,5 +3,6 @@ module github.com/cnsilvan/UnblockNeteaseMusic
go 1.16
require (
github.com/mitchellh/mapstructure v1.4.2
golang.org/x/text v0.3.3
)

2
go.sum
View File

@ -1,3 +1,5 @@
github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo=
github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -6,15 +6,6 @@ import (
"crypto/md5"
"encoding/hex"
"encoding/json"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"github.com/cnsilvan/UnblockNeteaseMusic/cache"
"github.com/cnsilvan/UnblockNeteaseMusic/common"
"github.com/cnsilvan/UnblockNeteaseMusic/config"
@ -23,45 +14,58 @@ import (
"github.com/cnsilvan/UnblockNeteaseMusic/provider"
"github.com/cnsilvan/UnblockNeteaseMusic/utils"
"golang.org/x/text/width"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
)
var (
eApiKey = "e82ckenh8dichen8"
linuxApiKey = "rFgB&h#%2?^eDg:Q"
///api/song/enhance/player/url
///eapi/mlivestream/entrance/playlist/get
// /api/song/enhance/player/url
// /eapi/mlivestream/entrance/playlist/get
Path = map[string]int{
"/api/v3/playlist/detail": 1,
"/api/v3/song/detail": 1,
"/api/v6/playlist/detail": 1,
"/api/album/play": 1,
"/api/artist/privilege": 1,
"/api/album/privilege": 1,
"/api/v1/artist": 1,
"/api/v1/artist/songs": 1,
"/api/artist/top/song": 1,
"/api/v1/album": 1,
"/api/album/v3/detail": 1,
"/api/playlist/privilege": 1,
"/api/song/enhance/player/url": 1,
"/api/song/enhance/player/url/v1": 1,
"/api/song/enhance/download/url": 1,
"/batch": 2, //Search
"/api/batch": 1,
"/api/v1/search/get": 2, //IOS
"/api/v1/search/song/get": 2,
"/api/search/complex/get": 1,
"/api/search/complex/get/v2": 2, //Android
"/api/cloudsearch/pc": 3, //PC Value
"/api/v1/playlist/manipulate/tracks": 1,
"/api/song/like": 1,
"/api/v1/play/record": 1,
"/api/playlist/v4/detail": 1,
"/api/v1/radio/get": 1,
"/api/v1/discovery/recommend/songs": 1,
"/api/cloudsearch/get/web": 1,
"/api/song/enhance/privilege": 1,
"/api/osx/version": 1,
"/api/v3/playlist/detail": 1,
"/api/v3/song/detail": 1,
"/api/v6/playlist/detail": 1,
"/api/album/play": 1,
"/api/artist/privilege": 1,
"/api/album/privilege": 1,
"/api/v1/artist": 1,
"/api/v1/artist/songs": 1,
"/api/artist/top/song": 1,
"/api/v1/album": 1,
"/api/album/v3/detail": 1,
"/api/playlist/privilege": 1,
"/api/song/enhance/player/url": 1,
"/api/song/enhance/player/url/v1": 1,
"/api/song/enhance/download/url": 1,
"/batch": 2, // Search
"/api/batch": 1,
"/api/v1/search/get": 2, // IOS
"/api/v1/search/song/get": 2,
"/api/search/complex/get": 1,
"/api/search/complex/get/v2": 2, // Android
"/api/cloudsearch/pc": 3, // PC Value
"/api/v1/playlist/manipulate/tracks": 1,
"/api/song/like": 1,
"/api/v1/play/record": 1,
"/api/playlist/v4/detail": 1,
"/api/v1/radio/get": 1,
"/api/v1/discovery/recommend/songs": 1,
"/api/cloudsearch/get/web": 1,
"/api/song/enhance/privilege": 1,
"/api/osx/version": 1,
"/api/usertool/sound/mobile/promote": 1,
"/api/usertool/sound/mobile/theme": 1,
"/api/usertool/sound/mobile/animationList": 1,
"/api/usertool/sound/mobile/all": 1,
"/api/usertool/sound/mobile/detail": 1,
}
)
@ -84,7 +88,7 @@ func RequestBefore(request *http.Request) *Netease {
netease := &Netease{Path: request.URL.Path}
if request.Method == http.MethodPost && (strings.Contains(netease.Path, "/eapi/") || strings.Contains(netease.Path, "/api/linux/forward")) {
if *config.BlockAds && (strings.Index(netease.Path, "/eapi/ad/") == 0 || strings.Index(netease.Path, "/api/ad/") == 0) {
if *config.BlockAds && (strings.Contains(netease.Path, "api/ad/") || strings.Contains(netease.Path, "api/clientlog/upload") || strings.Contains(netease.Path, "api/feedback/weblog")) {
return nil
}
request.Header.Del("x-napm-retry")
@ -184,8 +188,20 @@ func RequestAfter(request *http.Request, response *http.Response, netease *Netea
if ok {
code = codeN.String()
}
if *config.BlockUpdate && strings.EqualFold(netease.Path, "/api/osx/version") {
if strings.EqualFold(netease.Path, "/api/osx/version") {
modified = disableUpdate(netease)
} else if strings.Contains(netease.Path, "/usertool/sound/") {
modified = unblockSoundEffects(netease.JsonBody)
} else if strings.Contains(netease.Path, "/batch") {
modified = localVIP(netease)
for key, resp := range netease.JsonBody {
if strings.Contains(key, "/usertool/sound/") {
modified = unblockSoundEffects(resp.(map[string]interface{}))
} else if *config.BlockAds && strings.Contains(netease.Path, "api/ad/") {
resp = &common.MapType{}
modified = true
}
}
} else if !netease.Web && (code == "401" || code == "512") && strings.Contains(netease.Path, "manipulate") {
modified = tryCollect(netease, request)
} else if !netease.Web && (code == "401" || code == "512") && strings.EqualFold(netease.Path, "/api/song/like") {
@ -200,31 +216,40 @@ func RequestAfter(request *http.Request, response *http.Response, netease *Netea
response.Header.Del("transfer-encoding")
response.Header.Del("content-encoding")
response.Header.Del("content-length")
//netease.JsonBody = netease.JsonBody
//log.Println("NeedRepackage")
// netease.JsonBody = netease.JsonBody
// log.Println("NeedRepackage")
modifiedJson, _ := json.Marshal(netease.JsonBody)
//log.Println(netease)
//log.Println(string(modifiedJson))
// log.Println(netease)
// log.Println(string(modifiedJson))
if netease.Encrypted {
modifiedJson = crypto.AesEncryptECB(modifiedJson, []byte(aeskey))
}
response.Body = ioutil.NopCloser(bytes.NewBuffer(modifiedJson))
} else {
//log.Println("NotNeedRepackage")
// log.Println("NotNeedRepackage")
responseHold := ioutil.NopCloser(bytes.NewBuffer(tmpBody))
response.Body = responseHold
}
//log.Println(utils.ToJson(netease.JsonBody))
// log.Println("netease.Path: " + netease.Path)
// log.Println("netease.JsonBody: " + utils.ToJson(netease.JsonBody) +
// "\nrequestRequestURI: " + request.RequestURI +
// "\nrequestHeader: " + utils.ToJson(request.Header) +
// "\nrequestMethod: " + request.Method +
// "\nrequestUserAgent: " + request.UserAgent())
} else {
responseHold := ioutil.NopCloser(bytes.NewBuffer(tmpBody))
response.Body = responseHold
}
} else {
//log.Println("Not Process")
// log.Println("Not Process: " + netease.Path)
}
}
func disableUpdate(netease *Netease) bool {
if !*config.BlockUpdate {
return false
}
modified := false
jsonBody := netease.JsonBody
if value, ok := jsonBody["updateFiles"]; ok {
@ -241,10 +266,75 @@ func disableUpdate(netease *Netease) bool {
// log.Println(string(modifiedJson))
return modified
}
func localVIP(netease *Netease) bool {
if !*config.EnableLocalVip {
return false
}
modified := false
if utils.Exist("/api/music-vip-membership/client/vip/info", netease.JsonBody) {
log.Println("localVIP has been triggered.")
modified = true
info := netease.JsonBody["/api/music-vip-membership/client/vip/info"]
expireTime, _ := info.(common.MapType)["data"].(common.MapType)["now"].(json.Number).Int64()
expireTime += 3162240000000
info.(common.MapType)["data"].(common.MapType)["redVipLevel"] = 7
info.(common.MapType)["data"].(common.MapType)["redVipAnnualCount"] = 1
info.(common.MapType)["data"].(common.MapType)["musicPackage"] = &common.MapType{"expireTime": expireTime, "vipCode": 230}
info.(common.MapType)["data"].(common.MapType)["associator"] = &common.MapType{"expireTime": expireTime}
}
return modified
}
func unblockSoundEffects(jsonBody map[string]interface{}) bool {
if !*config.UnlockSoundEffects {
return false
}
// JsonBody,_ := json.Marshal(jsonBody)
modified := false
if value, ok := jsonBody["data"]; ok {
switch value.(type) {
case common.SliceType:
if len(value.(common.SliceType)) > 0 {
modified = true
for _, data := range value.(common.SliceType) {
if datum, ok := data.(common.MapType); ok {
datum["type"] = 1
if utils.Exist("type", datum["sound"].(common.MapType)) {
datum["sound"].(common.MapType)["type"] = 1
}
if utils.Exist("type", datum["animation"].(common.MapType)) {
datum["animation"].(common.MapType)["type"] = 1
}
}
}
}
case common.MapType:
if utils.Exist("type", value.(common.MapType)) {
modified = true
value.(common.MapType)["type"] = 1
if utils.Exist("type", value.(common.MapType)["sound"].(common.MapType)) {
value.(common.MapType)["sound"].(common.MapType)["type"] = 1
}
if utils.Exist("type", value.(common.MapType)["animation"].(common.MapType)) {
value.(common.MapType)["animation"].(common.MapType)["type"] = 1
}
}
default:
}
}
// modifiedJson, _ := json.Marshal(jsonBody)
// log.Println("netease.JsonBody: " + string(JsonBody))
// log.Println("netease.modifiedJson: " + string(modifiedJson))
if modified {
log.Println("unblockSoundEffects has been triggered.")
}
return modified
}
func tryCollect(netease *Netease, request *http.Request) bool {
modified := false
//log.Println(utils.ToJson(netease))
// log.Println(utils.ToJson(netease))
if utils.Exist("trackIds", netease.Params) {
trackId := ""
switch netease.Params["trackIds"].(type) {
@ -287,7 +377,7 @@ func tryCollect(netease *Netease, request *http.Request) bool {
return modified
}
func tryLike(netease *Netease, request *http.Request) bool {
//log.Println("try like")
// log.Println("try like")
modified := false
if utils.Exist("trackId", netease.Params) {
trackId := netease.Params["trackId"].(string)
@ -371,8 +461,8 @@ func tryMatch(netease *Netease) bool {
default:
}
}
//modifiedJson, _ := json.Marshal(jsonBody)
//log.Println(string(modifiedJson))
// modifiedJson, _ := json.Marshal(jsonBody)
// log.Println(string(modifiedJson))
return modified
}
func searchGreySongs(data common.SliceType, netease *Netease) bool {
@ -423,9 +513,9 @@ func searchGreySong(data common.MapType, netease *Netease) bool {
song.Br = 128000
}
}
data["encodeType"] = data["type"] //web
data["level"] = "standard" //web
data["fee"] = 8 //web
data["encodeType"] = data["type"] // web
data["level"] = "standard" // web
data["fee"] = 8 // web
uri, err := url.Parse(song.Url)
if err != nil {
log.Println("url.Parse error:", song.Url)
@ -454,7 +544,7 @@ func searchGreySong(data common.MapType, netease *Netease) bool {
data["size"] = song.Size
data["freeTrialInfo"] = nil
data["code"] = 200
if strings.Contains(netease.Path, "download") { //calculate the file md5
if strings.Contains(netease.Path, "download") { // calculate the file md5
if !haveSongMd5 {
data["md5"] = calculateSongMd5(searchMusic, song.Url)
}
@ -465,7 +555,7 @@ func searchGreySong(data common.MapType, netease *Netease) bool {
} else {
}
//log.Println(utils.ToJson(data))
// log.Println(utils.ToJson(data))
return modified
}
func calculateSongMd5(music common.SearchMusic, songUrl string) string {
@ -489,7 +579,7 @@ func calculateSongMd5(music common.SearchMusic, songUrl string) string {
}
songMd5 = hex.EncodeToString(h.Sum(nil))
provider.UpdateCacheMd5(music, songMd5)
//log.Println("calculateSongMd5 songId:", songId, ",songUrl:", songUrl, ",md5:", songMd5)
// log.Println("calculateSongMd5 songId:", songId, ",songUrl:", songUrl, ",md5:", songMd5)
return songMd5
}
func processSliceJson(jsonSlice common.SliceType) bool {
@ -503,8 +593,8 @@ func processSliceJson(jsonSlice common.SliceType) bool {
needModify = processSliceJson(value.(common.SliceType)) || needModify
default:
//log.Printf("index(%T):%v\n", index, index)
//log.Printf("value(%T):%v\n", value, value)
// log.Printf("index(%T):%v\n", index, index)
// log.Printf("value(%T):%v\n", value, value)
}
}
return needModify
@ -513,7 +603,7 @@ func processMapJson(jsonMap common.MapType) bool {
needModify := false
if utils.Exists([]string{"st", "subp", "pl", "dl"}, jsonMap) {
if v, _ := jsonMap["st"]; v.(json.Number).String() != "0" {
//open gray song
// open gray song
jsonMap["st"] = 0
needModify = true
}
@ -537,7 +627,7 @@ func processMapJson(jsonMap common.MapType) bool {
case common.SliceType:
needModify = processSliceJson(value.(common.SliceType)) || needModify
default:
//if key == "fee" {
// if key == "fee" {
// fee := "0"
// switch value.(type) {
// case int:
@ -551,14 +641,14 @@ func processMapJson(jsonMap common.MapType) bool {
// jsonMap[key] = 0
// needModify = true
// }
//}
// }
}
}
return needModify
}
func unifiedMusicQuality(netease *Netease) {
//log.Println(fmt.Sprintf("%+v\n", utils.ToJson(netease.Params)))
// log.Println(fmt.Sprintf("%+v\n", utils.ToJson(netease.Params)))
netease.MusicQuality = common.Lossless
if !*config.ForceBestQuality {
if levelParam, ok := netease.Params["level"]; ok {
@ -588,7 +678,7 @@ func unifiedMusicQuality(netease *Netease) {
}
}
}
//log.Println(fmt.Sprintf("%+v\n", utils.ToJson(netease.MusicQuality)))
// log.Println(fmt.Sprintf("%+v\n", utils.ToJson(netease.MusicQuality)))
}
}
func generateEndpoint(netease *Netease) string {
@ -616,24 +706,24 @@ func generateEndpoint(netease *Netease) string {
}
}
netease.EndPoint = protocol + endPoint
//log.Println(fmt.Sprintf("%+v\n", utils.ToJson(netease.EndPoint)))
// log.Println(fmt.Sprintf("%+v\n", utils.ToJson(netease.EndPoint)))
return netease.EndPoint
}
func searchOtherPlatform(netease *Netease) *Netease {
//log.Println(utils.ToJson(netease))
// log.Println(utils.ToJson(netease))
if *config.SearchLimit > 0 && Path[netease.Path] == 2 {
var paramsMap map[string]interface{}
if utils.Exists([]string{"offset", "s"}, netease.Params) { //单曲
if utils.Exists([]string{"offset", "s"}, netease.Params) { // 单曲
netease.SearchPath = netease.Path
paramsMap = netease.Params
} else if utils.Exists([]string{"keyword", "scene"}, netease.Params) { //综合
} else if utils.Exists([]string{"keyword", "scene"}, netease.Params) { // 综合
netease.SearchPath = netease.Path
paramsMap = netease.Params
} else { //pc
} else { // pc
for k, v := range netease.Params {
//pc
// pc
if t, ok := Path[k]; ok {
if t == 3 { //search
if t == 3 { // search
if searchValue, ok := v.(string); ok {
paramsMap = utils.ParseJson([]byte(searchValue))
netease.SearchPath = k
@ -646,13 +736,13 @@ func searchOtherPlatform(netease *Netease) *Netease {
if paramsMap != nil {
var offset int64
if offsetJson, ok := paramsMap["offset"].(json.Number); ok {
//just offset=0
// just offset=0
if i, err := offsetJson.Int64(); err == nil {
offset = i
}
} else if offsetS, ok := paramsMap["offset"].(string); ok {
//just offset=0
// just offset=0
if i, err := strconv.ParseInt(offsetS, 10, 64); err == nil {
offset = i
}
@ -681,7 +771,7 @@ func searchOtherPlatform(netease *Netease) *Netease {
}
func trySearch(netease *Netease, ch chan []*common.Song) {
ch <- provider.SearchSongFromAllSource(common.SearchSong{Keyword: netease.SearchKey, Limit: *config.SearchLimit, OrderBy: common.PlatformDefault})
//log.Println(utils.ToJson(songs))
// log.Println(utils.ToJson(songs))
}
func tryAddOtherPlatformResult(netease *Netease) bool {
modified := false
@ -694,7 +784,7 @@ func tryAddOtherPlatformResult(netease *Netease) bool {
close(netease.SearchTaskChan)
}
}
if len(netease.SearchKey) > 0 && netease.JsonBody != nil && len(netease.SearchSongs) > 0 { //搜索页面
if len(netease.SearchKey) > 0 && netease.JsonBody != nil && len(netease.SearchSongs) > 0 { // 搜索页面
var orginalMap common.MapType
var orginalSongsKey string
var neteaseSongs common.SliceType
@ -707,7 +797,7 @@ func tryAddOtherPlatformResult(netease *Netease) bool {
}
}
} else if jBody, ok := netease.JsonBody["data"].(common.MapType); ok { //android 综合
} else if jBody, ok := netease.JsonBody["data"].(common.MapType); ok { // android 综合
if result, ok := jBody["complete"].(common.MapType); ok {
if s, ok := result["song"].(common.MapType); ok {
if neteaseSongs, ok = s["songs"].(common.SliceType); ok {
@ -719,10 +809,10 @@ func tryAddOtherPlatformResult(netease *Netease) bool {
}
} else if jBody, ok := netease.JsonBody["result"].(common.MapType); ok {
if neteaseSongs, ok = jBody["songs"].(common.SliceType); ok { //android&ios 单曲
if neteaseSongs, ok = jBody["songs"].(common.SliceType); ok { // android&ios 单曲
orginalMap = jBody
orginalSongsKey = "songs"
} else if s, ok := jBody["song"].(common.MapType); ok { //ios 综合
} else if s, ok := jBody["song"].(common.MapType); ok { // ios 综合
if neteaseSongs, ok = s["songs"].(common.SliceType); ok {
orginalMap = s
orginalSongsKey = "songs"
@ -757,7 +847,7 @@ func tryAddOtherPlatformResult(netease *Netease) bool {
idTag = common.KuWoTag
}
if _, ok := copySong["name"]; ok { //make sure ok
if _, ok := copySong["name"]; ok { // make sure ok
copySong["alia"] = []string{source}
if ar, ok := copySong["ar"]; ok {
artMap := make(common.MapType)

View File

@ -10,8 +10,7 @@ import (
kugou "github.com/cnsilvan/UnblockNeteaseMusic/provider/kugou"
"github.com/cnsilvan/UnblockNeteaseMusic/provider/kuwo"
"github.com/cnsilvan/UnblockNeteaseMusic/provider/migu"
//"github.com/cnsilvan/UnblockNeteaseMusic/provider/qq"
"github.com/cnsilvan/UnblockNeteaseMusic/provider/qq"
"net/http"
"net/url"
"strconv"
@ -44,6 +43,8 @@ func NewProvider(kind string) Provider {
return &kugou.KuGou{}
case "migu":
return &migu.Migu{}
case "qq":
return &qq.QQ{}
default:
return &kuwo.KuWo{}
}
@ -86,6 +87,8 @@ func Find(music common.SearchMusic) common.Song {
re = calculateSongInfo(GetProvider("kuwo").GetSongUrl(music, song))
} else if strings.Index(music.Id, string(common.MiGuTag)) == 0 {
re = calculateSongInfo(GetProvider("migu").GetSongUrl(music, song))
} else if strings.Index(music.Id, string(common.QQTag)) == 0 {
re = calculateSongInfo(GetProvider("qq").GetSongUrl(music, song))
} else {
}

245
provider/qq/qq.go Normal file
View File

@ -0,0 +1,245 @@
package qq
import (
"github.com/cnsilvan/UnblockNeteaseMusic/common"
"github.com/cnsilvan/UnblockNeteaseMusic/config"
"github.com/cnsilvan/UnblockNeteaseMusic/network"
"github.com/cnsilvan/UnblockNeteaseMusic/provider/base"
"github.com/cnsilvan/UnblockNeteaseMusic/utils"
"github.com/mitchellh/mapstructure"
"log"
"math/rand"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"sync"
"time"
)
type QQ struct{}
type typeSong struct {
Album struct {
Name string
}
File struct {
Media_Mid string
}
Mid string
Name string
Singer []struct {
Name string
}
}
type typeSongResult struct {
Code int
Data struct {
Sip []string
Midurlinfo []struct {
Purl string
}
}
}
type getSongConfig struct {
fmid string
mid string
cookies []*http.Cookie
song *common.Song
br string
format string
}
func (m *QQ) SearchSong(song common.SearchSong) (songs []*common.Song) {
song = base.PreSearchSong(song)
cookies := getCookies()
result, err := base.Fetch(
"https://c.y.qq.com/soso/fcgi-bin/client_search_cp?"+
"ct=24&qqmusic_ver=1298&new_json=1&remoteplace=txt.yqq.center&"+
"t=0&aggr=1&cr=1&catZhida=1&lossless=0&flag_qc=0&p=1&n=20&w="+
song.Keyword+
"&"+
"g_tk=5381&loginUin=0&hostUin=0&"+
"format=json&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq&needNewCode=0",
cookies, nil, true)
if err != nil {
log.Println(err)
return songs
}
data := result["data"]
if data != nil {
if dMap, ok := data.(common.MapType); ok {
if dSong, ok := dMap["song"].(common.MapType); ok {
if list, ok := dSong["list"].(common.SliceType); ok {
if ok && len(list) > 0 {
listLength := len(list)
maxIndex := listLength/2 + 1
if maxIndex > 10 {
maxIndex = 10
}
for index, matched := range list {
if index >= maxIndex {
break
}
qqSong := &typeSong{}
if err = mapstructure.Decode(matched, &qqSong); err == nil {
artists := make([]string, 2)
for _, singer := range qqSong.Singer {
artists = append(artists, singer.Name)
}
songResult := &common.Song{}
songResult.PlatformUniqueKey = matched.(common.MapType)
songResult.PlatformUniqueKey["UnKeyWord"] = song.Keyword
songResult.PlatformUniqueKey["Mid"] = qqSong.File.Media_Mid
songResult.PlatformUniqueKey["MusicId"] = qqSong.Mid
songResult.Source = "qq"
songResult.Name = qqSong.Name
songResult.Artist = strings.Join(artists, " & ")
songResult.AlbumName = qqSong.Album.Name
songResult.Id = string(common.QQTag) + qqSong.Mid
songResult.MatchScore, ok = base.CalScore(song, qqSong.Name, songResult.Artist, index, maxIndex)
if !ok {
continue
}
songs = append(songs, songResult)
}
}
}
}
}
}
}
return base.AfterSearchSong(song, songs)
}
func (m *QQ) GetSongUrl(searchSong common.SearchMusic, song *common.Song) *common.Song {
if fmid, ok := song.PlatformUniqueKey["Mid"].(string); ok {
if mid, ok := song.PlatformUniqueKey["MusicId"].(string); ok {
cookies := getCookies()
if cookies == nil {
format := "mp3"
br := "M800"
searchSong.Quality = common.Standard
conf := &getSongConfig{
fmid,
mid,
nil,
song,
br,
format,
}
if gotSong := getSong(conf); gotSong != nil {
song = gotSong
}
} else {
wg := sync.WaitGroup{}
wg.Add(3)
rand.Seed(time.Now().UnixNano())
songCh := make(chan *common.Song, 3)
for _, quality := range []map[string]string{{"M500": "mp3"}, {"M800": "mp3"}, {"F000": "flac"}} {
for br, format := range quality {
conf := &getSongConfig{
fmid,
mid,
cookies,
song,
br,
format,
}
go func(conf *getSongConfig) {
if gotSong := getSong(conf); gotSong != nil {
gotSong.PlatformUniqueKey["Quality"] = conf.br
songCh <- gotSong
}
wg.Done()
}(conf)
}
}
wg.Wait()
songs := make(map[string]*common.Song, 3)
for gotSong := range songCh {
songs[gotSong.PlatformUniqueKey["Quality"].(string)] = gotSong
if len(songCh) == 0 {
break
}
}
quality := "M500"
finished := false
for !finished {
switch searchSong.Quality {
case common.Standard:
quality = "M500"
case common.Higher:
fallthrough
case common.ExHigh:
quality = "M800"
case common.Lossless:
quality = "F000"
default:
quality = "M500"
}
if gotSong, ok := songs[quality]; ok {
song = gotSong
finished = true
}
searchSong.Quality--
}
}
}
}
return song
}
func getSong(config *getSongConfig) *common.Song {
guid := utils.ToFixed(rand.Float64()*10000000, 0)
rawQueryData := `{"req_0":{"module":"vkey.GetVkeyServer","method":"CgiGetVkey","param":{"guid":"` +
strconv.Itoa(int(guid)) +
`","loginflag":1,"filename":["` + config.br + config.fmid + "." + config.format +
`"],"songmid":["` + config.mid + `"],"songtype":[0],"uin":"0","platform":"20"}}}`
clientRequest := network.ClientRequest{
Method: http.MethodGet,
ForbiddenEncodeQuery: true,
RemoteUrl: "https://u.y.qq.com/cgi-bin/musicu.fcg?data=" + url.QueryEscape(rawQueryData),
Proxy: true,
Cookies: config.cookies,
}
resp, err := network.Request(&clientRequest)
if err != nil {
log.Println(err)
}
defer resp.Body.Close()
body, err := network.StealResponseBody(resp)
songData := utils.ParseJsonV2(body)
songResult := &typeSongResult{}
if err = mapstructure.Decode(songData["req_0"], &songResult); err == nil {
if songResult.Data.Midurlinfo[0].Purl != "" {
config.song.Url = songResult.Data.Sip[0] + songResult.Data.Midurlinfo[0].Purl
return config.song
} else {
log.Println(config.song.PlatformUniqueKey["UnKeyWord"].(string) + "该歌曲QQ音乐版权保护")
// log.Println(utils.ToJson(songData))
}
}
return nil
}
func (m *QQ) ParseSong(searchSong common.SearchSong) *common.Song {
song := &common.Song{}
songs := m.SearchSong(searchSong)
if len(songs) > 0 {
song = m.GetSongUrl(common.SearchMusic{Quality: searchSong.Quality}, songs[0])
}
return song
}
func getCookies() []*http.Cookie {
if _, err := os.Stat(*config.QQCookieFile); os.IsNotExist(err) {
return nil
}
return utils.ParseCookies(*config.QQCookieFile)
}

View File

@ -1,6 +1,7 @@
package utils
import (
"bufio"
"bytes"
"compress/gzip"
"crypto/md5"
@ -9,9 +10,12 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/cnsilvan/UnblockNeteaseMusic/cookiestxt"
"io"
"io/ioutil"
"log"
"math"
"net/http"
"os"
"os/exec"
"path/filepath"
@ -302,3 +306,27 @@ func ParseSingerKeyWord(data string) []string {
sort.Sort(ByLenSort(keys))
return keys
}
func round(num float64) int {
return int(num + math.Copysign(0.5, num))
}
func ToFixed(num float64, precision int) float64 {
output := math.Pow(10, float64(precision))
return float64(round(num*output)) / output
}
func ParseCookies(file string) []*http.Cookie {
fl, err := os.Open(file)
if err != nil {
fmt.Println(file, err)
return nil
}
defer fl.Close()
r := bufio.NewReader(fl)
cl, err := cookiestxt.Parse(r)
if err != nil {
return nil
}
return cl
}