mirror of
https://github.com/cnsilvan/UnblockNeteaseMusic.git
synced 2025-01-07 03:27:15 +08:00
add source from qq;enable local vip;unlock sound effects;
This commit is contained in:
parent
322a29f784
commit
2df2740609
3
app.go
3
app.go
@ -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
|
||||
|
@ -47,6 +47,7 @@ const (
|
||||
KuWoTag PlatformIdTag = "90000"
|
||||
MiGuTag PlatformIdTag = "90001"
|
||||
KuGouTag PlatformIdTag = "90002"
|
||||
QQTag PlatformIdTag = "90003"
|
||||
)
|
||||
|
||||
type SearchOrderBy int32
|
||||
|
@ -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 mode(1: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 mode(1: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
114
cookiestxt/cookiestxt.go
Normal 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
1
go.mod
@ -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
2
go.sum
@ -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=
|
||||
|
@ -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)
|
||||
|
@ -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
245
provider/qq/qq.go
Normal 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¬ice=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)
|
||||
}
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user