Compare commits

..

15 Commits

Author SHA1 Message Date
ZxwyWebSite
9155f34105 2024-06-22 v1.0.3.0622 2024-06-22 23:34:25 +08:00
ZxwyWebSite
3437281f73 fix action build 2024-06-15 01:32:06 +08:00
ZxwyWebSite
55e7c6227e 2024-06-14 v1.0.3.0614 2024-06-15 01:03:38 +08:00
ZxwyWebSite
76e7f5a1b6 2024-05-25 Special Ver 2024-05-25 02:02:14 +08:00
ZxwyWebSite
70238162c1 fix remote module address 2024-05-25 01:43:31 +08:00
Zxwy
d7a2563a98
Merge pull request #30 from ZxwyWebSite/go1.20
support go1.20 and action build
2024-05-25 01:37:47 +08:00
ZxwyWebSite
05b8f3766c support go1.20 and action build 2024-05-25 01:26:48 +08:00
ZxwyWebSite
30b3260617 2024-05-18 v1.0.3.0518 2024-05-18 19:22:22 +08:00
ZxwyWebSite
e402c10381 2024-04-30 v1.0.3.0430 2024-05-01 04:10:23 +08:00
ZxwyWebSite
1ddc08d3b4 2024-03-16 v1.0.3-rc2 2024-03-17 02:27:21 +08:00
ZxwyWebSite
0b44b071c0 2024-02-22 v1.0.3-rc1 2024-02-22 21:54:52 +08:00
ZxwyWebSite
07c32ab6d4 2024-02-20 v1.0.3-fix 2024-02-21 03:49:01 +08:00
ZxwyWebSite
2dea36c3ce 2024-02-15 v1.0.3-pre 2024-02-15 23:55:41 +08:00
ZxwyWebSite
32c2617f3f 2024-01-31 v1.0.2-b12-d3 2024-02-01 14:52:43 +08:00
ZxwyWebSite
ad7614c564 2024-01-21 v1.0.2-b12-d2 2024-01-21 23:23:47 +08:00
82 changed files with 7347 additions and 1738 deletions

107
.github/workflows/action.yml vendored Normal file
View File

@ -0,0 +1,107 @@
name: Action
on:
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
build:
runs-on: ubuntu-20.04
steps:
- name: Checkout git repo
uses: actions/checkout@v4
with:
path: ./repo
fetch-depth: 0
- name: Set up Golang
uses: actions/setup-go@v5
with:
go-version: '1.21'
- name: Install Dependencies
run: |
go version && go env && export PATH=$PATH:$(go env GOPATH)/bin
go install golang.org/dl/go1.20.14@latest && go1.20.14 download && go1.20.14 version
sudo apt-get update
sudo apt-get -y install gcc-mingw-w64-x86-64
sudo apt-get -y install gcc-arm-linux-gnueabihf libc6-dev-armhf-cross
sudo apt-get -y install gcc-aarch64-linux-gnu libc6-dev-arm64-cross
wget -q https://dl.google.com/android/repository/android-ndk-r26b-linux.zip && unzip -d ~ android-ndk-r26b-linux.zip && rm android-ndk-r26b-linux.zip
- name: Fetch Modules
run: |
wget -q "https://r2eu.zxwy.link/gh/lx-source/static/ztool_20240525.zip" -O ztool.zip && unzip ztool.zip && rm ztool.zip
wget -q "https://r2eu.zxwy.link/gh/lx-source/static/cr-go-sdk_20240525.zip" -O cr-go-sdk.zip && unzip cr-go-sdk.zip && rm cr-go-sdk.zip
- name: Run Action
run: cd repo && go run action.go && mv dist ../
- name: Short SHA
uses: benjlevesque/short-sha@v3.0
id: short-sha
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: lx-source-bin_${{ env.SHA }}
path: ./dist
- name: Generate Changelog
run: cd repo && echo PACKAGE_VERSION=`go run release.go` >> $GITHUB_ENV && mv changelog.md ../
- name: Create git tag
uses: pkgdeps/git-tag-action@v3
with:
github_token: ${{ github.token }}
github_repo: ${{ github.repository }}
version: ${{ env.PACKAGE_VERSION }}
git_commit_sha: ${{ github.sha }}
git_tag_prefix: "v"
- name: Release
uses: softprops/action-gh-release@v2
with:
body_path: ./changelog.md
prerelease: false
draft: false
tag_name: v${{ env.PACKAGE_VERSION }}
files: |
./dist/lx-source-android-386.zip
./dist/lx-source-android-amd64.zip
./dist/lx-source-android-arm.zip
./dist/lx-source-android-arm64.zip
./dist/lx-source-darwin-amd64v2-go1.20.14.zip
./dist/lx-source-darwin-amd64v3-go1.20.14.zip
./dist/lx-source-darwin-arm64-go1.20.14.zip
./dist/lx-source-linux-amd64v1.zip
./dist/lx-source-linux-amd64v2.zip
./dist/lx-source-linux-amd64v3.zip
./dist/lx-source-linux-amd64v4.zip
./dist/lx-source-linux-arm5.zip
./dist/lx-source-linux-arm5-go1.20.14.zip
./dist/lx-source-linux-arm6.zip
./dist/lx-source-linux-arm6-go1.20.14.zip
./dist/lx-source-linux-arm64.zip
./dist/lx-source-linux-arm64-go1.20.14.zip
./dist/lx-source-linux-arm7.zip
./dist/lx-source-linux-arm7-go1.20.14.zip
./dist/lx-source-linux-mips64hardfloat-go1.20.14.zip
./dist/lx-source-linux-mips64lehardfloat-go1.20.14.zip
./dist/lx-source-linux-mips64lesoftfloat-go1.20.14.zip
./dist/lx-source-linux-mips64softfloat-go1.20.14.zip
./dist/lx-source-linux-mipshardfloat-go1.20.14.zip
./dist/lx-source-linux-mipslehardfloat-go1.20.14.zip
./dist/lx-source-linux-mipslesoftfloat-go1.20.14.zip
./dist/lx-source-linux-mipssoftfloat-go1.20.14.zip
./dist/lx-source-windows-amd64v1-go1.20.14.zip
./dist/lx-source-windows-amd64v2-go1.20.14.zip
./dist/lx-source-windows-amd64v2.zip
./dist/lx-source-windows-amd64v3-go1.20.14.zip
./dist/lx-source-windows-amd64v3.zip
./dist/lx-source-windows-amd64v4.zip
env:
GITHUB_TOKEN: ${{ github.token }}

1
.gitignore vendored
View File

@ -6,4 +6,3 @@ data/
test.go
test_test.go
# src/sources/builtin/
rsrc_windows_amd64.syso

532
action.go Normal file
View File

@ -0,0 +1,532 @@
//go:build ignore
package main
import (
"archive/zip"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
)
const (
// 运行参数
args_name = `lx-source` // 程序名称
args_path = `dist/` // 输出目录
args_zpak = true // 打包文件
// args_repo = `repo/` // 源码目录
args_home = `/home/runner` // 用户目录
)
var (
workDir string
homeDir string
)
func init() {
if runtime.GOOS != `linux` {
fmt.Println(`不兼容的运行环境:`, runtime.GOOS)
os.Exit(0)
}
workDir, _ = os.Getwd()
fmt.Println(`运行目录:`, workDir)
homeDir = os.Getenv(`HOME`)
if homeDir == `` {
homeDir = args_home
}
fmt.Println(`用户目录:`, homeDir)
}
type (
// 架构参数 [v2]
list_vers map[string]struct {
Tags string
}
// CGO参数 [nil]
list_cgos struct {
AR string
CC string
CXX string
}
// 架构列表 [amd64]
list_arch map[string]struct {
Cgos *list_cgos
Vers list_vers
Venv string // 覆盖架构参数名 'mipsle'->'GOMIPS'
}
// 目标系统 [linux]
list_goos map[string]struct {
Arch list_arch
}
// 编译环境 [go1.20.14]
list_conf map[string]struct {
Args []string
GoOS list_goos
}
)
// 构建参数
var def_args = []string{
`-trimpath`, `-buildvcs=false`,
`-ldflags`, `-s -w -linkmode external`,
}
type param struct {
GoVer string // 环境 go1.20.14
GoOS string // 系统 linux
GoArch string // 架构 amd64
GoIns string // 指令 GOAMD64=v2
Args []string // 参数 ldflags
Tag string // 标志 go_json
Cgos *list_cgos
Venv string
}
// 获取相对用户目录
func home(str string) string {
return homeDir + `/` + str
}
// 检测环境是否存在
func chkenv(s ...string) (err error) {
for _, f := range s {
if _, e := exec.LookPath(f); e != nil && !errors.Is(e, exec.ErrDot) {
err = fmt.Errorf(`未找到指定环境: %s`, e)
break
}
}
return
}
func main() {
var def_list = list_conf{
`go`: {
Args: def_args,
GoOS: list_goos{
`linux`: {
Arch: list_arch{
`amd64`: {
Cgos: &list_cgos{
AR: `x86_64-linux-gnu-ar`,
CC: `x86_64-linux-gnu-gcc`,
CXX: `x86_64-linux-gnu-g++`,
},
Vers: list_vers{
`v1`: {
Tags: `go_json`,
},
`v2`: {
Tags: `go_json`,
},
`v3`: {
Tags: `sonic avx`,
},
`v4`: {
Tags: `sonic avx`,
},
},
},
`arm`: {
Cgos: &list_cgos{
AR: `arm-linux-gnueabihf-gcc-ar`,
CC: `arm-linux-gnueabihf-gcc`,
CXX: `arm-linux-gnueabihf-cpp`,
},
Vers: list_vers{
`5`: {
Tags: `go_json`,
},
`6`: {
Tags: `go_json`,
},
`7`: {
Tags: `go_json`,
},
},
},
`arm64`: {
Cgos: &list_cgos{
AR: `aarch64-linux-gnu-gcc-ar`,
CC: `aarch64-linux-gnu-gcc`,
CXX: `aarch64-linux-gnu-cpp`,
},
Vers: list_vers{
``: {
Tags: `go_json`,
},
},
},
},
},
`windows`: {
Arch: list_arch{
`amd64`: {
Cgos: &list_cgos{
AR: `x86_64-w64-mingw32-ar`,
CC: `x86_64-w64-mingw32-gcc`,
CXX: `x86_64-w64-mingw32-cpp`,
},
Vers: list_vers{
`v2`: {
Tags: `go_json`,
},
`v3`: {
Tags: `sonic avx`,
},
`v4`: {
Tags: `sonic avx`,
},
},
},
},
},
`android`: {
Arch: list_arch{
`amd64`: {
Cgos: &list_cgos{
AR: home(`android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar`),
CC: home(`android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android24-clang`),
CXX: home(`android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android24-clang++`),
},
Vers: list_vers{
``: {
Tags: `go_json`,
},
},
},
`arm64`: {
Cgos: &list_cgos{
AR: home(`android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar`),
CC: home(`android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang`),
CXX: home(`android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang++`),
},
Vers: list_vers{
``: {
Tags: `go_json`,
},
},
},
`386`: {
Cgos: &list_cgos{
AR: home(`android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar`),
CC: home(`android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android24-clang`),
CXX: home(`android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android24-clang++`),
},
Vers: list_vers{
``: {
Tags: `go_json`,
},
},
},
`arm`: {
Cgos: &list_cgos{
AR: home(`android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar`),
CC: home(`android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi24-clang`),
CXX: home(`android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi24-clang++`),
},
Vers: list_vers{
``: {
Tags: `go_json`,
},
},
},
},
},
},
},
home(`go/bin/go1.20.14`): {
Args: []string{
`-trimpath`, `-buildvcs=false`,
`-ldflags`, `-s -w -extldflags '-v -static'`,
},
GoOS: list_goos{
`windows`: {
Arch: list_arch{
`amd64`: {
Cgos: &list_cgos{
AR: `x86_64-w64-mingw32-ar`,
CC: `x86_64-w64-mingw32-gcc`,
CXX: `x86_64-w64-mingw32-cpp`,
},
Vers: list_vers{
`v1`: {
Tags: `go_json`,
},
`v2`: {
Tags: `go_json`,
},
`v3`: {
Tags: `sonic avx`,
},
},
},
},
},
`linux`: {
Arch: list_arch{
`arm`: {
Cgos: &list_cgos{
AR: `arm-linux-gnueabihf-gcc-ar`,
CC: `arm-linux-gnueabihf-gcc`,
CXX: `arm-linux-gnueabihf-cpp`,
},
Vers: list_vers{
`5`: {
Tags: `go_json`,
},
`6`: {
Tags: `go_json`,
},
`7`: {
Tags: `go_json`,
},
},
},
`arm64`: {
Cgos: &list_cgos{
AR: `aarch64-linux-gnu-gcc-ar`,
CC: `aarch64-linux-gnu-gcc`,
CXX: `aarch64-linux-gnu-cpp`,
},
Vers: list_vers{
``: {
Tags: `go_json`,
},
},
},
// 针对部分OpenWrt路由器系统 暂不支持开启CGO
`mips`: {
Vers: list_vers{
`hardfloat`: {
Tags: `go_json`,
},
`softfloat`: {
Tags: `go_json`,
},
},
},
`mipsle`: {
Vers: list_vers{
`hardfloat`: {
Tags: `go_json`,
},
`softfloat`: {
Tags: `go_json`,
},
},
Venv: `MIPS`,
},
`mips64`: {
Vers: list_vers{
`hardfloat`: {
Tags: `go_json`,
},
`softfloat`: {
Tags: `go_json`,
},
},
},
`mips64le`: {
Vers: list_vers{
`hardfloat`: {
Tags: `go_json`,
},
`softfloat`: {
Tags: `go_json`,
},
},
Venv: `MIPS64`,
},
},
},
// Mac OS
`darwin`: {
Arch: list_arch{
`amd64`: {
Vers: list_vers{
`v2`: {
Tags: `go_json`,
},
`v3`: {
Tags: `sonic avx`,
},
},
},
`arm64`: {
Vers: list_vers{
``: {
Tags: `go_json`,
},
},
},
},
},
},
},
}
fmt.Printf(`
================================
| Action 一键编译脚本
| 程序名称%v
| 输出目录%v
| 打包文件%v
================================
`, args_name, args_path, args_zpak)
// 解析配置文件
for goVer, conf_list := range def_list {
// 环境检测
if err := chkenv(goVer); err != nil {
fmt.Println(err, `跳过该环境`)
continue
}
for goOS, goos_list := range conf_list.GoOS {
for goArch, arch_list := range goos_list.Arch {
// 工具链检测
if arch_list.Cgos != nil {
if err := chkenv(
arch_list.Cgos.AR,
arch_list.Cgos.CC,
arch_list.Cgos.CXX,
); err != nil {
fmt.Println(err, `跳过该架构`)
continue
}
}
for goIns, vers_list := range arch_list.Vers {
// 构建程序二进制
if err := build(&param{
GoVer: goVer,
GoOS: goOS,
GoArch: goArch,
GoIns: goIns,
Args: conf_list.Args,
Tag: vers_list.Tags,
Cgos: arch_list.Cgos,
Venv: arch_list.Venv,
}); err != nil {
fmt.Println(`err:`, err)
}
}
}
}
}
fmt.Println(`执行结束`)
}
func build(p *param) (err error) {
// 拼接程序名称
var b strings.Builder
b.WriteString(args_name) // lx-source
b.WriteByte('-') // lx-source-
b.WriteString(p.GoOS) // lx-source-linux
b.WriteByte('-') // lx-source-linux-
b.WriteString(p.GoArch) // lx-source-linux-amd64
/*var digit byte
if p.Venv != `` {
digit = p.Venv[len(p.Venv)-1]
} else {
digit = p.GoArch[len(p.GoArch)-1]
}
if !unicode.IsDigit(rune(digit)) {
// 架构名结尾不是数字的再加一个连字符
b.WriteByte('-') // lx-source-linux-mipsle-softfloat
}*/
b.WriteString(p.GoIns) // lx-source-linux-amd64v2
if biname := filepath.Base(p.GoVer); biname != `go` {
b.WriteByte('-') // lx-source-linux-amd64v2-
b.WriteString(biname) // lx-source-linux-amd64v2-go1.20.14
}
// 拼接输出名称
oname := args_path + b.String() // dist/lx-source-linux-amd64v2
if p.GoOS == `windows` {
oname += `.exe` // dist/lx-source-linux-amd64v2.exe
}
fmt.Println(`开始编译:`, oname)
fmt.Printf("编译参数: %+v\n", *p)
// 填入参数并构建
var args = []string{
`build`, `-o`, oname,
// `-asmflags=-trimpath="` + workDir + `"`,
// `-gcflags=-trimpath="` + workDir + `"`,
`-tags`, p.Tag,
}
cmd := exec.Command(
p.GoVer,
// append(append(args, p.Args...), args_repo)...,
append(args, p.Args...)...,
)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = cmd.Stdout
cmd.Dir = workDir
cmd.Env = append(os.Environ(), []string{
`GOOS=` + p.GoOS,
`GOARCH=` + p.GoArch,
}...)
if p.Cgos != nil {
cmd.Env = append(cmd.Env, []string{
`AR=` + p.Cgos.AR,
`CC=` + p.Cgos.CC,
`CXX=` + p.Cgos.CXX,
`CGO_ENABLED=1`,
}...)
} /*else {
cmd.Env = append(cmd.Env, `CGO_ENABLED=0`)
}*/
if p.GoIns != `` {
if p.Venv != `` {
cmd.Env = append(cmd.Env, `GO`+p.Venv+`=`+p.GoIns)
} else {
cmd.Env = append(cmd.Env, `GO`+strings.ToUpper(p.GoArch)+`=`+p.GoIns)
}
}
if err = cmd.Start(); err == nil {
err = cmd.Wait()
}
if err != nil || !args_zpak {
return
}
// 打包输出文件
/*apath := filepath.Join(args_path, `archieve`)
if _, e := os.Stat(apath); e != nil {
if os.IsNotExist(e) {
err = os.MkdirAll(apath, os.ModePerm)
if err != nil {
return
}
}
}*/
zipname := filepath.Join(args_path, b.String()+`.zip`)
fmt.Println(`打包文件:`, zipname)
zipfile, err := os.Create(zipname)
if err != nil {
return err
}
archive := zip.NewWriter(zipfile)
info, err := os.Lstat(oname)
if err == nil {
header, _ := zip.FileInfoHeader(info)
header.Method = zip.Deflate
header.Name = filepath.Base(oname)
writer, err := archive.CreateHeader(header)
if err == nil {
file, err := os.Open(oname)
if err == nil {
_, err = io.Copy(writer, file)
file.Close()
if err == nil {
err = os.Remove(oname)
}
}
}
}
archive.Close()
zipfile.Close()
return err
}

View File

@ -39,6 +39,8 @@ const (
args_zpak = true // 打包文件
)
var workDir string
// 编译
func doCompile(v_os, v_arch, v_archv, v_cc string) error {
// 构建 | 目标系统 | 目标架构 | 优化等级 | 不包含调试信息 | 使用外部链接器 | 输出详细操作 | 静态编译 | JSON解释器
@ -52,7 +54,11 @@ func doCompile(v_os, v_arch, v_archv, v_cc string) error {
return ztool.Str_FastConcat(name, v_archv, wexe)
}()
pname := filepath.Clean(ztool.Str_FastConcat(args_path, fname))
cmd := ztool.Str_FastConcat(`go build -o `, pname, ` -ldflags "-s -w -linkmode external -extldflags ''" -tags "go_json"`) // go_json | json(std) | jsoniter | sonic
cmd := ztool.Str_FastConcat(
`go build -o `, pname,
` -gcflags=-trimpath="`, workDir, `" -asmflags=-trimpath="`, workDir, `" -trimpath -buildvcs=false`,
` -ldflags "-s -w -linkmode external" -tags "go_json"`, // go_json | json(std) | jsoniter | sonic
)
// 输出要执行的命令
ztool.Cmd_FastPrintln(ztool.Str_FastConcat(`执行命令:`, cmd))
// 设置环境&执行编译
@ -84,7 +90,11 @@ func doCompile(v_os, v_arch, v_archv, v_cc string) error {
if !ztool.Fbj_IsExists(`archieve`) {
os.MkdirAll(filepath.Join(args_path, `archieve`), 0755)
}
if err := ztool.Pak_ZipFile(pname, ztool.Str_FastConcat(filepath.Join(args_path, `archieve`, fname), `.zip`), ztool.Pak_ZipConfig{UnPath: true}); err != nil {
if err := ztool.Pak_ZipFile(
pname,
filepath.Join(args_path, `archieve`, ztool.Str_LastBefore(fname, `.`))+`.zip`,
ztool.Pak_ZipConfig{UnPath: true},
); err != nil {
ztool.Cmd_FastPrintln(ztool.Str_FastConcat(`打包["`, pname, `"]出错:`, err.Error()))
} else {
ztool.Cmd_FastPrintln(ztool.Str_FastConcat(`打包["`, pname, `"]完成`))
@ -98,6 +108,7 @@ func init() {
ztool.Cmd_FastPrintln("简易脚本未对Linux以外系统做适配请复制执行以下命令编译\ngo build -ldflags \"-s -w\" -tags \"go_json\"\n如无报错则会在本目录生成名为lx-source的可执行文件。")
os.Exit(1)
}
workDir, _ = os.Getwd()
ztool.Cmd_FastPrintln(ztool.Str_FastConcat(`
================================
| Golang 一键编译脚本

48
go.mod
View File

@ -1,41 +1,51 @@
module lx-source
go 1.21.4
go 1.20
require (
github.com/ZxwyWebSite/cr-go-sdk v0.0.2
github.com/ZxwyWebSite/ztool v0.0.1
github.com/gin-contrib/gzip v0.0.6
github.com/gin-contrib/gzip v1.0.0
github.com/gin-gonic/gin v1.9.1
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/google/uuid v1.6.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
)
require (
github.com/bytedance/sonic v1.10.2 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/bytedance/sonic v1.11.5 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.3 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/cors v1.7.1
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/go-playground/validator/v10 v10.19.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/pelletier/go-toml/v2 v2.2.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/text v0.9.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.7.0 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace github.com/ZxwyWebSite/ztool v0.0.1 => ./pkg/ztool // ../ztool
replace (
github.com/ZxwyWebSite/cr-go-sdk v0.0.2 => ../cr-go-sdk
github.com/ZxwyWebSite/ztool v0.0.1 => ../ztool // ./pkg/ztool
)

130
go.sum
View File

@ -1,67 +1,54 @@
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE=
github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/bytedance/sonic v1.11.5 h1:G00FYjjqll5iQ1PYXynbg/hyzqBqavH8Mo9/oTopd9k=
github.com/bytedance/sonic v1.11.5/go.mod h1:X2PC2giUdj/Cv2lliWFLk6c/DUQok5rViJSemeB0wDw=
github.com/bytedance/sonic/loader v0.1.0/go.mod h1:UmRT+IRTGKz/DAkzcEGzyVqQFJ7H9BqwBO3pm9H/+HY=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cloudwego/base64x v0.1.3 h1:b5J/l8xolB7dyDTTmhJP2oTs5LdrjyrUFuNxdfq5hAg=
github.com/cloudwego/base64x v0.1.3/go.mod h1:1+1K5BUHIQzyapgpF7LwvOGAEDicKtt1umPV+aN8pi8=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/cors v1.7.1 h1:s9SIppU/rk8enVvkzwiC2VK3UZ/0NNGsWfUKvV55rqs=
github.com/gin-contrib/cors v1.7.1/go.mod h1:n/Zj7B4xyrgk/cX1WCX2dkzFfaNm/xJb6oIUk7WTtps=
github.com/gin-contrib/gzip v1.0.0 h1:UKN586Po/92IDX6ie5CWLgMI81obiIp5nSP85T3wlTk=
github.com/gin-contrib/gzip v1.0.0/go.mod h1:CtG7tQrPB3vIBo6Gat9FVUsis+1emjvQqd66ME5TdnE=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
@ -70,71 +57,50 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pelletier/go-toml/v2 v2.2.1 h1:9TA9+T8+8CUCO2+WYnDLCgrYi9+omqKXyjDtosvtEhg=
github.com/pelletier/go-toml/v2 v2.2.1/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=

217
init.go Normal file
View File

@ -0,0 +1,217 @@
package main
import (
"encoding/base64"
"lx-source/src/caches"
"lx-source/src/caches/cloudcache"
"lx-source/src/caches/localcache"
"lx-source/src/env"
// "lx-source/src/sources"
// "lx-source/src/sources/builtin"
"net/http"
stdurl "net/url"
"path/filepath"
"github.com/ZxwyWebSite/cr-go-sdk"
"github.com/ZxwyWebSite/ztool"
"github.com/ZxwyWebSite/ztool/logs"
"github.com/ZxwyWebSite/ztool/zcypt"
"github.com/gin-gonic/gin"
)
// 生成连接码
func genAuth() {
ga := env.Loger.NewGroup(`LxM-Auth`)
// 检测Key是否存在, 否则生成并保存
if env.Config.Auth.ApiKey_Value == `` {
pass := zcypt.Base64ToString(base64.StdEncoding, zcypt.RandomBytes(4*4))
env.Config.Auth.ApiKey_Value = pass // env.Config.Apis.LxM_Auth
ga.Info(`已生成默认连接码: %q`, pass)
ga.Info(`可在配置文件 [Auth].ApiKey_Value 中修改`) //可在配置文件 [Apis].LxM_Auth 中修改, 填写 "null" 关闭验证
if err := env.Cfg.Save(``); err != nil {
ga.Error(`写入配置文件失败: %s, 将导致下次启动时连接码发生变化`, err)
}
}
if !env.Config.Auth.ApiKey_Enable {
ga.Warn(`已关闭Key验证, 公开部署可能导致安全隐患`)
} else {
ga.Warn(`已开启Key验证, 记得在脚本中填写 apipass=%q`, env.Config.Auth.ApiKey_Value)
}
ga.Free()
}
// 加载文件日志 (请在初始化配置文件后调用)
func loadFileLoger() {
// 最后加载FileLoger保证必要日志已输出 (Debug模式强制在控制台输出日志)
if env.Config.Main.LogPath != `` {
lg := env.Loger.NewGroup(`FileLoger`)
printout := env.Config.Main.Print // || env.Config.Main.Debug
f, do, err := env.Loger.SetOutFile(ztool.Str_FastConcat(env.RunPath, env.Config.Main.LogPath), printout)
if err == nil {
// env.Defer.Add(do)
env.Defer.Add(func() { do(); f.Close() })
env.Tasker.Add(`flog_flush`, func(loger *logs.Logger, now int64) error {
loger.Debug(`已写入文件并清理日志缓存`)
return do()
}, 3600, false)
gin.DefaultWriter = env.Loger.GetOutput()
gin.ForceConsoleColor()
// lg.Info(`文件日志初始化成功`)
} else {
lg.Error(`文件日志初始化失败:%v`, err)
}
lg.Free()
}
}
// 初始化基础功能
func initMain() {
// 载入内存缓存
storepath := env.RunPath + env.Config.Main.Store
env.Cache.MustRestore(storepath)
env.Defer.Add(func() { env.Cache.MustPersist(storepath) })
env.Tasker.Add(`memo_flush`, func(*logs.Logger, int64) error {
return env.Cache.Persist(storepath)
}, 3600, false)
// 初始化数据库
// idb := env.Loger.NewGroup(`InitDB`)
// switch `sqlite` {
// case `memo`:
// break
// case `sqlite`:
// err := database.InitDB(`data/data.db`)
// if err != nil {
// idb.Error(`数据库载入失败: %s`, err)
// }
// default:
// idb.Error(`未定义的数据库模式,请检查配置 [DataBase].Mode`)
// }
// idb.Free()
// 初始化代理
ipr := env.Loger.NewGroup(`InitProxy`)
switch env.Config.Source.FakeIP_Mode {
case `0`, `off`:
break
case `1`, `req`:
ipr.Fatal(`暂未实现此功能`)
case `2`, `val`:
if env.Config.Source.FakeIP_Value != `` {
ipr.Info(`已开启伪装IP当前值: %v`, env.Config.Source.FakeIP_Value)
ztool.Net_header[`X-Real-IP`] = env.Config.Source.FakeIP_Value
ztool.Net_header[`X-Forwarded-For`] = env.Config.Source.FakeIP_Value
} else {
ipr.Error(`伪装IP为空请检查配置 [Source].FakeIP_Value`)
}
default:
ipr.Error(`未定义的代理模式,请检查配置 [Source].FakeIP_Mode本次启动禁用IP伪装`)
}
if env.Config.Source.Proxy_Enable {
ipr.Debug(`ProxyAddr: %v`, env.Config.Source.Proxy_Address)
addr, err := stdurl.Parse(env.Config.Source.Proxy_Address)
if err != nil {
ipr.Error(`代理Url解析失败: %s, 将禁用代理功能`, err)
} else {
type chkRegion struct {
AmapFlag int `json:"amap_flag"`
IPFlag int `json:"ip_flag"`
AmapAddress string `json:"amap_address"`
Country string `json:"country"`
Flag int `json:"flag"`
Errcode int `json:"errcode"`
Status int `json:"status"`
Error string `json:"error"`
}
var out chkRegion
oldval := ztool.Net_client.Transport
ztool.Net_client.Transport = &http.Transport{Proxy: http.ProxyURL(addr)}
err := ztool.Net_Request(http.MethodGet,
`https://mips.kugou.com/check/iscn?&format=json`, nil,
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeader(ztool.Net_header)},
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&out)},
)
if err != nil {
ztool.Net_client.Transport = oldval
ipr.Error(`地区验证失败: %s, 已恢复默认配置`, err)
} else {
ipr.Debug(`Resp: %+v`, out)
if out.Flag != 1 {
ipr.Warn(`您正在使用非中国大陆(%v)代理,可能导致部分音乐不可用`, out.Country)
} else {
ipr.Warn(`代理开启成功请注意潜在的Cookie泄露问题`)
}
}
}
}
ipr.Free()
// 初始化缓存
icl := env.Loger.NewGroup(`InitCache`)
switch env.Config.Cache.Mode {
case `0`, `off`:
// NothingToDo... (已默认禁用缓存)
break
case `1`, `local`:
// 注由于需要修改LocalCachePath参数必须在InitRouter之前执行
cache, err := caches.New(&localcache.Cache{
Path: filepath.Join(env.RunPath, env.Config.Cache.Local_Path),
Bind: env.Config.Cache.Local_Bind,
})
if err != nil {
icl.Error(`驱动["local"]初始化失败: %v, 将禁用缓存功能`, err)
}
caches.UseCache = cache
icl.Warn(`本地缓存绑定地址:%q,请确认其与实际访问地址相符`, env.Config.Cache.Local_Bind)
// LocalCachePath = filepath.Join(runPath, env.Config.Cache.Local_Path)
// UseCache = &localcache.Cache{
// Path: LocalCachePath,
// Addr: env.Config.Apis.BindAddr,
// }
// icl.Info(`使用本地缓存,文件路径 %q绑定地址 %v`, LocalCachePath, env.Config.Apis.BindAddr)
case `2`, `cloudreve`:
icl.Warn(`欢迎使用新版 Cloudreve 驱动, 由 cr-go-sdk 提供强力支持`)
site := &cr.SiteObj{
Addr: env.Config.Cache.Cloud_Site,
ApiVer: cr.ApiV383,
Users: &cr.UserObj{
Mail: env.Config.Cache.Cloud_User,
Pass: env.Config.Cache.Cloud_Pass,
Cookie: cr.ParseCookie(env.Config.Cache.Cloud_Sess),
},
}
cache, err := caches.New(&cloudcache.Cache{
Site: site,
Path: env.Config.Cache.Cloud_Path,
})
if err != nil {
icl.Error(`驱动["cloudreve"]初始化失败: %v, 将禁用缓存功能`, err)
} else {
env.Tasker.Add(`cloud_sess`, func(l *logs.Logger, i int64) error {
if sess := site.Users.Cookie.String(); sess != env.Config.Cache.Cloud_Sess {
env.Config.Cache.Cloud_Sess = sess
}
return env.Cfg.Save(``)
}, 3600, true)
}
caches.UseCache = cache
default:
icl.Error(`未定义的缓存模式,请检查配置 [Cache].Mode本次启动禁用缓存`)
}
icl.Free()
// 初始化音乐源
// ise := env.Loger.NewGroup(`InitSource`)
// switch env.Config.Source.Mode {
// case `0`, `off`:
// break
// case `1`, `builtin`:
// sources.UseSource = &builtin.Source{}
// case `2`, `custom`:
// ise.Fatal(`暂未实现账号解析源`)
// default:
// ise.Error(`未定义的音乐源,请检查配置 [Source].Mode本次启动禁用内置源`)
// }
// ise.Free()
}

224
main.go
View File

@ -2,19 +2,16 @@ package main
import (
"context"
"encoding/base64"
"flag"
"lx-source/src/caches"
"lx-source/src/caches/localcache"
"io/fs"
"lx-source/src/env"
"lx-source/src/router"
"lx-source/src/sources"
"lx-source/src/sources/builtin"
"math/rand"
"lx-source/src/server"
"net/http"
"os"
"os/signal"
"path/filepath"
"strings"
"sync"
"sync/atomic"
"syscall"
"time"
@ -23,50 +20,6 @@ import (
"github.com/gin-gonic/gin"
)
func genAuth() {
ga := env.Loger.NewGroup(`LxM-Auth`)
// 检测Key是否存在, 否则生成并保存
if env.Config.Auth.ApiKey_Value == `` {
randomBytes := func(size int) []byte {
buf := make([]byte, size)
for i := 0; i < size; i++ {
buf[i] = byte(rand.New(rand.NewSource(time.Now().UnixNano() + int64(i*i+rand.Intn(256)))).Intn(256))
}
return buf
}
pass := base64.StdEncoding.EncodeToString(randomBytes(4 * 4))
env.Config.Auth.ApiKey_Value = pass // env.Config.Apis.LxM_Auth
ga.Info(`已生成默认连接码: %q`, pass)
ga.Info(`可在配置文件 [Auth].ApiKey_Value 中修改`) //可在配置文件 [Apis].LxM_Auth 中修改, 填写 "null" 关闭验证
if err := env.Cfg.Save(``); err != nil {
ga.Error(`写入配置文件失败: %s, 将导致下次启动时连接码发生变化`, err)
}
}
if !env.Config.Auth.ApiKey_Enable {
ga.Warn(`已关闭Key验证, 公开部署可能导致安全隐患`)
} else {
ga.Warn(`已开启Key验证, 记得在脚本中填写 apipass=%q`, env.Config.Auth.ApiKey_Value)
}
}
// 加载文件日志 (请在初始化配置文件后调用)
func loadFileLoger() {
// 最后加载FileLoger保证必要日志已输出 (Debug模式强制在控制台输出日志)
if env.Config.Main.LogPath != `` {
lg := env.Loger.NewGroup(`FileLoger`)
printout := env.Config.Main.Print // || env.Config.Main.Debug
_, do, err := env.Loger.SetOutFile(ztool.Str_FastConcat(env.RunPath, env.Config.Main.LogPath), printout)
if err == nil {
env.Defer.Add(do)
gin.DefaultWriter = env.Loger.GetOutput()
gin.ForceConsoleColor()
// lg.Info(`文件日志初始化成功`)
} else {
lg.Error(`文件日志初始化失败:%v`, err)
}
}
}
// 初始化
func init() {
ztool.Cmd_FastPrint(ztool.Str_FastConcat(`
@ -82,18 +35,26 @@ func init() {
env.RunPath, _ = os.Getwd()
var confPath string
flag.StringVar(&confPath, `c`, ztool.Str_FastConcat(env.RunPath, `/data/conf.ini`), `指定配置文件路径`)
etag := flag.String(`e`, ``, `扩展启动参数`)
perm := flag.Uint(`p`, 0, `自定义文件权限(8进制前面加0)`)
flag.Parse()
// fileLoger() // 注:记录日志必然会影响性能,自行选择是否开启
// logs.DefLogger(`LX-SOURCE`, logs.LevelDebu)
// logs.Main = `LX-SOURCE`
env.Cfg.MustInit(confPath) //conf.InitConfig(confPath)
if perm != nil && *perm != 0 {
ztool.Fbj_DefPerm = fs.FileMode(*perm)
fp := env.Loger.NewGroup(`FilePerm`)
// if ztool.Fbj_DefPerm > 777 {
// fp.Fatal(`请在实际权限前面加0`)
// }
fp.Info(`设置默认文件权限为 %o (%v)`, *perm, ztool.Fbj_DefPerm).Free()
}
env.Cfg.MustInit(confPath)
parseEtag(etag)
// fmt.Printf("%+v\n", env.Config)
env.Loger.NewGroup(`ServHello`).Info(`欢迎使用 LX-SOURCE 洛雪音乐自定义源`)
env.Loger.NewGroup(`ServHello`).Info(`欢迎使用 LX-SOURCE 洛雪音乐自定义源`).Free()
if !env.Config.Main.Debug {
gin.SetMode(gin.ReleaseMode)
} else {
logs.Levell = logs.LevelDebu // logs.Level = 3
env.Loger.NewGroup(`DebugMode`).Debug(`已开启调试模式, 将输出更详细日志 (配置文件中 [Main].Debug 改为 false 关闭)`)
env.Loger.NewGroup(`DebugMode`).Debug(`已开启调试模式, 将输出更详细日志 (配置文件中 [Main].Debug 改为 false 关闭)`).Free()
}
genAuth()
if env.Config.Main.SysLev {
@ -103,102 +64,93 @@ func init() {
} else {
sl.Warn(`成功设置较高优先级,此功能可能导致系统不稳定`)
}
sl.Free()
}
if env.Config.Main.Timeout != env.DefCfg.Main.Timeout {
ztool.Net_client.Timeout = time.Second * time.Duration(env.Config.Main.Timeout) // 自定义请求超时
env.Loger.NewGroup(`InitNet`).Info(`请求超时已设为 %s`, ztool.Net_client.Timeout).Free()
}
}
func main() {
defer env.Defer.Do()
// 初始化缓存
icl := env.Loger.NewGroup(`InitCache`)
switch env.Config.Cache.Mode {
case `0`, `off`:
// NothingToDo... (已默认禁用缓存)
break
case `1`, `local`:
// 注由于需要修改LocalCachePath参数必须在InitRouter之前执行
cache, err := caches.New(&localcache.Cache{
Path: filepath.Join(env.RunPath, env.Config.Cache.Local_Path),
Bind: env.Config.Cache.Local_Bind,
})
if err != nil {
icl.Error(`驱动["local"]初始化失败: %v, 将禁用缓存功能`, err)
}
caches.UseCache = cache
icl.Warn(`本地缓存绑定地址: %q, 请确认其与实际访问地址相符`, env.Config.Cache.Local_Bind)
// LocalCachePath = filepath.Join(runPath, env.Config.Cache.Local_Path)
// UseCache = &localcache.Cache{
// Path: LocalCachePath,
// Addr: env.Config.Apis.BindAddr,
// }
// icl.Info(`使用本地缓存,文件路径 %q绑定地址 %v`, LocalCachePath, env.Config.Apis.BindAddr)
case `2`, `cloudreve`:
icl.Fatal(`Cloudreve驱动暂未完善未兼容新版调用方式当前版本禁用`)
// icl.Warn(`Cloudreve驱动暂未完善使用非本机存储时存在兼容性问题请谨慎使用`)
// cs, err := cloudreve.NewSite(&cloudreve.Config{
// SiteUrl: env.Config.Cache.Cloud_Site,
// Username: env.Config.Cache.Cloud_User,
// Password: env.Config.Cache.Cloud_Pass,
// Session: env.Config.Cache.Cloud_Sess,
// })
// if err != nil {
// icl.Error(`驱动["cloudreve"]初始化失败: %v, 将禁用缓存功能`, err)
// }
// UseCache = &crcache.Cache{
// Cs: cs,
// Path: env.Config.Cache.Cloud_Path,
// IsOk: err == nil,
// }
default:
icl.Error(`未定义的缓存模式,请检查配置 [Cache].Mode本次启动禁用缓存`)
}
// 初始化音乐源
ise := env.Loger.NewGroup(`InitSource`)
switch env.Config.Source.Mode {
case `0`, `off`:
break
case `1`, `builtin`:
sources.UseSource = &builtin.Source{}
case `2`, `custom`:
ise.Fatal(`暂未实现账号解析源`)
default:
ise.Error(`未定义的音乐源,请检查配置 [Source].Mode本次启动禁用内置源`)
}
// 初始化基础功能
initMain()
// 载入必要模块
env.Inits.Do()
env.Loger.NewGroup(`ServStart`).Info(`服务端启动, 监听地址 %s`, env.Config.Main.Listen)
env.Loger.NewGroup(`ServInit`).Info(`服务端启动, 监听地址 %s`, strings.Join(env.Config.Main.Listen, `|`)).Free()
loadFileLoger()
env.Defer.Add(env.Tasker.Run(env.Loger)) // wait
// 启动Http服务
r := router.InitRouter() //InitRouter()
server := &http.Server{
Addr: env.Config.Main.Listen,
Handler: r,
}
go func() {
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
env.Loger.NewGroup(`InitRouter().Run`).Fatal(`%s`, err)
}
}()
listenAndServe(server.InitRouter(), env.Config.Main.Listen)
}
// 监听多端口
func listenAndServe(handler http.Handler, addrs []string) {
// 前置检测
length := len(addrs)
ss := env.Loger.NewGroup(`ServStart`)
if length == 0 {
ss.Fatal(`监听地址列表为空`)
}
// ss.Info(`服务端启动,请稍候...`)
srvlist := make(map[int]*http.Server, length) // 伪数组,便于快速删除数据
lock := new(sync.Mutex)
var failnum int32
length32 := int32(length)
// 启动服务
for i := 0; i < length; i++ {
lock.Lock()
srvlist[i] = &http.Server{Addr: addrs[i], Handler: handler}
lock.Unlock()
go func(n int) {
server := srvlist[n]
// ss.Info(`开始监听 %v`, server.Addr)
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
ss.Error(`监听%q失败: %s`, server.Addr, err) // 监听":1011"失败: http: Server closed
fn := atomic.AddInt32(&failnum, 1)
if fn == length32 {
ss.Fatal(`所有地址监听失败,程序被迫退出`)
}
lock.Lock()
delete(srvlist, n)
lock.Unlock()
}
}(i)
}
// time.Sleep(time.Millisecond * 300)
// if len(srvlist) == 0 {
// ss.Fatal(`所有地址监听失败,程序被迫退出`)
// }
// ss.Free()
// 安全退出
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
<-quit
sc := env.Loger.NewGroup(`ServClose`)
sc.Info(`等待结束活动连接...`) // Shutdown Server ...
sc.Info(`等待结束活动连接...`)
// 停止服务
var unsafenum int32
wg := new(sync.WaitGroup)
for i := range srvlist {
wg.Add(1)
go func(n int) {
server := srvlist[n]
ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
sc.Fatal(`未安全退出: %s`, err) // Server Shutdown
sc.Warn(`连接%q未安全退出: %s`, server.Addr, err) // 连接":1011"未安全退出: timeout
atomic.AddInt32(&unsafenum, 1)
}
cancel()
wg.Done()
}(i)
}
wg.Wait()
if unsafenum != 0 {
sc.Warn(`未安全退出 :(`)
} else {
sc.Info(`已安全退出 :)`)
}
sc.Info(`已安全退出 :)`) // Server exited
// if err := InitRouter().Run(env.Config.Main.Listen); err != nil {
// env.Loger.NewGroup(`InitRouter().Run`).Fatal(`%s`, err)
// }
}

132
menu.go Normal file
View File

@ -0,0 +1,132 @@
package main
import (
"fmt"
"lx-source/src/env"
"lx-source/src/sources/custom/tx"
wm "lx-source/src/sources/custom/wy/modules"
"runtime"
"strings"
"time"
qrcode "github.com/skip2/go-qrcode"
)
func parseEtag(etag *string) {
if etag == nil {
return
}
loger := env.Loger.NewGroup(`ParseEtag`)
switch *etag {
case ``:
break
case `menu`:
loger.Fatal(`暂不支持交互菜单,敬请期待...`)
// menuMian()
case `wyqr`:
wyQrLogin()
case `txqq`:
txQqLogin()
default:
loger.Fatal(`未知参数:%q`, *etag)
}
loger.Free()
}
// 网易云扫码登录
func wyQrLogin() {
loger := env.Loger.NewGroup(`WyQrLogin`)
defer loger.Free()
loger.Info(`执行模块: 网易云扫码登录`)
if env.Config.Custom.Wy_Api_Cookie != `` {
loger.Warn("已存在账号数据, 继续操作可能导致数据覆盖丢失!")
fmt.Print(`输入'y'继续: `)
var input string
fmt.Scanln(&input)
if input != `y` {
loger.Fatal(`用户取消操作`)
}
}
res, err := wm.LoginQrKey()
if err != nil {
loger.Fatal(`无法创建请求: %s`, err)
}
key := res.Body[`unikey`].(string)
loger.Info(`创建请求成功: %v`, key)
link := wm.LoginQrCreate(key)
qr, err := qrcode.New(link, qrcode.Low)
if err != nil {
loger.Fatal(`无法生成二维码: %s`, err)
}
loger.Info("\n请使用网易云音乐手机APP扫描以下二维码授权登录:\n%v", qr.ToSmallString(false))
for {
time.Sleep(time.Second * 5)
res, err = wm.LoginQrCheck(key)
if err != nil {
loger.Error(`检测状态失败: %s`, err)
continue
}
msg := res.Body[`message`].(string)
switch msg {
case `等待扫码`:
loger.Info(msg)
case `授权中`:
loger.Info(`扫码成功: %q, 请在手机上确认登录`, res.Body[`nickname`])
case `授权登陆成功`:
loger.Info(`授权成功`)
env.Config.Custom.Wy_Enable = true
env.Config.Custom.Wy_Mode = `163api`
env.Config.Custom.Wy_Api_Cookie = strings.Join(res.Cookie, `; `)
env.Config.Custom.Wy_Refresh_Enable = true
if err := env.Cfg.Save(``); err != nil {
loger.Error(`配置保存失败: %s`, err)
} else {
loger.Info(`配置保存成功`)
}
return
case `二维码不存在或已过期`:
loger.Fatal(`授权请求超时,请重试!`)
default:
loger.Fatal(`未知状态: %v`, msg)
}
}
}
// QQ快速登录
func txQqLogin() {
loger := env.Loger.NewGroup(`TxQqLogin`)
defer loger.Free()
loger.Info(`执行模块: QQ快速登录`)
if runtime.GOOS != `windows` {
loger.Fatal(`该模块仅支持在windows环境下使用`)
return
}
if env.Config.Custom.Tx_Ukey != `` {
loger.Warn("已存在账号数据, 继续操作可能导致数据覆盖丢失!")
fmt.Print(`输入'y'继续: `)
var input string
fmt.Scanln(&input)
if input != `y` {
loger.Fatal(`用户取消操作`)
}
}
if err := tx.Qlogin_graph(loger); err != nil {
loger.Fatal(err.Error())
}
}
// func menuMian() {
// app := menu.NewApp(`Lx-Source`)
// app.Data = menu.Data{
// `Main`: func(this *menu.App) string { return ` ` },
// }
// app.Run()
// os.Exit(0)
// }

View File

@ -11,11 +11,15 @@
## ZxwyWebSite/LX-Source
### 简介
+ LX-Music 解析源 (洛雪音乐自定义源)
+ **由于本项目的特殊性,请低调使用,切勿宣传**
+ **由于本项目的特殊性,请低调使用,切勿大肆宣传**<!--~~传的越多,寄的越快~~-->
+ 测试阶段,不代表最终品质
+ 验证部分暂未完善,建议仅本地部署,不要公开发布
+ 视频教程:[使用教程.mp4](https://r2eu.zxwy.link/gh/lx-source/v1.0.2-b0.1/%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B.mp4)
<!-- + **锟斤拷** -->
<!-- + *手持两把锟斤拷,口中疾呼烫烫烫。 脚踏千朵屯屯屯,笑看万物锘锘锘。* -->
### 注意
+ 不保证内置源可用性,如需长期使用请自备会员账号
+ 音源最好留着自己用,你非要当大好人当我没说(由于传播导致的账号被封与本项目无关)
### 使用
#### 服务端
@ -48,8 +52,8 @@
<!-- + ... -->
### 音乐源
+ 内置源 (抓取自网络公开接口) **注:文明上网,请勿滥用**
+ 账号源 (登录Vip账号解析) **注:可能导致封号,如出问题本项目不负责**
+ 内置源 (抓取自网络公开接口) **注:文明上网,请勿滥用,否则停止后续更新**
+ 账号源 (登录会员账号解析) **注:可能导致封号,如出问题本项目不负责**
### 开发
+ 环境要求Golang 1.21 (建议 >=1.20)
@ -61,18 +65,21 @@
- pkg/ 依赖包,一般在外部调用,不轻易修改
- src/ 源码包,用于实现各种功能
* env 公用变量,需要全局调用的参数
* database 数据库相关
* caches 文件缓存封装
* router Gin路由
* server Gin路由
* middleware 请求中间件
* sources 音乐源
<!-- - public/ 静态资源,打包进程序,新环境运行自动释放 -->
<!-- - scripts/ 一些快捷脚本 -->
- build.go 快速构建脚本 (请先根据本地环境编辑配置)
- init.go 初始化检测
- menu.go 交互菜单 (暂未实现)
- main.go 主程序
### 其它
+ 基于 Golang + Gin框架 编写
+ 部分功能参考 [Python版](https://github.com/lxmusics/lx-music-api-server-python) 实现
+ 感谢以下项目提供参考:[Python版](https://github.com/lxmusics/lx-music-api-server-python)[WyApi](https://github.com/ZxwyWebSite/NeteaseCloudMusicApi)...
### 更新
+ 见 `update.md`
@ -92,5 +99,5 @@
音乐平台不易,请尊重版权,支持正版。
本项目仅用于对技术可行性的探索及研究,不接受任何商业(包括但不限于广告等)合作及捐赠。
若对此有疑问请 mail to: admin+zxwy.tk (请将`+`替换为`@`)
若对此有疑问请 mail to: admin+zxwy.link (请将`+`替换为`@`)

107
release.go Normal file
View File

@ -0,0 +1,107 @@
//go:build ignore
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
// 获取版本号
func version() string {
fenv, _ := os.Open(`src/env/env.go`)
benv := bufio.NewReader(fenv)
var ever string
for {
line, _, _ := benv.ReadLine()
length := len(line)
if length == 0 {
continue
}
sline := string(line)
if strings.HasPrefix(sline, ` Version`) {
ever = `v` + sline[12:length-1]
break
}
}
fenv.Close()
if ever == `` {
panic(`No Version`)
} else {
return ever
}
}
// 生成更新日志
func changelog(ever string) string {
fupd, _ := os.Open(`update.md`)
bupd := bufio.NewReader(fupd)
var eupd strings.Builder
eupd.WriteString(`### 更新内容:`)
eupd.WriteByte('\n')
for {
line, _, _ := bupd.ReadLine()
length := len(line)
if length == 0 {
continue
}
if strings.Contains(string(line), ever) {
for {
lline, _, _ := bupd.ReadLine()
length := len(lline)
if length == 0 {
break
}
eupd.WriteString(string(lline))
eupd.WriteByte('\n')
}
break
}
}
fupd.Close()
eupd.WriteByte('\n')
eupd.WriteString(`### CDN加速下载`)
eupd.WriteByte('\n')
for _, v := range []string{
`lx-source-android-arm.zip`,
`lx-source-android-arm64.zip`,
`lx-source-linux-amd64v2.zip`,
`lx-source-linux-amd64v3.zip`,
`lx-source-linux-arm7.zip`,
`lx-source-linux-arm64.zip`,
`lx-source-windows-amd64v2.zip`,
`lx-source-windows-amd64v2-go1.20.14.zip`,
`lx-source-windows-amd64v3.zip`,
} {
eupd.WriteByte('+')
eupd.WriteByte(' ')
eupd.WriteByte('[')
eupd.WriteString(v)
eupd.WriteByte(']')
eupd.WriteByte('(')
eupd.WriteString(`https://r2eu.zxwy.link/gh/lx-source/`)
eupd.WriteString(ever)
eupd.WriteByte('/')
eupd.WriteString(v)
eupd.WriteByte(')')
eupd.WriteByte('\n')
}
return eupd.String()
}
func main() {
ever := version()
fmt.Println(ever)
eupd := changelog(ever)
file, err := os.Create(`changelog.md`)
if err != nil {
panic(err)
}
file.WriteString(eupd)
file.Close()
}

BIN
rsrc_windows_amd64.syso Normal file

Binary file not shown.

View File

@ -1,10 +1,13 @@
package caches
import (
"lx-source/src/env"
"net/http"
"strings"
"sync"
"github.com/ZxwyWebSite/ztool"
"github.com/ZxwyWebSite/ztool/logs"
)
type (
@ -15,6 +18,7 @@ type (
Quality string // quality 音质 128k / 320k / flac / flac24bit
Extname string // rext 扩展名 mp3 / flac (没有前缀点)
query string // 查询字符串缓存
Request *http.Request
}
// 缓存需实现以下接口
Cache interface {
@ -40,7 +44,6 @@ func (*Nullcache) Stat() bool { return false }
func (*Nullcache) Init() error { return nil }
var (
// Loger = env.Loger.NewGroup(`Caches`)
UseCache Cache = &Nullcache{}
// ErrNotInited = errors.New(`缓存策略未初始化`)
query_pool = sync.Pool{New: func() any { return new(Query) }}
@ -52,7 +55,7 @@ func (c *Query) Free() { c.query = ``; query_pool.Put(c) }
// 根据音质判断文件后缀
func rext(q string) string {
if ztool.Chk_IsMatch(q, `128k`, `320k`) /*q == `128k` || q == `320k`*/ {
if /*ztool.Chk_IsMatch(q, `128k`, `320k`)*/ q == `128k` || q == `320k` {
return `mp3`
}
return `flac`
@ -82,7 +85,7 @@ func (c *Query) Query() string {
return c.query
}
// 分割查询字符串
// 分割查询字符串 (已弃用)
/*
kg: 分割 Hash-Album "6DC276334F56E22BE2A0E8254D332B45-13097991"
tx: 分割 songmid-strMediaMid "002fktJg3cmSpC-000V6uuv35Cwnh"
@ -111,3 +114,12 @@ func MustNew(c Cache) Cache {
}
return out
}
var Loger *logs.Logger
// 初始化Loger
func init() {
env.Inits.Add(func() {
Loger = env.Loger.NewGroup(`Caches`)
})
}

View File

@ -1 +1,121 @@
package cloudcache
import (
"lx-source/src/caches"
"lx-source/src/env"
"net/http"
"strings"
cr "github.com/ZxwyWebSite/cr-go-sdk"
"github.com/ZxwyWebSite/cr-go-sdk/service/explorer"
"github.com/ZxwyWebSite/ztool"
)
type Cache struct {
Site *cr.SiteObj
Path string
state bool
}
func (c *Cache) Get(q *caches.Query) string {
var b strings.Builder
b.WriteString(c.Path)
b.WriteByte('/')
b.WriteString(q.Source)
b.WriteByte('/')
b.WriteString(q.MusicID)
list, err := c.Site.Directory(b.String())
if err != nil {
caches.Loger.Debug(`列出目录: %v`, err)
return ``
}
name := q.Quality + `.` + q.Extname
var id string
for _, v := range list.Objects {
if v.Name == name && v.Type == `file` {
id = v.ID
break
}
}
if id == `` {
caches.Loger.Debug(`文件不存在`)
return ``
}
srcs, err := c.Site.FileSource(cr.GenerateSrc(false, id))
if err != nil {
caches.Loger.Debug(`生成外链: %v`, err)
return ``
}
return (*srcs)[0].URL
/*link, err := c.Site.FileDownload(id)
if err != nil {
caches.Loger.Debug(`下载文件: %v`, err)
return ``
}
if (*link)[0] == '/' {
return c.Site.Addr + (*link)[1:]
}
return *link*/
}
func (c *Cache) Set(q *caches.Query, l string) string {
var b strings.Builder
b.WriteString(c.Path)
b.WriteByte('/')
b.WriteString(q.Source)
b.WriteByte('/')
b.WriteString(q.MusicID)
dir := b.String()
err := c.Site.DirectoryNew(&explorer.DirectoryService{
Path: dir,
})
if err != nil {
caches.Loger.Debug(`创建目录: %v`, err)
return ``
}
/*var buf bytes.Buffer
err = ztool.Net_Download(l, &buf, nil)
if err != nil {
caches.Loger.Debug(`下载文件: %v`, err)
return ``
}*/
name := q.Quality + `.` + q.Extname
err = ztool.Net_Request(
http.MethodGet, l, nil,
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders()},
[]ztool.Net_ResHandlerFunc{func(res *http.Response) error {
return (&cr.UploadTask{
Site: c.Site,
File: res.Body,
Size: uint64(res.ContentLength),
Name: name,
Mime: `audio/mpeg`,
}).Do(dir)
}},
)
if err != nil {
caches.Loger.Debug(`上传文件: %v`, err)
return ``
}
return c.Get(q)
}
func (c *Cache) Stat() bool {
return c.state
}
func (c *Cache) Init() error {
cr.Cr_Debug = env.Config.Main.Debug
err := c.Site.SdkInit()
if err != nil {
return err
}
if c.Site.Users.Cookie == nil || c.Site.Config.User.Anonymous {
err = c.Site.SdkLogin()
if err != nil {
return err
}
}
c.state = true
return nil
}

View File

@ -4,9 +4,11 @@ import (
"errors"
"lx-source/src/caches"
"lx-source/src/env"
"lx-source/src/middleware/util"
"net/url"
"os"
"strings"
"time"
"github.com/ZxwyWebSite/ztool"
)
@ -17,10 +19,17 @@ type Cache struct {
state bool // 激活状态
}
var loger = env.Loger.NewGroup(`Caches`) //caches.Loger.AppGroup(`local`)
// var loger = env.Loger.NewGroup(`Caches`) //caches.Loger.AppGroup(`local`)
func (c *Cache) getLink(q string) string {
return ztool.Str_FastConcat(c.Bind, `/file/`, q) // c.Addr + `file/` + q
func (c *Cache) getLink(q *caches.Query) (furl string) {
// fmt.Printf("%#v\n", q.Request)
if env.Config.Cache.Local_Auto {
// 注此方式无法确定是否支持HTTPS暂时默认HTTP让Nginx重定向
furl = ztool.Str_FastConcat(util.GetPath(q.Request, `link/`), `file/`, q.Query())
} else {
furl = ztool.Str_FastConcat(c.Bind, `/file/`, q.Query())
}
return
}
func (c *Cache) Get(q *caches.Query) string {
@ -31,7 +40,7 @@ func (c *Cache) Get(q *caches.Query) string {
}
// env.Cache.Set(q.Query(), struct{}{}, 3600)
// }
return c.getLink(q.Query())
return c.getLink(q)
// fpath := filepath.Join(c.Path, q.Source, q.MusicID, q.Quality)
// if _, e := os.Stat(fpath); e != nil {
// return ``
@ -54,14 +63,23 @@ func (c *Cache) Set(q *caches.Query, l string) string {
// }
// loger.Debug(`FFMpeg_Out: %v`, out)
// } else {
for i := 0; true; i++ {
err := ztool.Net_DownloadFile(l, fpath, nil)
if err != nil {
loger.Error(`DownloadFile: %v`, err)
if err == nil {
break
}
caches.Loger.Error(`DownloadFile: %v, Retry: %v`, err, i)
if i == 1 || !strings.Contains(err.Error(), `context deadline exceeded`) {
if err := os.Remove(fpath); err != nil {
caches.Loger.Error(`RemoveFile: %s`, err)
}
return ``
}
time.Sleep(time.Second)
}
// }
// env.Cache.Set(q.Query(), struct{}{}, 3600)
return c.getLink(q.Query())
return c.getLink(q)
// fpath := filepath.Join(c.Path, q.String)
// os.MkdirAll(filepath.Dir(fpath), fs.ModePerm)
// g := c.Loger.NewGroup(`localcache`)

38
src/database/driver.go Normal file
View File

@ -0,0 +1,38 @@
//go:build gorm
package database
// import (
// "lx-source/src/database/modules"
// "lx-source/src/env"
// "lx-source/src/sources"
// "gorm.io/gorm"
// )
// var DB *gorm.DB
// func InitDB(dsn string) (err error) {
// loger := env.Loger.NewGroup(`InitDB`)
// defer loger.Free()
// DB, err = gorm.Open(modules.Sqlite(dsn), &gorm.Config{})
// if err == nil {
// for _, typ := range []struct {
// Name string
// Type interface{}
// }{
// {Name: T_music, Type: &XMusicItem{}},
// {Name: T_lyric, Type: &XLyricItem{}},
// } {
// for _, src := range sources.S_al {
// err = DB.Table(src + `_` + typ.Name).AutoMigrate(typ.Type)
// if err != nil {
// return
// }
// }
// }
// }
// return
// }
// type Driver struct{}

View File

@ -0,0 +1,9 @@
//go:build cgo && gorm
package modules
// import (
// "gorm.io/driver/sqlite"
// )
// var Sqlite = sqlite.Open

View File

@ -0,0 +1,9 @@
//go:build !cgo && gorm
package modules
// import (
// "github.com/glebarez/sqlite"
// )
// var Sqlite = sqlite.Open

118
src/database/types.go Normal file
View File

@ -0,0 +1,118 @@
//go:build gorm
package database
// MusicFree 数据结构
// type (
// // 其他
// IExtra map[string]interface{}
// // 音乐
// IMusicItem struct {
// Artist string `json:"artist"` // 作者
// Title string `json:"title"` // 歌曲标题
// Duration int `json:"duration,omitempty"` // 时长(s)
// Album string `json:"album,omitempty"` // 专辑名
// Artwork string `json:"artwork,omitempty"` // 专辑封面图
// Url string `json:"url,omitempty"` // 默认音源
// Lrc string `json:"lrc,omitempty"` // 歌词URL
// RawLrc string `json:"rawLrc,omitempty"` // 歌词文本lrc格式 带时间戳)
// Other IExtra `json:"extra,omitempty"` // 其他
// }
// // 歌单
// IMusicSheetItem struct {
// Artwork string `json:"artwork,omitempty"` // 封面图
// Title string `json:"title"` // 标题
// Description string `json:"description,omitempty"` // 描述
// WorksNum int `json:"worksNum,omitempty"` // 作品总数
// PlayCount int `json:"playCount,omitempty"` // 播放次数
// MusicList []IMusicItem `json:"musicList,omitempty"` // 播放列表
// CreateAt int64 `json:"createAt,omitempty"` // 歌单创建日期
// Artist string `json:"artist,omitempty"` // 歌单作者
// Other IExtra `json:"extra,omitempty"` // 其他
// }
// // 专辑
// IAlbumItem IMusicSheetItem
// // 作者
// IArtistItem struct {
// Platform string `json:"platform,omitempty"` // 插件名
// ID interface{} `json:"id"` // 唯一id
// Name string `json:"name"` // 姓名
// Fans int `json:"fans,omitempty"` // 粉丝数
// Description string `json:"description,omitempty"` // 简介
// Avatar string `json:"avatar,omitempty"` // 头像
// WorksNum int `json:"worksNum,omitempty"` // 作品数目
// MusicList []IMusicItem `json:"musicList,omitempty"` // 作者的音乐列表
// AlbumList []IAlbumItem `json:"albumList,omitempty"` // 作者的专辑列表
// Other IExtra `json:"extra,omitempty"` // 其他
// }
// )
// 结构表
// type (
// // 重复
// XPublicKeys struct {
// ID string `json:"id" gorm:"primaryKey"` // 唯一ID
// Exp int64 `json:"exp" gorm:"column:exp"` // 过期时间
// }
// // 音乐
// XMusicItem struct {
// ID string `json:"id" gorm:"primaryKey"` // 唯一ID
// Name string `json:"name" gorm:"column:name"` // 歌曲名称
// }
// // 作者
// // XArtistItem struct {
// // ID string `json:"id" gorm:"primaryKey"`
// // Name string `json:"name" gorm:"column:name"`
// // }
// // 歌词
// XLyricItem struct {
// ID string `json:"id" gorm:"primaryKey"`
// Lyric string `json:"lyric" gorm:"column:lyric"` // 歌曲歌词
// TLyric string `json:"tlyric" gorm:"column:tlyric"` // 翻译歌词,没有可为 null
// RLyric string `json:"rlyric" gorm:"column:rlyric"` // 罗马音歌词,没有可为 null
// LxLyric string `json:"lxlyric" gorm:"column:lxlyric"` // lx 逐字歌词,没有可为 null
// // 歌词格式为 [分钟:秒.毫秒]<开始时间(基于该句),持续时间>歌词文字
// // 例如: [00:00.000]<0,36>测<36,36>试<50,60>歌<80,75>词
// }
// // 视频
// // XMovieItem struct {
// // ID string `json:"id" gorm:"primaryKey"`
// // Name string `json:"name" gorm:"column:name"`
// // }
// // 链接
// XLinkItem struct {
// ID string `json:"id" gorm:"primaryKey"`
// }
// )
// const (
// T_artist = `artist`
// T_detail = `detail`
// T_lyric = `lyric`
// T_music = `music`
// )
// 分源表
// type (
// // Music
// WyMusic MusicItem
// MgMusic MusicItem
// KwMusic MusicItem
// KgMusic MusicItem
// TxMusic MusicItem
// LxMusic MusicItem
// // Artist
// WyArtist ArtistItem
// MgArtist ArtistItem
// KwArtist ArtistItem
// KgArtist ArtistItem
// TxArtist ArtistItem
// LxArtist ArtistItem
// // Lyric
// WyLyric LyricItem
// MgLyric LyricItem
// KwLyric LyricItem
// KgLyric LyricItem
// TxLyric LyricItem
// LxLyric LyricItem
// )

165
src/env/env.go vendored
View File

@ -12,7 +12,7 @@ import (
)
const (
Version = `1.0.2-b12`
Version = `1.0.3.0622`
)
var (
@ -26,20 +26,34 @@ var (
序号 名称 描述
*/
type (
// 系统
Conf_Main struct {
Debug bool `comment:"调试模式"`
Listen string `comment:"监听地址"`
Listen []string `comment:"监听地址 (多端口以','分隔)"`
Gzip bool `comment:"开启GZip (对已压缩的内容使用会产生反效果)"`
Cors bool `comment:"添加跨域响应头 (兼容前端请求)"`
LogPath string `comment:"文件日志路径,不填禁用"`
Print bool `comment:"控制台输出"`
Print bool `comment:"控制台输出 (影响io性能后台使用建议关闭)"`
SysLev bool `comment:"(实验性) 设置进程高优先级"`
// FFConv bool `comment:"(实验性) 使用FFMpeg修复音频(本地缓存)"`
NgProxy bool `comment:"兼容反向代理(beta)"`
Timeout int64 `comment:"网络请求超时(单位:秒,海外服务器可适当调大)"`
Store string `comment:"内存缓存持久化文件地址"`
ErrMp3 string `comment:"获取失败默认音频"`
}
// 接口
Conf_Apis struct {
// 预留后期可能会出一个WebUI/webui相关设置
// BindAddr string `comment:"外部访问地址,用于生成文件链接"`
// LxM_Auth string `comment:"验证Key自动生成填写null禁用"`
// WebUI
// WebUI_Enable bool `comment:"是否开启WebUI相关接口"`
// App
// App_Enable bool `comment:"是否开启软件接口"`
// App Lx-Music
// App_LX_Enable bool `comment:"是否开启Lx-Music相关接口"`
// App MusicFree
// App_MF_Enable bool `comment:"是否开启MusicFree相关接口"`
}
// 验证
Conf_Auth struct {
// ApiKey
ApiKey_Enable bool `comment:"是否开启Key验证"`
@ -47,17 +61,18 @@ type (
// 速率限制
RateLimit_Enable bool `comment:"是否开启速率限制"`
RateLimit_Block uint32 `comment:"检测范围每分区为x秒"` // 每x秒一个分区
RateLimit_Global uint32 `comment:"全局速率限制单位次每x秒(暂未开放)"`
// RateLimit_Global uint32 `comment:"全局速率限制单位次每x秒(暂未开放)"`
RateLimit_Single uint32 `comment:"单IP速率限制单位次每x秒"`
RateLimit_BanNum uint32 `comment:"容忍限度超出限制N次后封禁"`
RateLimit_BanTim uint32 `comment:"封禁后每次延长时间"`
// 黑白名单
BanList_Mode string `comment:"名单模式 0: off(关闭), 1: white(白名单), 2: black(黑名单)"`
BanList_White []string `comment:"host白名单"`
BanList_Black []string `comment:"host黑名单"`
// BanList_Mode string `comment:"名单模式 0: off(关闭), 1: white(白名单), 2: black(黑名单)"`
// BanList_White []string `comment:"host白名单"`
// BanList_Black []string `comment:"host黑名单"`
}
// 来源
Conf_Source struct {
Mode string `comment:"音乐来源 0: off(关闭 仅本地), 1: builtin(内置), 2: custom(登录账号 暂不支持)"`
// Mode string `comment:"音乐来源 0: off(关闭 仅本地), 1: builtin(内置), 2: custom(登录账号 暂不支持)"`
// 伪装IP
FakeIP_Mode string `comment:"伪装IP模式 0: off(关闭), 1: req(传入值), 2: val(静态)"`
FakeIP_Value string `comment:"静态伪装IP"`
@ -66,18 +81,42 @@ type (
Proxy_Address string `comment:"代理地址 (支持http, socks)"`
// 验证
MusicIdVerify bool `comment:"(beta) 验证音乐ID可用性"`
ForceFallback bool `comment:"忽略音质限制,强制获取试听音频"`
ForceFallback bool `comment:"忽略音质限制,强制返回链接(部分支持)"`
// 总开关(解决部分源无法彻底禁用问题)?
Enable_Wy bool `comment:"是否开启小芸源"`
Enable_Mg bool `comment:"是否开启小蜜源"`
Enable_Kw bool `comment:"是否开启小蜗源"`
Enable_Kg bool `comment:"是否开启小枸源"`
Enable_Tx bool `comment:"是否开启小秋源"`
Enable_Lx bool `comment:"是否开启小洛源"`
} // `comment:""`
// 账号
Conf_Custom struct {
// wy (暂未实现)
Wy_Enable bool `comment:"是否开启小芸源"`
Wy_Cookie string `comment:"账号cookie数据"`
// wy
Wy_Enable bool `comment:"是否启用小芸源"`
Wy_Mode string `comment:"获取方式 0: builtin, 1: 163api"`
// wy 163api
Wy_Api_Type string `comment:"调用方式 0: native(内置模块), 1: remote(指定地址)"`
Wy_Api_Address string `comment:"NeteaseCloudMusicApi项目地址"`
Wy_Api_Cookie string `comment:"账号cookie数据"`
// wy refresh
Wy_Refresh_Enable bool `comment:"是否启用刷新登录"`
Wy_Refresh_Interval int64 `comment:"下次刷新时间 (由程序维护)"`
// mg (暂未实现)
// Mg_Enable bool `comment:"是否开启小蜜源"`
// mg
Mg_Enable bool `comment:"是否启用小蜜源"`
Mg_Mode string `comment:"获取方式 0: builtin, 1: custom"`
// mg custom
Mg_Usr_VerId string `comment:"field user.aversionid"`
Mg_Usr_Token string `comment:"field user.token"`
Mg_Usr_OSVer string `comment:"field user.osversion"`
Mg_Usr_ReqUA string `comment:"field user.useragent"`
// mg refresh
Mg_Refresh_Enable bool `comment:"是否启用Cookie保活"`
Mg_Refresh_Interval int64 `comment:"下次运行时间 (自动更新)"`
// kw
Kw_Enable bool `comment:"是否开启小蜗源"`
Kw_Enable bool `comment:"是否小蜗源"`
Kw_Mode string `comment:"接口模式 0: bdapi(需验证), 1: kwdes"`
// kw bdapi
Kw_Bd_Uid string `comment:"field user.uid"`
@ -85,31 +124,66 @@ type (
Kw_Bd_DevId string `comment:"field user.device_id"`
// kw kwdes
Kw_Des_Type string `comment:"返回格式 0: text, 1: json"`
Kw_Des_Source string `comment:"query source"`
Kw_Des_Header string `comment:"请求头 User-Agent"`
// kg (暂未实现)
// Kg_Enable bool `comment:"是否开启小枸源"`
// kg
Kg_Enable bool `comment:"是否启用小枸源"`
// kg client
Kg_Client_AppId string `comment:"酷狗音乐的appid官方安卓为1005官方PC为1001client.appid"`
Kg_Client_SignKey string `comment:"客户端signature采用的key值需要与appid对应client.signatureKey"`
Kg_Client_Version string `comment:"客户端versioncodepidversionsecret可能随此值而变化client.clientver"`
Kg_Client_PidVerSec string `comment:"获取URL时所用的key值计算验证值client.pidversionsecret"`
Kg_Client_Pid string `comment:"field client.pid"`
// kg user
Kg_token string `comment:"field user.token"`
Kg_userId string `comment:"field user.userid"`
// kg lite_sign_in
Kg_Lite_Enable bool `comment:"是否启用概念版自动签到仅在appid=3116时运行"`
Kg_Lite_MixId string `comment:"mix_songmid的获取方式, 默认auto, 可以改成一个数字手动"`
Kg_Lite_Interval int64 `comment:"调用时间,自动刷新"`
// kg refresh_login
Kg_Refresh_Enable bool `comment:"是否启动刷新登录"`
Kg_Refresh_Interval int64 `comment:""`
// tx
Tx_Enable bool `comment:"是否开启小秋源"`
Tx_Enable bool `comment:"是否小秋源"`
Tx_Ukey string `comment:"Cookie中/客户端的请求体中的comm.authst"`
Tx_Uuin string `comment:"key对应的QQ号"`
Tx_CDNUrl string `comment:"指定音频CDN地址"`
// tx refresh_login
Tx_Refresh_Enable bool `comment:"是否启动刷新登录"`
Tx_Refresh_Interval int64 `comment:"刷新间隔 (由程序维护,非必要无需修改)"`
// lx (local)
// Lx_Enable bool `comment:"是否启用小洛源"`
}
Conf_Script struct {
// 脚本
Conf_Script_Update struct {
Ver string `comment:"自定义脚本版本" json:"ver"`
Log string `comment:"更新日志" json:"log"`
Url string `comment:"脚本下载地址 (public目录内文件名)" json:"url"`
Force bool `comment:"强制推送更新" json:"force"`
}
Conf_Script struct {
Name string `comment:"源的名字建议不要过长24个字符以内"`
Descript string `comment:"源的描述建议不要过长36个字符以内可不填"`
Version string `comment:"源的版本号,可不填"`
Author string `comment:"脚本作者名字,可不填"`
Homepage string `comment:"脚本主页,可不填"`
Update Conf_Script_Update `ini:"Script"`
Auto int `comment:"自动填写配置(beta) 0: 关闭, 1: 仅api地址, 2: 包含密钥"`
}
// 缓存
Conf_Cache struct {
Mode string `comment:"缓存模式 0: off(关闭), 1: local(本地), 2: cloudreve(云盘 未完善)"`
LinkMode string `comment:"外链样式 1: static(永久链), 2: dynamic(临时链)"`
// 本地
Local_Path string `comment:"本地缓存保存路径"`
Local_Bind string `comment:"本地缓存外部访问地址"`
Local_Auto bool `comment:"自适应缓存访问地址(beta)"`
// 云盘
Cloud_Site string `comment:"Cloudreve站点地址"`
Cloud_User string `comment:"Cloudreve用户名"`
@ -117,6 +191,7 @@ type (
Cloud_Sess string `comment:"Cloudreve会话"`
Cloud_Path string `comment:"Cloudreve存储路径"`
}
// 结构
Conf struct {
Main Conf_Main `comment:"程序主配置"`
Apis Conf_Apis `comment:"接口设置"`
@ -133,11 +208,14 @@ var (
DefCfg = Conf{
Main: Conf_Main{
Debug: false,
Listen: `127.0.0.1:1011`,
Listen: []string{`127.0.0.1:1011`},
Gzip: false,
LogPath: `/data/logfile.log`,
Print: true,
SysLev: false,
Timeout: 30,
Store: `/data/memo.bin`,
ErrMp3: `https://r2eu.zxwy.link/gh/lx-source/static/error.mp3`,
},
Apis: Conf_Apis{
// BindAddr: `http://192.168.10.22:1011/`,
@ -146,39 +224,72 @@ var (
ApiKey_Enable: true,
RateLimit_Enable: false,
RateLimit_Block: 30,
RateLimit_Global: 1,
// RateLimit_Global: 1,
RateLimit_Single: 15,
RateLimit_BanNum: 5,
RateLimit_BanTim: 10,
BanList_Mode: `off`,
BanList_White: []string{`127.0.0.1`},
// BanList_Mode: `off`,
// BanList_White: []string{`127.0.0.1`},
},
Source: Conf_Source{
Mode: `builtin`,
// Mode: `builtin`,
FakeIP_Mode: `0`,
FakeIP_Value: `192.168.10.2`,
Proxy_Enable: false,
Proxy_Address: `{protocol}://({user}:{password})@{address}:{port}`,
Enable_Wy: true,
Enable_Mg: true,
Enable_Kw: true,
Enable_Kg: true,
Enable_Tx: true,
Enable_Lx: false,
},
Custom: Conf_Custom{
Wy_Enable: true,
Wy_Mode: `builtin`,
Wy_Api_Type: `native`,
Wy_Refresh_Interval: 1633622400,
Mg_Enable: true,
Mg_Mode: `builtin`,
Mg_Usr_OSVer: `10`,
Mg_Usr_ReqUA: `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36`,
Kw_Enable: true,
Kw_Mode: `kwdes`,
Kw_Des_Type: `json`,
Kw_Des_Header: `okhttp/3.10.0`,
Kg_Enable: false,
Kg_Client_AppId: `1005`,
Kg_Client_SignKey: `OIlwieks28dk2k092lksi2UIkp`,
Kg_Client_Version: `12029`,
Kg_Client_PidVerSec: `57ae12eb6890223e355ccfcb74edf70d`,
Kg_Client_Pid: `2`,
Kg_userId: `0`,
Kg_Lite_MixId: `auto`,
Tx_Enable: false,
Tx_CDNUrl: `https://isure6.stream.qqmusic.qq.com/`,
Tx_Refresh_Enable: false,
Tx_Refresh_Interval: 86000,
},
Script: Conf_Script{
Name: `Lx-Source-Script`,
Descript: `洛雪音乐自定义源脚本`,
Version: `1.1.0`,
Author: `Zxwy`,
Homepage: `https://github.com/ZxwyWebSite/lx-script`,
Update: Conf_Script_Update{
Log: `发布更新 (请删除旧源后重新导入)进行了部分优化修复了部分Bug`, // 更新日志
Ver: `1.0.3`, // 自定义脚本版本
Force: true, // 强制推送更新
Url: `public/lx-custom-source.js`, // 脚本下载地址
Url: `lx-custom-source.js`, // 脚本下载地址
},
},
Cache: Conf_Cache{
Mode: `local`, // 缓存模式

View File

@ -2,6 +2,7 @@
package auth
import (
"encoding/gob"
"lx-source/src/env"
"lx-source/src/middleware/resp"
"sync/atomic"
@ -17,21 +18,12 @@ type (
}
)
func init() {
gob.Register(RateLimit{})
}
func InitHandler(h gin.HandlerFunc) (out []gin.HandlerFunc) {
loger := env.Loger.NewGroup(`AuthHandler`)
// ApiKey 请求头验证
if env.Config.Auth.ApiKey_Enable {
loger.Debug(`ApiKeyAuth Enabled`)
out = append(out, func(c *gin.Context) {
resp.Wrap(c, func() *resp.Resp {
if auth := c.Request.Header.Get(`X-LxM-Auth`); auth != env.Config.Auth.ApiKey_Value {
loger.Debug(`验证失败: %q`, auth)
return &resp.Resp{Code: 3, Msg: `验证Key失败, 请联系网站管理员`}
}
return nil
})
})
}
// RateLimit 速率限制
/*
逻辑
@ -51,9 +43,16 @@ func InitHandler(h gin.HandlerFunc) (out []gin.HandlerFunc) {
block_mem := int(env.Config.Auth.RateLimit_Block * env.Config.Auth.RateLimit_BanNum)
bannum := env.Config.Auth.RateLimit_Single + env.Config.Auth.RateLimit_BanNum
bantim := int64(env.Config.Auth.RateLimit_BanTim)
var getIp func(c *gin.Context) string
if env.Config.Main.NgProxy {
loger.Info(`已开启反向代理兼容模式`)
getIp = func(c *gin.Context) string { return c.ClientIP() }
} else {
getIp = func(c *gin.Context) string { return c.RemoteIP() }
}
out = append(out, func(c *gin.Context) {
resp.Wrap(c, func() *resp.Resp {
rip := c.RemoteIP()
rip := getIp(c)
if rip == `` {
rip = `0.0.0.0`
}
@ -84,21 +83,24 @@ func InitHandler(h gin.HandlerFunc) (out []gin.HandlerFunc) {
})
})
}
// ApiKey 请求头验证
if env.Config.Auth.ApiKey_Enable {
loger.Debug(`ApiKeyAuth Enabled`)
out = append(out, func(c *gin.Context) {
resp.Wrap(c, func() *resp.Resp {
var auth string
if key, ok := c.GetQuery(`key`); ok {
auth = key
} else {
auth = c.Request.Header.Get(`X-LxM-Auth`)
}
if auth != env.Config.Auth.ApiKey_Value {
loger.Debug(`验证失败: %q`, auth)
return &resp.Resp{Code: 3, Msg: `验证Key失败, 请联系网站管理员`}
}
return nil
})
})
}
return append(out, h)
}
// 请求验证
// func AuthHandler(c *gin.Context) {
// loger := env.Loger.NewGroup(`AuthHandler`)
// resp.Wrap(c, func() *resp.Resp {
// // ApiKey
// if env.Config.Auth.ApiKey_Enable {
// if auth := c.Request.Header.Get(`X-LxM-Auth`); auth != env.Config.Auth.ApiKey_Value {
// loger.Debug(`验证失败: %q`, auth)
// return &resp.Resp{Code: 3, Msg: `验证Key失败, 请联系网站管理员`}
// }
// }
// return nil
// })
// // c.Next()
// }

View File

@ -0,0 +1 @@
package auth

View File

@ -1,75 +0,0 @@
// 静态资源
package loadpublic
import (
"embed"
"fmt"
"io"
"io/fs"
"lx-source/src/env"
"net/http"
"path/filepath"
"github.com/ZxwyWebSite/ztool"
"github.com/gin-gonic/gin"
)
//go:embed public
var publicEM embed.FS // 打包默认Public目录 src/router/router.go
// 载入Public目录并设置路由
func LoadPublic(r *gin.Engine) {
pf := env.Loger.NewGroup(`PublicFS`)
dir := ztool.Str_FastConcat(env.RunPath, `/data/public`)
publicFS, err := fs.Sub(publicEM, `public`)
var httpFS http.FileSystem = http.FS(publicFS)
if err != nil {
pf.Fatal(`内置Public目录载入错误: %s, 请尝试重新编译`, err)
}
if !ztool.Fbj_IsExists(dir) {
pf.Info(`不存在Public目录, 释放默认静态文件`)
walk := func(relPath string, d fs.DirEntry, err error) error {
if err != nil {
return fmt.Errorf(`无法获取[%q]的信息: %s`, relPath, err)
}
if !d.IsDir() {
out, err := ztool.Fbj_CreatFile(filepath.Join(dir, relPath))
if err != nil {
return fmt.Errorf(`无法创建文件[%q]: %s`, relPath, err)
}
defer out.Close()
pf.Debug(`导出 [%q]...`, relPath)
obj, err := publicFS.Open(relPath)
if err != nil {
return fmt.Errorf(`无法打开文件[%q]: %s`, relPath, err)
}
if _, err := io.Copy(out, obj); err != nil {
return fmt.Errorf(`无法写入文件[%q]: %s`, relPath, err)
}
}
return nil
}
if err := fs.WalkDir(publicFS, `.`, walk); err != nil {
pf.Error(`无法释放静态文件: %s`, err)
// pf.Warn(`正在使用内置Public目录, 将无法自定义静态文件`)
// httpFS = http.FS(publicFS)
} else {
pf.Info(`全部静态资源导出完成, 祝你使用愉快 ^_^`)
}
}
// httpFS = gin.Dir(dir, false)
// r.GET(`/:file`, func(c *gin.Context) {
// file := c.Param(`file`)
// switch file {
// case `favicon.ico`:
// c.FileFromFS(`icon.ico`, httpFS)
// // case `lx-custom-source.js`:
// // c.FileFromFS(`lx-custom-source.js`, http.FS(publicFS))
// default:
// c.FileFromFS(file, httpFS)
// }
// })
r.StaticFileFS(`/favicon.ico`, `icon.ico`, httpFS)
r.StaticFileFS(`/lx-custom-source.js`, `lx-custom-source.js`, httpFS)
r.StaticFS(`/public`, httpFS)
}

View File

@ -2,6 +2,7 @@
package resp
import (
"lx-source/src/env"
"net/http"
"github.com/gin-gonic/gin"
@ -9,7 +10,7 @@ import (
// 统一输出
/*
返回码对应表
返回码对应表 (参考Python版)
0: http.StatusOK, // [200] 成功
1: http.StatusForbidden, // [403] IP被封禁
2: http.StatusServiceUnavailable, // [503] 获取失败
@ -21,36 +22,45 @@ import (
type Resp struct {
Code int `json:"code"` // 状态码 为兼容内置源设置 暂无实际作用 (1.0.2后已兼容Python版定义)
Msg string `json:"msg"` // 提示or报错信息
Data string `json:"data"` // 音乐URL
Ext string `json:"ext,omitempty"` // 其它信息
Data any `json:"data"` // 音乐URL
Ext any `json:"ext,omitempty"` // 其它信息
}
// 返回码对应列表 (参考Python版)
var statusMap = map[int]int{
0: http.StatusOK, // 成功
1: http.StatusForbidden, // IP被封禁
2: http.StatusServiceUnavailable, // 获取失败
3: http.StatusUnauthorized, // 验证失败
4: http.StatusInternalServerError, // 服务器内部错误
5: http.StatusTooManyRequests, // 请求过于频繁
6: http.StatusBadRequest, // 参数错误
}
var ErrMp3 = `https://r2eu.zw-cdn.tk/gh/lx-source/static/error.mp3`
// 获取失败默认音频
// var ErrMp3 = `https://r2eu.zxwy.link/gh/lx-source/static/error.mp3`
// 返回请求
/*
Code不为0时调用c.Abort()终止Handler
*/
func (o *Resp) Execute(c *gin.Context) {
status, ok := statusMap[o.Code]
if !ok {
// StatusCode转换 (小分支switch快, 大分支map快)
var status int
switch o.Code {
case 0:
status = http.StatusOK
case 1:
status = http.StatusForbidden
case 2:
status = http.StatusServiceUnavailable
if o.Data == nil || o.Data == `` {
o.Data = env.Config.Main.ErrMp3 //ErrMp3
}
case 3:
status = http.StatusUnauthorized
case 4:
status = http.StatusInternalServerError
case 5:
status = http.StatusTooManyRequests
case 6:
status = http.StatusBadRequest
default:
status = http.StatusOK
}
if o.Code != 0 {
if o.Code == 2 /*&& o.Data == ``*/ {
o.Data = ErrMp3
}
// if o.Code == 2 /*&& o.Data == ``*/ {
// o.Data = ErrMp3
// }
c.Abort()
}
c.JSON(status, o)
@ -65,3 +75,9 @@ func Wrap(c *gin.Context, f func() *Resp) {
r.Execute(c)
}
}
// func Wrap2(c *gin.Context, p []string, f func([]string) *Resp) {
// if r := f(util.ParaArr(c)); r != nil {
// r.Execute(c)
// }
// }

View File

@ -1,8 +1,21 @@
package util
import "github.com/gin-gonic/gin"
import (
"lx-source/src/env"
"net/http"
"github.com/ZxwyWebSite/ztool"
"github.com/gin-gonic/gin"
)
// 将路由参数转为Map
/*
`:s/:id/:q` -> {
`s`: `source`,
`id`: `musicId`,
`q`: `quality`,
}
*/
func ParaMap(c *gin.Context) map[string]string {
parmlen := len(c.Params)
parms := make(map[string]string, parmlen)
@ -11,3 +24,51 @@ func ParaMap(c *gin.Context) map[string]string {
}
return parms
}
// 将路由参数转为Array
/*
ParaArr(c, `id`, `s`, `xxx`) => [
`musicId`,
`source`,
``,
]
*/
func ParaArr(c *gin.Context, s ...string) []string {
parmlen := len(c.Params)
parslen := len(s)
out := make([]string, parslen)
for im := 0; im < parmlen; im++ {
obj := c.Params[im]
for is := 0; is < parslen; is++ {
if s[is] == obj.Key {
out[is] = obj.Value
}
}
}
return out
}
var pathCache string
func init() {
env.Inits.Add(func() {
if !env.Config.Main.NgProxy {
pathCache = `/`
}
})
}
// 动态获取相对路径 <Ctx, 特征> <路径>
/*
HOST = `192.168.10.22:1011`
URI = `/path/to/lxs/link/wy/2049512697/flac`
sub = `link/`
-> http://192.168.10.22:1011/path/to/lxs/
*/
func GetPath(c *http.Request, sub string) string {
// 从缓存读取相对路径 `/path/to/lxs/` or `/`
if pathCache == `` {
pathCache = ztool.Str_Before(c.RequestURI, sub)
}
return ztool.Str_FastConcat(`http://`, c.Host, pathCache)
}

View File

@ -1,198 +0,0 @@
package router
import (
"lx-source/src/caches"
"lx-source/src/env"
"lx-source/src/middleware/auth"
"lx-source/src/middleware/dynlink"
"lx-source/src/middleware/loadpublic"
"lx-source/src/middleware/resp"
"lx-source/src/middleware/util"
"lx-source/src/sources"
"net/http"
"github.com/ZxwyWebSite/ztool"
"github.com/gin-contrib/gzip"
"github.com/gin-gonic/gin"
)
var (
// 默认音质
defQuality = []string{`128k`, `320k`, `flac`, `flac24bit`}
// 试听音质
tstQuality = []string{`128k`}
// 标准音质
stdQuality = []string{`128k`, `320k`, `flac`}
)
// 自动生成支持的音质表
func loadQMap() [][]string {
m := make([][]string, 6)
// 0.wy
if env.Config.Custom.Wy_Enable {
m[0] = defQuality
} else {
m[0] = tstQuality
}
// 1.mg
m[1] = defQuality
// 2.kw
m[2] = defQuality
// 3.kg
m[3] = tstQuality
// 4.tx
if env.Config.Custom.Tx_Enable {
m[4] = stdQuality
} else {
m[4] = tstQuality
}
// 5.lx
// m[sources.S_lx] = defQuality
return m
}
// 载入路由
func InitRouter() *gin.Engine {
r := gin.Default()
qmap := loadQMap()
// Gzip压缩
if env.Config.Main.Gzip {
r.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{"/file/"})))
}
// 源信息
r.GET(`/`, func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
`version`: env.Version, // 服务端程序版本
`name`: `lx-music-source`, // 名称
`msg`: `Hello~::^-^::~v1~`, // Api大版本
// `developer`: []string{`Zxwy`}, // 开发者列表, 可在保留原作者的基础上添加你自己的名字?
// 仓库地址
// `github`: `https://github.com/ZxwyWebSite/lx-source`,
// 可用平台
`source`: gin.H{
sources.S_wy: qmap[0], //true,
sources.S_mg: qmap[1], //true,
sources.S_kw: qmap[2], //true,
sources.S_kg: qmap[3], //[]string{`128k`, `320k`}, // 测试结构2, 启用时返回音质列表, 禁用为false
sources.S_tx: qmap[4], //gin.H{ // "测试结构 不代表最终方式"
// `enable`: false,
// `qualitys`: []string{`128k`, `320k`, `flac`, `flac24bit`},
// },
sources.S_lx: qmap[5],
},
// 自定义源脚本更新
`script`: env.DefCfg.Script, //env.Config.Script,
})
})
// 静态文件
loadpublic.LoadPublic(r)
// r.StaticFile(`/favicon.ico`, `public/icon.ico`)
// r.StaticFile(`/lx-custom-source.js`, `public/lx-custom-source.js`)
// 解析接口
r.GET(`/link/:s/:id/:q`, auth.InitHandler(linkHandler)...)
dynlink.LoadHandler(r)
// r.GET(`/file/:t/:x/:f`, dynlink.FileHandler())
// if cache, ok := caches.UseCache.(*localcache.Cache); ok {
// r.Static(`/file`, cache.Path)
// }
// if env.Config.Cache.Mode == `local` {
// r.Static(`/file`, env.Config.Cache.Local_Path)
// }
// 软件接口
// api := r.Group(`/api`)
// {
// api.GET(`/lx`, lxHandler) // 洛雪音乐
// }
// 数据接口
// r.GET(`/file/:t/:hq/:n`, func(c *gin.Context) {
// c.String(http.StatusOK, time.Now().Format(`20060102150405`))
// })
// 暂不对文件接口进行验证 脚本返回链接无法附加请求头 只可在Get添加Query
// g := r.Group(``)
// {
// g.Use(authHandler)
// g.GET(`/link/:s/:id/:q`, linkHandler)
// g.Static(`/file`, LocalCachePath)
// }
return r
}
// 数据返回格式
const (
cacheHIT = `Cache HIT` // 缓存已命中
cacheMISS = `Cache MISS` // 缓存未命中
cacheSet = `Cache Seted` // 缓存已设置
memHIT = `Memory HIT` // 内存已命中
memRej = `Memory Reject` // 内存已拒绝
)
// 外链解析
func linkHandler(c *gin.Context) {
resp.Wrap(c, func() *resp.Resp {
// 获取传入参数 检查合法性
parms := util.ParaMap(c)
// getParam := func(p string) string { return strings.TrimSuffix(strings.TrimPrefix(c.Param(p), `/`), `/`) } //strings.Trim(c.Param(p), `/`)
s := parms[`s`] //c.Param(`s`) //getParam(`s`) // source 平台 wy, mg, kw
id := parms[`id`] //c.Param(`id`) //getParam(`id`) // sid 音乐ID wy: songmid, mg: copyrightId
q := parms[`q`] //c.Param(`q`) //getParam(`q`) // quality 音质 128k / 320k / flac / flac24bit
env.Loger.NewGroup(`LinkQuery`).Debug(`s: %v, id: %v, q: %v`, s, id, q)
if ztool.Chk_IsNilStr(s, q, id) {
return &resp.Resp{Code: 6, Msg: `参数不全`} // http.StatusBadRequest
}
cquery := caches.NewQuery(s, id, q)
// fmt.Printf("%+v\n", cquery)
defer cquery.Free()
// _, ok := sources.UseSource.Verify(cquery) // 获取请求音质 同时检测是否支持(如kw源没有flac24bit) qualitys[q][s]rquery
// if !ok {
// return &resp.Resp{Code: 6, Msg: `不支持的平台或音质`}
// }
// 查询内存
clink, ok := env.Cache.Get(cquery.Query())
if ok {
if str, ok := clink.(string); ok {
env.Loger.NewGroup(`MemCache`).Debug(`MemHIT [%q]=>[%q]`, cquery.Query(), str)
if str == `` {
return &resp.Resp{Code: 2, Msg: memRej} // 拒绝请求,当前一段时间内解析出错 `MemCache Reject`
}
return &resp.Resp{Msg: memHIT, Data: str} // `MemCache HIT`
}
}
// 查询缓存
var cstat bool
if caches.UseCache != nil {
cstat = caches.UseCache.Stat()
}
sc := env.Loger.NewGroup(`StatCache`)
if cstat {
sc.Debug(`Method: Get, Query: %v`, cquery.Query())
if link := caches.UseCache.Get(cquery); link != `` {
env.Cache.Set(cquery.Query(), link, 3600)
return &resp.Resp{Msg: cacheHIT, Data: link}
}
} else {
sc.Debug(`Disabled`)
}
// 解析歌曲外链
outlink, emsg := sources.UseSource.GetLink(cquery)
if emsg != `` {
if emsg == sources.Err_Verify { // Verify Failed: 不支持的平台或音质
return &resp.Resp{Code: 6, Msg: ztool.Str_FastConcat(emsg, `: 不支持的平台或音质`)}
}
env.Cache.Set(cquery.Query(), ``, 600) // 发生错误的10分钟内禁止再次查询
return &resp.Resp{Code: 2, Msg: emsg}
}
// 缓存并获取直链 !(s == `kg` || (s == `tx` && !tx_en)) => (s != `kg` && (s != `tx` || tx_en))
if outlink != `` && cstat && cquery.Source != sources.S_kg && (cquery.Source != sources.S_tx || env.Config.Custom.Tx_Enable) {
sc.Debug(`Method: Set, Link: %v`, outlink)
if link := caches.UseCache.Set(cquery, outlink); link != `` {
env.Cache.Set(cquery.Query(), link, 3600)
return &resp.Resp{Msg: cacheSet, Data: link}
}
}
// 无法获取直链 直接返回原链接
env.Cache.Set(cquery.Query(), outlink, 1200)
return &resp.Resp{Msg: cacheMISS, Data: outlink}
})
}

158
src/server/api_music.go Normal file
View File

@ -0,0 +1,158 @@
package server
import (
"lx-source/src/caches"
"lx-source/src/env"
"lx-source/src/middleware/auth"
"lx-source/src/middleware/resp"
"lx-source/src/middleware/util"
"lx-source/src/sources"
"lx-source/src/sources/custom"
"strings"
"sync/atomic"
"github.com/ZxwyWebSite/ztool"
"github.com/gin-gonic/gin"
)
// type (
// Context struct {
// Parms map[string]string
// }
// Source interface {
// Link(c *Context) (string, error)
// Lyric(c *Context)
// }
// )
func loadMusic(api gin.IRouter) {
// /{method}/{source}/{musicId}/{?quality}
api.GET(`/:m/:s/:id/*q`, auth.InitHandler(musicHandler)...)
}
func musicHandler(c *gin.Context) {
resp.Wrap(c, func() *resp.Resp {
// 获取请求参数 (测试用Array会不会提升性能)
arr := util.ParaArr(c, `s`, `m`, `id`, `q`)
ps, pm, pid, pq := arr[0], arr[1], arr[2], strings.TrimPrefix(arr[3], `/`)
out := &resp.Resp{Code: 0} // 默认Code:6 (参数错误)
loger := env.Loger.NewGroup(`MusicHandler`)
defer loger.Free()
loger.Debug(`s:'%v', m:'%v', id:'%v', q:'%v'`, ps, pm, pid, pq)
// 定位音乐源
var source custom.Source
var active bool // 是否激活(自定义账号)
switch ps {
case sources.S_wy:
active = env.Config.Custom.Wy_Enable
source = custom.WySource
case sources.S_mg:
active = env.Config.Custom.Mg_Enable
source = custom.MgSource
case sources.S_kw:
active = env.Config.Custom.Kw_Enable
source = custom.KwSource
case sources.S_kg:
active = env.Config.Custom.Kg_Enable
source = custom.KgSource
case sources.S_tx:
active = env.Config.Custom.Tx_Enable
source = custom.TxSource
case sources.S_lx:
source = custom.LxSource
default:
out.Code = 6
out.Msg = ztool.Str_FastConcat(`无效源参数:'`, ps, `'`)
return out
}
if source == nil {
out.Code = 6
out.Msg = sources.ErrDisable
return out
}
if !source.Vef(&pid) {
out.Code = 6
out.Msg = sources.E_VefMusicId
return out
}
// 查询内存缓存
atomic.AddInt64(&accnum, 1)
cquery := strings.Join([]string{pm, ps, pid, pq}, `/`)
loger.Debug(`MemoGet: %v`, cquery)
if cdata, ok := env.Cache.Get(cquery); ok {
loger.Debug(`MemoHIT: %q`, cdata)
if cdata == `` {
out.Code = 2
out.Msg = memRej
} else {
out.Msg = memHIT
out.Data = cdata
}
return out
}
// 定位源方法
switch pm {
case `url`, `link`:
// if !active && pq != sources.Q_128k {
// out.Msg = `未激活源仅可试听128k音质`
// return out
// }
// 查询文件缓存
var cstat bool
if caches.UseCache != nil {
cstat = caches.UseCache.Stat()
}
uquery := caches.NewQuery(ps, pid, pq)
uquery.Request = c.Request
defer uquery.Free()
if cstat {
loger.Debug(`FileGet: %v`, uquery.Query())
if olink := caches.UseCache.Get(uquery); olink != `` {
env.Cache.Set(cquery, olink, sources.C_lx)
out.Msg = cacheHIT
out.Data = olink
return out
}
}
// 解析歌曲外链
atomic.AddInt64(&reqnum, 1)
out.Data, out.Msg = source.Url(pid, pq)
if out.Data != `` {
// 缓存并获取直链
atomic.AddInt64(&secnum, 1)
if out.Msg == `` {
if cstat && active {
loger.Debug(`FileSet: %v`, out.Data)
if link := caches.UseCache.Set(uquery, out.Data.(string)); link != `` {
env.Cache.Set(cquery, link, sources.C_lx)
out.Msg = cacheSet
out.Data = link
return out
}
out.Msg = cacheFAIL
} else {
out.Msg = cacheMISS
}
} else {
loger.Warn(`发生错误: %s`, out.Msg)
}
// 无法获取直链 直接返回原链接
env.Cache.Set(cquery, out.Data, source.Exp()-300)
return out
}
case `lrc`, `lyric`:
out.Data, out.Msg = source.Lrc(pid)
case `pic`, `cover`:
out.Data, out.Msg = source.Pic(pid)
default:
out.Code = 6
out.Msg = ztool.Str_FastConcat(`无效源方法:'`, pm, `'`)
return out
}
if out.Msg != `` {
out.Code = 2
env.Cache.Set(cquery, out.Data, 600)
}
return out
})
}

61
src/server/app_lxmusic.go Normal file
View File

@ -0,0 +1,61 @@
//go:build extapp
package server
import (
"lx-source/src/middleware/resp"
"github.com/ZxwyWebSite/ztool"
"github.com/gin-gonic/gin"
)
type queryObj struct {
Name string `json:"name"` // 歌名
Singer string `json:"singer"` // 歌手
Source string `json:"source"` // 平台
Songmid string `json:"songmid"` // 音乐ID
Interval string `json:"interval"` // 时长
AlbumName string `json:"albumName"` // 专辑
Img string `json:"img"` // 封面
// TypeURL struct {
// } `json:"typeUrl"` // 未知
AlbumID string `json:"albumId"` // 专辑ID
// 支持音质
Types []struct {
Type string `json:"type"` // 音质
Size string `json:"size"` // 大小
Hash string `json:"hash"` // 哈希(kg only)
} `json:"types"`
// tx
StrMediaMid string `json:"strMediaMid"` // 当前文件ID
AlbumMid string `json:"albumMid"` // 专辑ID
SongID int `json:"songId"` // 音乐ID
// mg
CopyrightID string `json:"copyrightId"` // 音乐ID
LrcURL string `json:"lrcUrl"` // lrc歌词
MrcURL string `json:"mrcUrl"` // mrc歌词
TrcURL string `json:"trcUrl"` // trc歌词
// kg
Hash string `json:"hash"` // 文件哈希(kg only)
}
func loadLxMusic(lx *gin.RouterGroup) {
// 获取链接
lx.POST(`/link/:q`, func(c *gin.Context) {
resp.Wrap(c, func() *resp.Resp {
var obj queryObj
if err := c.ShouldBindJSON(&obj); err != nil {
return &resp.Resp{Code: 6, Msg: `解析错误: ` + err.Error()}
}
pams := map[string]string{
`s`: obj.Source,
`id`: ztool.Str_Select(obj.Hash, obj.CopyrightID, obj.Songmid),
}
for k, v := range pams {
c.Params = append(c.Params, gin.Param{Key: k, Value: v})
}
return nil
})
}, linkHandler)
// lx.GET(`/info`)
}

View File

@ -0,0 +1,36 @@
//go:build extapp
package server
import (
"lx-source/src/middleware/util"
"lx-source/src/sources"
"net/http"
"github.com/ZxwyWebSite/ztool"
"github.com/gin-gonic/gin"
)
func loadMusicFree(mf *gin.RouterGroup) {
// 插件订阅
mf.GET(`/subscribe`, func(c *gin.Context) {
slist := sources.S_al
type plugins struct {
Name string `json:"name"`
Url string `json:"url"`
Version string `json:"version"`
}
length := len(slist)
plgs := make([]plugins, length)
url := ztool.Str_FastConcat(util.GetPath(c.Request, `app/`), `public/musicfree/`)
for i := 0; i < length; i++ {
name := `lxs-` + slist[i]
plgs[i] = plugins{
Name: name,
Url: ztool.Str_FastConcat(url, name, `.js`),
Version: `0.0.0`,
}
}
c.JSON(http.StatusOK, gin.H{`plugins`: plgs})
})
}

170
src/server/loadpublic.go Normal file
View File

@ -0,0 +1,170 @@
// 静态资源
package server
import (
"bytes"
"embed"
"fmt"
"io"
"io/fs"
"lx-source/src/env"
"net/http"
"path/filepath"
"strings"
"github.com/ZxwyWebSite/ztool"
"github.com/ZxwyWebSite/ztool/x/bytesconv"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/render"
)
//go:embed public
var publicEM embed.FS // 打包默认Public目录 src/server/public
// 载入Public目录并设置路由
func loadPublic(r *gin.Engine) {
pf := env.Loger.NewGroup(`PublicFS`)
dir := ztool.Str_FastConcat(env.RunPath, `/data/public`)
publicFS, err := fs.Sub(publicEM, `public`)
var httpFS http.FileSystem = http.FS(publicFS)
if err != nil {
pf.Fatal(`内置Public目录载入错误: %s, 请尝试重新编译`, err)
}
if !ztool.Fbj_IsExists(dir) {
pf.Info(`不存在Public目录, 释放默认静态文件`)
walk := func(relPath string, d fs.DirEntry, err error) error {
if err != nil {
return fmt.Errorf(`无法获取[%q]的信息: %s`, relPath, err)
}
if !d.IsDir() {
out, err := ztool.Fbj_CreatFile(filepath.Join(dir, relPath))
if err != nil {
return fmt.Errorf(`无法创建文件[%q]: %s`, relPath, err)
}
defer out.Close()
pf.Debug(`导出 [%q]...`, relPath)
obj, err := publicFS.Open(relPath)
if err != nil {
return fmt.Errorf(`无法打开文件[%q]: %s`, relPath, err)
}
if _, err := io.Copy(out, obj); err != nil {
return fmt.Errorf(`无法写入文件[%q]: %s`, relPath, err)
}
}
return nil
}
if err := fs.WalkDir(publicFS, `.`, walk); err != nil {
pf.Error(`无法释放静态文件: %s`, err)
// pf.Warn(`正在使用内置Public目录, 将无法自定义静态文件`)
// httpFS = http.FS(publicFS)
} else {
pf.Info(`全部静态资源导出完成, 祝你使用愉快 ^_^`)
}
}
pf.Free()
// 使用本地public目录
// httpFS = gin.Dir(dir, false)
// r.GET(`/:file`, func(c *gin.Context) {
// file := c.Param(`file`)
// switch file {
// case `favicon.ico`:
// c.FileFromFS(`icon.ico`, httpFS)
// // case `lx-custom-source.js`:
// // c.FileFromFS(`lx-custom-source.js`, http.FS(publicFS))
// default:
// c.FileFromFS(file, httpFS)
// }
// })
// 自动填写源脚本参数
if env.Config.Script.Auto > 0 {
file, _ := publicFS.Open(`lx-custom-source.js`)
data, _ := io.ReadAll(file)
file.Close()
data = bytes.Replace(data,
bytesconv.StringToBytes(`http://127.0.0.1:1011/`),
bytesconv.StringToBytes(env.Config.Cache.Local_Bind), 1,
)
if env.Config.Auth.ApiKey_Enable && env.Config.Script.Auto >= 2 {
data = bytes.Replace(data,
bytesconv.StringToBytes(`apipass = ''`),
bytesconv.StringToBytes(ztool.Str_FastConcat(
`apipass = '`, env.Config.Auth.ApiKey_Value, `'`,
)), 1,
)
}
r.GET(`/lx-custom-source.js`, func(c *gin.Context) {
var mime string
if _, ok := c.GetQuery(`raw`); ok {
mime = `application/octet-stream`
} else {
mime = `text/javascript; charset=utf-8`
}
c.Render(http.StatusOK, render.Data{
ContentType: mime,
Data: data,
})
})
} else {
r.StaticFileFS(`/lx-custom-source.js`, `lx-custom-source.js`, httpFS)
}
// 新版源脚本
{
// 构建文件头
var b strings.Builder
b.Grow(75 +
len(env.Config.Script.Name) +
len(env.Config.Script.Descript) +
len(env.Config.Script.Version) +
len(env.Config.Script.Author) +
len(env.Config.Script.Homepage),
)
b.WriteString("/*!\n * @name ")
b.WriteString(env.Config.Script.Name)
b.WriteString("\n * @description ")
b.WriteString(env.Config.Script.Descript)
b.WriteString("\n * @version v")
b.WriteString(env.Config.Script.Version)
b.WriteString("\n * @author ")
b.WriteString(env.Config.Script.Author)
b.WriteString("\n * @homepage ")
b.WriteString(env.Config.Script.Homepage)
b.WriteString("\n */\n")
// 构建文件体
file, _ := publicFS.Open(`lx-source-script.js`)
data, _ := io.ReadAll(file)
file.Close()
r.GET(`/lx-source-script.js`, func(c *gin.Context) {
var mime string
if _, ok := c.GetQuery(`raw`); ok {
mime = `application/octet-stream`
} else {
mime = `text/javascript; charset=utf-8`
}
// 构建文件尾
var d strings.Builder
d.WriteString(`globalThis.ls={api:{addr:'`)
d.WriteString(env.Config.Cache.Local_Bind)
d.WriteString(`',pass:'`)
if env.Config.Auth.ApiKey_Enable {
if env.Config.Script.Auto >= 2 {
d.WriteString(env.Config.Auth.ApiKey_Value)
} else {
if key, ok := c.GetQuery(`key`); ok {
d.WriteString(key)
}
}
}
d.WriteString(`'}};`)
d.WriteByte('\n')
// Render
c.Status(http.StatusOK)
c.Writer.Header()[`Content-Type`] = []string{mime}
c.Writer.WriteString(b.String())
c.Writer.WriteString(d.String())
c.Writer.Write(data)
})
}
r.StaticFileFS(`/favicon.ico`, `lx-icon.ico`, httpFS)
r.StaticFileFS(`/status`, `status.html`, httpFS)
r.StaticFS(`/public`, httpFS)
}

63
src/server/loadquality.go Normal file
View File

@ -0,0 +1,63 @@
package server
import (
"lx-source/src/env"
"lx-source/src/sources"
)
var (
// 默认音质
defQuality = []string{`128k`, `320k`, `flac`, `flac24bit`}
// 试听音质
tstQuality = []string{`128k`}
// 标准音质
stdQuality = []string{`128k`, `320k`, `flac`}
)
// 自动生成支持的音质表
func loadQMap() [][]string {
m := make([][]string, 6)
// 0.wy
if env.Config.Source.Enable_Wy {
if env.Config.Custom.Wy_Enable {
m[sources.I_wy] = defQuality
} else {
m[sources.I_wy] = tstQuality
}
}
// 1.mg
if env.Config.Source.Enable_Mg {
if env.Config.Custom.Mg_Enable {
m[sources.I_mg] = defQuality
} else {
m[sources.I_mg] = tstQuality
}
}
// 2.kw
if env.Config.Source.Enable_Kw {
if env.Config.Custom.Kw_Enable {
m[sources.I_kw] = stdQuality
}
}
// 3.kg
if env.Config.Source.Enable_Kg {
if env.Config.Custom.Kg_Enable {
m[sources.I_kg] = defQuality
} else {
m[sources.I_kg] = tstQuality
}
}
// 4.tx
if env.Config.Source.Enable_Tx {
if env.Config.Custom.Tx_Enable {
m[sources.I_tx] = stdQuality
} else {
m[sources.I_tx] = tstQuality
}
}
// 5.lx
if env.Config.Source.Enable_Lx {
m[sources.I_lx] = defQuality
}
return m
}

View File

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 162 KiB

View File

@ -3,23 +3,19 @@
* @description Client
* version 1.0.1
* @author Zxwy
* @homepage https://github.com/ZxwyWebSite/lx-source
* homepage null
*/
// 脚本配置
const version = '1.0.3' // 脚本版本
const apiaddr = 'http://127.0.0.1:1011/' // 服务端地址,末尾加斜杠
const apipass = '' // 验证密钥,由服务端自动生成 '${apipass}'
const apipass = '' // 验证密钥,填在单引号内
const devmode = true // 调试模式
// const timeout = 60 * 1000 // 请求超时(ms)
// 常量 & 默认值
const { EVENT_NAMES, request, on, send } = window.lx ?? globalThis.lx
const { EVENT_NAMES, request, on, send } = globalThis.lx
const defs = { type: 'music', actions: ['musicUrl'] }
// const defaults = {
// type: 'music', // 目前固定为 music
// actions: ['musicUrl'], // 目前固定为 ['musicUrl']
// qualitys: ['128k', '320k', 'flac', 'flac24bit'], // 当前脚本的该源所支持获取的Url音质有效的值有['128k', '320k', 'flac', 'flac24bit']
// }
const defheaders = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.84 Safari/537.36 HBPC/12.1.2.300',
'Accept': 'application/json, text/plain, */*',
@ -39,6 +35,7 @@ const conf = {
const httpRequest = (url, options) => new Promise((resolve, reject) => {
options.headers = { ...defheaders, ...options.headers } // 添加默认请求头
// options.timeout ? options.timeout : timeout // 添加默认请求超时
request(url, options, (err, resp) => {
if (err) return reject(err)
resolve(resp.body)
@ -48,8 +45,8 @@ const httpRequest = (url, options) => new Promise((resolve, reject) => {
const musicUrl = async (source, info, quality) => {
const start = new Date().getTime();
const id = info.hash ?? info.copyrightId ?? info.songmid // 音乐id kg源为hash, mg源为copyrightId
const ext = source == 'kg' ? info.albumId : '' //source == 'tx' ? info.strMediaMid
const query = `${source}/${id}${(ext != '' && ext != void 0) ? '-' + ext : ''}/${quality}`
//const ext = source == 'kg' ? info.albumId : '' //source == 'tx' ? info.strMediaMid
const query = `${source}/${id}/${quality}` //${(ext != '' && ext != void 0) ? '-' + ext : ''}
console.log('创建任务: %s, 音乐信息: %O', query, info)
const body = await httpRequest(`${apiaddr}link/${query}`, { method: 'get' });
console.log('返回数据: %O', body, `, 耗时 ${new Date().getTime() - start} ms`)
@ -80,8 +77,7 @@ const init = () => {
'use strict';
console.log('初始化脚本, 版本: %s, 服务端地址: %s', version, apiaddr)
var stat = false; var msg = ''; var updUrl = ''; var sourcess = {}
httpRequest(apiaddr, { method: 'get' })
.catch((err) => { msg = '初始化失败: ' + err ?? '连接服务端超时'; console.log(msg) })
httpRequest(apiaddr, { method: 'get', timeout: 1000 * 10 })
.then((body) => {
if (!body) { msg = '初始化失败:' + '无返回数据'; return }
console.log('获取服务端数据成功: %o, 版本: %s', body, body.version)
@ -104,7 +100,7 @@ const init = () => {
if (source[v] != null /*== true*/) {
sourcess[v] = {
name: v,
...defs, // ...defaults,
...defs,
qualitys: source[v], // 支持返回音质时启用 使用后端音质表
}
}
@ -112,18 +108,13 @@ const init = () => {
// 完成初始化
stat = true
})
.catch((err) => { msg = '初始化失败: ' + err ?? '连接服务端超时'; console.log(msg) })
.finally(() => {
// 脚本初始化完成后需要发送inited事件告知应用
send(EVENT_NAMES.inited, {
status: stat, // 初始化成功 or 失败 (初始化失败不打开控制台, 使用更新提示接口返回信息)
openDevTools: stat ? devmode : false, // 是否打开开发者工具,方便用于调试脚本 'devmode' or 'stat ? devmode : false'
sources: sourcess, // 使用服务端源列表
// sources: { // 当前脚本支持的源
// wy: { name: '网易音乐', ...defaults, },
// mg: { name: '咪咕音乐', ...defaults, },
// kw: { name: '酷我音乐', ...defaults, },
// // kg: { name: '酷狗音乐', ...defaults, }, // 暂不支持,仅供换源
// },
})
// 发送更新提示
if (msg) send(EVENT_NAMES.updateAlert, { log: '提示:' + msg, updateUrl: updUrl ? apiaddr + updUrl : '' })

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,126 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>LX Source API</title>
</head>
<body>
<h1>LX Source API</h1>
当你看到这个页面时,服务已经成功跑起来了~
<h2>示例:</h2>
<script>
const api = './';
function l(s, id, q) {
let url = `${api}link/${s}/${id[Math.floor(Math.random() * id.length)]}/${q}`;
const key = localStorage.getItem('apipass'); if (key) url += `?key=${encodeURIComponent(key)}`;
fetch(url)
.then((response) => response.json())
.then((res) => {
if (res.code != 0 || res.data == '') throw res.msg;
window.open(res.data);
})
.catch((err) => {
alert(err);
});
};
</script>
<ul>
<li>0. <a href="./lx-custom-source.js?raw">获取源脚本</a></li>
<li>1. <a onclick="l('wy',['473120817','434022356','2112290949','27646205'],'320k')">WY小芸源</a></li>
<li>2. <a onclick="l('mg',['6005970X6D1','63278105706','60054702010'],'320k')">MG小蜜源</a></li>
<li>3. <a onclick="l('kw',['236891','26428340','133360','112051'],'320k')">KW小蜗源</a></li>
<li>4. <a onclick="l('kg',['A90B30D3F51C56841D3FE51D5C5B09A8'],'128k')">KG小枸源</a></li>
<li>5. <a onclick="l('tx',['002Le9vl2JlYDK','0013Xlvm0ZKNnA'],'128k')">TX小秋源</a></li>
</ul>
<h2>状态:</h2>
<ul>
<li>
已持续运行
<strong id="lxs_run_d">20</strong>
<strong id="lxs_run_h">21</strong>
<strong id="lxs_run_m">10</strong>
<strong id="lxs_run_s">08</strong>
</li>
<li>
调用:
<strong id="lxs_sum_acc">0</strong>
/解析:
<strong id="lxs_sum_req">0</strong>
/成功:
<strong id="lxs_sum_sec">0</strong>
</li>
<li>
版本号:
<strong id="lxs_ver">dev</strong>
</li>
<li><strong id="lxs_err"></strong></li>
</ul>
<script>
fetch(api)
.then((response) => response.json())
.then((res) => {
document.getElementById("lxs_sum_acc").innerText = res.summary.Accessn;
document.getElementById("lxs_sum_req").innerText = res.summary.Request;
document.getElementById("lxs_sum_sec").innerText = res.summary.Success;
const startTime = new Date(res.summary.StartAt * 1000);
const updateTimer = () => {
const elapsed = new Date() - startTime;
document.getElementById("lxs_run_d").innerText = String(Math.floor(elapsed / 864e5)).padStart(2, "0");
document.getElementById("lxs_run_h").innerText = String(Math.floor((elapsed % 864e5) / 36e5)).padStart(2, "0");
document.getElementById("lxs_run_m").innerText = String(Math.floor((elapsed % 36e5) / 6e4)).padStart(2, "0");
document.getElementById("lxs_run_s").innerText = String(Math.floor((elapsed % 6e4) / 1000)).padStart(2, "0");
};
updateTimer(), setInterval(updateTimer, 1e3);
document.getElementById("lxs_ver").innerText = res.version;
if (res.auth.apikey && !localStorage.getItem("apipass")) {
const key = prompt("请填写API密钥"); if (key) localStorage.setItem("apipass", key);
}
})
.catch((err) => {
document.getElementById("lxs_err").innerText = err;
});
</script>
<style>
html,
body {
height: 100vh;
width: 100vw;
margin: 0;
padding: 0;
background: #ffffff;
text-align: center;
margin-top: 30px;
overflow: hidden;
}
* {
color: rgb(100, 100, 100);
}
a {
color: #42b983;
text-decoration: none;
cursor: pointer;
}
ul,
li {
margin: 0;
}
ul {
margin-left: -40px;
line-height: 30px;
}
li {
list-style: none;
}
</style>
</body>
</html>

192
src/server/router.go Normal file
View File

@ -0,0 +1,192 @@
package server
import (
"lx-source/src/env"
"lx-source/src/middleware/dynlink"
"lx-source/src/sources"
"net/http"
"time"
"github.com/gin-contrib/cors"
"github.com/gin-contrib/gzip"
"github.com/gin-gonic/gin"
)
var (
accnum int64
reqnum int64
secnum int64
)
// 载入路由
func InitRouter() *gin.Engine {
r := gin.Default()
qmap := loadQMap()
// Gzip压缩
if env.Config.Main.Gzip {
r.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{"/file/"})))
}
// Cors跨域
if env.Config.Main.Cors {
r.Use(cors.Default())
}
startime := time.Now().Unix()
// 源信息
r.GET(`/`, func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
`version`: env.Version, // 服务端程序版本
`name`: `lx-music-source`, // 名称
`msg`: `Hello~::^-^::~v1~`, // Api大版本
// `developer`: []string{`Zxwy`}, // 开发者列表, 可在保留原作者的基础上添加你自己的名字?
// 仓库地址
// `github`: `https://github.com/ZxwyWebSite/lx-source`,
// 可用平台
`source`: gin.H{
sources.S_wy: qmap[sources.I_wy],
sources.S_mg: qmap[sources.I_mg],
sources.S_kw: qmap[sources.I_kw],
sources.S_kg: qmap[sources.I_kg],
sources.S_tx: qmap[sources.I_tx],
sources.S_lx: qmap[sources.I_lx],
},
// 自定义源脚本更新
`script`: env.Config.Script.Update, //env.Config.Script,
// 数据统计
`summary`: gin.H{
`StartAt`: startime, // 启动时间
`Accessn`: accnum, // 访问次数
`Request`: reqnum, // 解析次数
`Success`: secnum, // 成功次数
},
// 验证方式
`auth`: gin.H{
`apikey`: env.Config.Auth.ApiKey_Enable,
},
})
})
// 静态文件
loadPublic(r)
// r.StaticFile(`/favicon.ico`, `public/icon.ico`)
// r.StaticFile(`/lx-custom-source.js`, `public/lx-custom-source.js`)
// 解析接口
loadMusic(r)
// r.GET(`/link/:s/:id/:q`, auth.InitHandler(linkHandler)...)
dynlink.LoadHandler(r)
// 动态链?
// r.GET(`/file/:t/:x/:f`, dynlink.FileHandler())
// if cache, ok := caches.UseCache.(*localcache.Cache); ok {
// r.Static(`/file`, cache.Path)
// }
// if env.Config.Cache.Mode == `local` {
// r.Static(`/file`, env.Config.Cache.Local_Path)
// }
// 功能接口
// api := r.Group(`/api`)
// {
// api.GET(`/:s/:m/:q`) // {source}/{method}/{query}
// }
// 软件接口
// app := r.Group(`/app`)
// {
// loadLxMusic(app.Group(`/lxmusic`))
// loadMusicFree(app.Group(`/musicfree`))
// }
// 数据接口
// r.GET(`/file/:t/:hq/:n`, func(c *gin.Context) {
// c.String(http.StatusOK, time.Now().Format(`20060102150405`))
// })
// 暂不对文件接口进行验证 脚本返回链接无法附加请求头 只可在Get添加Query
// g := r.Group(``)
// {
// g.Use(authHandler)
// g.GET(`/link/:s/:id/:q`, linkHandler)
// g.Static(`/file`, LocalCachePath)
// }
return r
}
// 数据返回格式
const (
cacheHIT = `Cache HIT` // 缓存已命中
cacheMISS = `Cache MISS` // 缓存未命中
cacheSet = `Cache Seted` // 缓存已设置
cacheFAIL = `Cache FAIL` // 缓存未成功
memHIT = `Memory HIT` // 内存已命中
memRej = `Memory Reject` // 内存已拒绝
)
// 外链解析
// func linkHandler(c *gin.Context) {
// resp.Wrap(c, func() *resp.Resp {
// // 获取传入参数 检查合法性
// arr := util.ParaArr(c, `s`, `id`, `q`)
// s, id, q := arr[0], arr[1], arr[2]
// // parms := util.ParaMap(c)
// // getParam := func(p string) string { return strings.TrimSuffix(strings.TrimPrefix(c.Param(p), `/`), `/`) } //strings.Trim(c.Param(p), `/`)
// // s := parms[`s`] //c.Param(`s`) //getParam(`s`) // source 平台 wy, mg, kw
// // id := parms[`id`] //c.Param(`id`) //getParam(`id`) // sid 音乐ID wy: songmid, mg: copyrightId
// // q := parms[`q`] //c.Param(`q`) //getParam(`q`) // quality 音质 128k / 320k / flac / flac24bit
// env.Loger.NewGroup(`LinkQuery`).Debug(`s: %v, id: %v, q: %v`, s, id, q).Free()
// if ztool.Chk_IsNilStr(s, q, id) {
// return &resp.Resp{Code: 6, Msg: `参数不全`} // http.StatusBadRequest
// }
// cquery := caches.NewQuery(s, id, q)
// cquery.Request = c.Request
// // fmt.Printf("%+v\n", cquery)
// defer cquery.Free()
// // _, ok := sources.UseSource.Verify(cquery) // 获取请求音质 同时检测是否支持(如kw源没有flac24bit) qualitys[q][s]rquery
// // if !ok {
// // return &resp.Resp{Code: 6, Msg: `不支持的平台或音质`}
// // }
// // 查询内存
// if clink, ok := env.Cache.Get(cquery.Query()); ok {
// if str, ok := clink.(string); ok {
// env.Loger.NewGroup(`MemCache`).Debug(`MemHIT [%q]=>[%q]`, cquery.Query(), str).Free()
// if str == `` {
// return &resp.Resp{Code: 2, Msg: memRej} // 拒绝请求,当前一段时间内解析出错 `MemCache Reject`
// }
// return &resp.Resp{Msg: memHIT, Data: str} // `MemCache HIT`
// }
// }
// // 查询缓存
// var cstat bool
// if caches.UseCache != nil {
// cstat = caches.UseCache.Stat()
// }
// sc := env.Loger.NewGroup(`StatCache`)
// defer sc.Free()
// if cstat {
// sc.Debug(`Method: Get, Query: %v`, cquery.Query())
// if link := caches.UseCache.Get(cquery); link != `` {
// env.Cache.Set(cquery.Query(), link, 3600)
// return &resp.Resp{Msg: cacheHIT, Data: link}
// }
// } else {
// sc.Debug(`Disabled`)
// }
// atomic.AddInt64(&reqnum, 1)
// // 解析歌曲外链
// outlink, emsg := sources.UseSource.GetLink(cquery)
// if emsg != `` {
// if emsg == sources.Err_Verify { // Verify Failed: 不支持的平台或音质
// return &resp.Resp{Code: 6, Msg: ztool.Str_FastConcat(emsg, `: 不支持的平台或音质`)}
// }
// env.Cache.Set(cquery.Query(), outlink, 600) // 发生错误的10分钟内禁止再次查询
// return &resp.Resp{Code: 2, Msg: emsg, Data: outlink}
// }
// atomic.AddInt64(&secnum, 1)
// // 缓存并获取直链 !(s == `kg` || (s == `tx` && !tx_en)) => (s != `kg` && (s != `tx` || tx_en))
// if outlink != `` && cstat && cquery.Source != sources.S_kg && (cquery.Source != sources.S_tx || env.Config.Custom.Tx_Enable) {
// sc.Debug(`Method: Set, Link: %v`, outlink)
// if link := caches.UseCache.Set(cquery, outlink); link != `` {
// env.Cache.Set(cquery.Query(), link, 3600)
// return &resp.Resp{Msg: cacheSet, Data: link}
// }
// }
// // 无法获取直链 直接返回原链接
// env.Cache.Set(cquery.Query(), outlink, 1200)
// return &resp.Resp{Msg: cacheMISS, Data: outlink}
// })
// }

View File

@ -1,239 +1,288 @@
// 内置解析源
package builtin
import (
"lx-source/src/caches"
"lx-source/src/env"
"lx-source/src/sources"
"lx-source/src/sources/custom/kw"
"lx-source/src/sources/custom/tx"
"net/http"
"strconv"
"sync"
"time"
// import (
// "lx-source/src/caches"
// "lx-source/src/env"
// "lx-source/src/sources"
// "lx-source/src/sources/custom/kg"
// "lx-source/src/sources/custom/kw"
// "lx-source/src/sources/custom/mg"
// "lx-source/src/sources/custom/tx"
// "lx-source/src/sources/custom/wy"
// wm "lx-source/src/sources/custom/wy/modules"
// "lx-source/src/sources/example"
// "net/http"
// "strconv"
// "sync"
// "time"
"github.com/ZxwyWebSite/ztool"
)
// "github.com/ZxwyWebSite/ztool"
// )
type Source struct{}
// type Source struct{}
// 预检 (兼容旧接口)
func (s *Source) Verify(c *caches.Query) (rquery string, ok bool) {
rquery, ok = qualitys[c.Quality][c.Source]
return
}
// // 预检 (兼容旧接口)
// func (s *Source) Verify(c *caches.Query) (rquery string, ok bool) {
// rquery, ok = qualitys[c.Quality][c.Source]
// return
// }
var (
// 并发对象池 (用户限制在Router处实现)
wy_pool = &sync.Pool{New: func() any { return new(WyApi_Song) }}
mg_pool = &sync.Pool{New: func() any { return new(MgApi_Song) }}
// kw_pool = &sync.Pool{New: func() any { return new(KwApi_Song) }}
kg_pool = &sync.Pool{New: func() any { return new(KgApi_Song) }}
// tx_pool = &sync.Pool{New: func() any { return new(res_tx) }}
wv_pool *sync.Pool
)
// var (
// // 并发对象池 (用户限制在Router处实现)
// wy_pool *sync.Pool
// mg_pool = &sync.Pool{New: func() any { return new(MgApi_Song) }}
// // kw_pool = &sync.Pool{New: func() any { return new(KwApi_Song) }}
// // kg_pool = &sync.Pool{New: func() any { return new(KgApi_Song) }}
// // tx_pool = &sync.Pool{New: func() any { return new(res_tx) }}
// wv_pool *sync.Pool
// )
func init() {
env.Inits.Add(func() {
if env.Config.Source.MusicIdVerify {
wv_pool = &sync.Pool{New: func() any { return new(WyApi_Vef) }}
}
})
}
// func init() {
// env.Inits.Add(func() {
// if env.Config.Source.Enable_Wy {
// wy_pool = &sync.Pool{New: func() any { return new(wm.PlayInfo) }}
// if env.Config.Source.MusicIdVerify {
// wv_pool = &sync.Pool{New: func() any { return new(wm.VerifyInfo) }}
// }
// }
// })
// }
// 查询
func (s *Source) GetLink(c *caches.Query) (outlink string, msg string) {
rquery, ok := s.Verify(c)
if !ok /*&& c.Source != `tx`*/ {
msg = sources.Err_Verify //`Verify Failed`
return
}
// var outlink string
jx := env.Loger.NewGroup(`Sources`) //sources.Loger.AppGroup(`builtin`) //env.Loger.NewGroup(`JieXiApis`)
switch c.Source {
case sources.S_wy:
if !env.Config.Custom.Wy_Enable {
msg = sources.ErrDisable
return
}
// 可用性验证
if env.Config.Source.MusicIdVerify {
vef := wv_pool.Get().(*WyApi_Vef)
defer wv_pool.Put(vef)
vurl := ztool.Str_FastConcat(`https://`, vef_wy, `&id=`, c.MusicID)
_, err := ztool.Net_HttpReq(http.MethodGet, vurl, nil, header_wy, &vef)
if err != nil {
jx.Error(`Wy, VefReq: %s`, err)
msg = sources.ErrHttpReq
return
}
jx.Debug(`Wy, Vef: %+v`, vef)
if vef.Code != 200 || !vef.Success {
msg = ztool.Str_FastConcat(`暂不可用:`, vef.Message)
return
}
}
// 获取外链
resp := wy_pool.Get().(*WyApi_Song)
defer wy_pool.Put(resp)
// 分流逻辑 (暂无其它节点)
// urls := [...]string{
// ztool.Str_FastConcat(`http://`, api_wy, `?id=`, c.MusicID, `&level=`, rquery, `&noCookie=true`),
// ztool.Str_FastConcat(`https://`, api_wy, `&id=`, c.MusicID, `&level=`, rquery, `&encodeType=`, c.Extname),
// }
// url := urls[rand.Intn(len(urls))]
url := ztool.Str_FastConcat(
`https://`, api_wy, `&id=`, c.MusicID, `&level=`, rquery,
`&timestamp=`, strconv.FormatInt(time.Now().UnixMilli(), 10),
)
// jx.Debug(`Wy, Url: %v`, url)
// wy源增加后端重试 默认3次
for i := 0; true; i++ {
_, err := ztool.Net_HttpReq(http.MethodGet, url, nil, header_wy, &resp)
if err != nil {
jx.Error(`HttpReq, Err: %s, ReTry: %v`, err, i)
if i > 3 {
jx.Error(`Wy, HttpReq: %s`, err)
msg = sources.ErrHttpReq
return
}
time.Sleep(time.Second)
continue
}
break
}
jx.Debug(`Wy, Resp: %+v`, resp)
if len(resp.Data) == 0 {
msg = `No DataApi接口忙请稍后重试`
return
}
var data = resp.Data[0]
if data.Code != 200 || data.FreeTrialInfo != nil {
// jx.Error("发生错误, 返回数据:\n%#v", resp)
msg = `触发风控或专辑单独收费`
return
}
if data.Level != rquery {
msg = ztool.Str_FastConcat(`实际音质不匹配: `, rquery, ` <= `, data.Level) // 实际音质不匹配: exhigh <= standard
return
}
// jx.Info(`WyLink, RealQuality: %v`, data.Level)
outlink = data.URL
case sources.S_mg:
resp := mg_pool.Get().(*MgApi_Song)
defer mg_pool.Put(resp)
// // 查询
// func (s *Source) GetLink(c *caches.Query) (outlink string, msg string) {
// rquery, ok := s.Verify(c)
// if !ok /*&& c.Source != `tx`*/ {
// msg = sources.Err_Verify //`Verify Failed`
// return
// }
// // var outlink string
// jx := env.Loger.NewGroup(`Sources`) //sources.Loger.AppGroup(`builtin`) //env.Loger.NewGroup(`JieXiApis`)
// defer jx.Free()
// switch c.Source {
// case sources.S_wy:
// if !env.Config.Source.Enable_Wy {
// msg = sources.ErrDisable
// return
// }
// if wy.Url != nil {
// outlink, msg = wy.Url(c.MusicID, c.Quality)
// break
// }
// // 可用性验证
// if env.Config.Source.MusicIdVerify {
// vef := wv_pool.Get().(*wm.VerifyInfo)
// defer wv_pool.Put(vef)
// vurl := ztool.Str_FastConcat(`https://`, example.Vef_wy, `&id=`, c.MusicID)
// _, err := ztool.Net_HttpReq(http.MethodGet, vurl, nil, example.Header_wy, &vef)
// if err != nil {
// jx.Error(`Wy, VefReq: %s`, err)
// msg = sources.ErrHttpReq
// return
// }
// jx.Debug(`Wy, Vef: %+v`, vef)
// if vef.Code != 200 || !vef.Success {
// msg = ztool.Str_FastConcat(`暂不可用:`, vef.Message)
// return
// }
// }
// // 获取外链
// resp := wy_pool.Get().(*wm.PlayInfo)
// defer wy_pool.Put(resp)
// // 分流逻辑 (暂无其它节点)
// // urls := [...]string{
// // ztool.Str_FastConcat(`http://`, api_wy, `?id=`, c.MusicID, `&level=`, rquery, `&noCookie=true`),
// // ztool.Str_FastConcat(`https://`, api_wy, `&id=`, c.MusicID, `&level=`, rquery, `&encodeType=`, c.Extname),
// // }
// // url := urls[rand.Intn(len(urls))]
// url := ztool.Str_FastConcat(
// `https://`, example.Api_wy, `&id=`, c.MusicID, `&level=`, rquery,
// `&timestamp=`, strconv.FormatInt(time.Now().UnixMilli(), 10),
// )
// // jx.Debug(`Wy, Url: %v`, url)
// // wy源增加后端重试 默认3次
// for i := 0; true; i++ {
// _, err := ztool.Net_HttpReq(http.MethodGet, url, nil, example.Header_wy, &resp)
// if err != nil {
// jx.Error(`HttpReq, Err: %s, ReTry: %v`, err, i)
// if i > 3 {
// jx.Error(`Wy, HttpReq: %s`, err)
// msg = sources.ErrHttpReq
// return
// }
// time.Sleep(time.Second)
// continue
// }
// break
// }
// jx.Debug(`Wy, Resp: %+v`, resp)
// if len(resp.Data) == 0 {
// msg = `No DataApi接口忙请稍后重试`
// return
// }
// var data = resp.Data[0]
// if data.Code != 200 || data.FreeTrialInfo != nil {
// // jx.Error("发生错误, 返回数据:\n%#v", resp)
// msg = `触发风控或专辑单独收费: ` + strconv.Itoa(data.Code)
// return
// }
// if data.Level != rquery {
// msg = ztool.Str_FastConcat(`实际音质不匹配: `, rquery, ` <= `, data.Level) // 实际音质不匹配: exhigh <= standard
// if !env.Config.Source.ForceFallback {
// return
// }
// }
// // jx.Info(`WyLink, RealQuality: %v`, data.Level)
// outlink = data.URL
// case sources.S_mg:
// if !env.Config.Source.Enable_Mg {
// msg = sources.ErrDisable
// return
// }
// if len(c.MusicID) != 11 {
// msg = sources.E_VefMusicId
// return
// }
// if mg.Url != nil {
// outlink, msg = mg.Url(c.MusicID, c.Quality)
// break
// }
// resp := mg_pool.Get().(*MgApi_Song)
// defer mg_pool.Put(resp)
url := ztool.Str_FastConcat(`https://`, api_mg, `?copyrightId=`, c.MusicID, `&type=`, rquery)
// jx.Debug(`Mg, Url: %v`, url)
_, err := ztool.Net_HttpReq(http.MethodGet, url, nil, header_mg, &resp)
if err != nil {
jx.Error(`Mg, HttpReq: %s`, err)
msg = sources.ErrHttpReq
return
}
jx.Debug(`Mg, Resp: %+v`, resp)
if link := resp.Data.PlayURL; link != `` {
outlink = `https:` + link
} // else {
// jx.Debug(`Mg, Err: %#v`, resp)
// }
case sources.S_kw:
if !env.Config.Custom.Kw_Enable {
msg = sources.ErrDisable
return
}
ourl, emsg := kw.Url(c.MusicID, c.Quality)
if emsg != `` {
msg = emsg
return
}
outlink = ourl
case sources.S_kg:
resp := kg_pool.Get().(*KgApi_Song)
defer kg_pool.Put(resp)
// url := ztool.Str_FastConcat(`https://`, example.Api_mg, `?copyrightId=`, c.MusicID, `&type=`, rquery)
// // jx.Debug(`Mg, Url: %v`, url)
// _, err := ztool.Net_HttpReq(http.MethodGet, url, nil, example.Header_mg, &resp)
// if err != nil {
// jx.Error(`Mg, HttpReq: %s`, err)
// msg = sources.ErrHttpReq
// return
// }
// jx.Debug(`Mg, Resp: %+v`, resp)
// if link := resp.Data.PlayURL; link != `` {
// outlink = `https:` + link
// } else {
// msg = ztool.Str_FastConcat(resp.Code, `: `, resp.Msg)
// }
// case sources.S_kw:
// if !env.Config.Source.Enable_Kw {
// msg = sources.ErrDisable
// return
// }
// outlink, msg = kw.Url(c.MusicID, c.Quality)
// // if emsg != `` {
// // msg = emsg
// // return
// // }
// // outlink = ourl
// case sources.S_kg:
// if !env.Config.Source.Enable_Kg {
// msg = sources.ErrDisable
// return
// }
// if len(c.MusicID) != 32 {
// msg = sources.E_VefMusicId
// return
// }
// outlink, msg = kg.Url(c.MusicID, c.Quality)
// // if emsg != `` {
// // msg = emsg
// // return
// // }
// // outlink = ourl
// // case sources.S_kg:
// // if !env.Config.Custom.Kg_Enable {
// // msg = sources.ErrDisable
// // return
// // }
// // resp := kg_pool.Get().(*KgApi_Song)
// // defer kg_pool.Put(resp)
// sep := strings.Split(c.MusicID, `-`) // 分割 Hash-Album 如 6DC276334F56E22BE2A0E8254D332B45-13097991
// alb := func() string {
// if len(sep) >= 2 {
// return sep[1]
// }
// return ``
// }()
sep := c.Split()
url := ztool.Str_FastConcat(api_kg, `&hash=`, sep[0], `&album_id=`, sep[1], `&_=`, strconv.FormatInt(time.Now().UnixMilli(), 10))
// jx.Debug(`Kg, Url: %s`, url)
_, err := ztool.Net_HttpReq(http.MethodGet, url, nil, nil, &resp)
if err != nil {
jx.Error(`Kg, HttpReq: %s`, err)
msg = sources.ErrHttpReq
return
}
jx.Debug(`Kg, Resp: %+v`, resp)
if resp.ErrCode != 0 {
msg = ztool.Str_FastConcat(`Error: `, strconv.Itoa(resp.ErrCode))
return
}
var data KgApi_Data
err = ztool.Val_MapToStruct(resp.Data, &data)
if err != nil {
msg = err.Error()
return
}
if data.PlayBackupURL == `` {
if data.PlayURL == `` {
msg = sources.ErrNoLink
return
}
outlink = data.PlayURL
}
outlink = data.PlayBackupURL
case sources.S_tx:
// sep := c.Split()
if len(c.MusicID) != 14 {
msg = sources.E_VefMusicId
return
}
ourl, emsg := tx.Url(c.MusicID, c.Quality)
if emsg != `` {
msg = emsg
return
}
outlink = ourl
// case `otx`:
// resp := tx_pool.Get().(*res_tx)
// defer tx_pool.Put(resp)
// // // sep := strings.Split(c.MusicID, `-`) // 分割 Hash-Album 如 6DC276334F56E22BE2A0E8254D332B45-13097991
// // // alb := func() string {
// // // if len(sep) >= 2 {
// // // return sep[1]
// // // }
// // // return ``
// // // }()
// // sep := c.Split()
// // url := ztool.Str_FastConcat(api_kg, `&hash=`, sep[0], `&album_id=`, sep[1], `&_=`, strconv.FormatInt(time.Now().UnixMilli(), 10))
// // // jx.Debug(`Kg, Url: %s`, url)
// // _, err := ztool.Net_HttpReq(http.MethodGet, url, nil, nil, &resp)
// // if err != nil {
// // jx.Error(`Kg, HttpReq: %s`, err)
// // msg = sources.ErrHttpReq
// // return
// // }
// // jx.Debug(`Kg, Resp: %+v`, resp)
// // if resp.ErrCode != 0 {
// // msg = ztool.Str_FastConcat(`Error: `, strconv.Itoa(resp.ErrCode))
// // return
// // }
// // var data KgApi_Data
// // err = ztool.Val_MapToStruct(resp.Data, &data)
// // if err != nil {
// // msg = err.Error()
// // return
// // }
// // if data.PlayBackupURL == `` {
// // if data.PlayURL == `` {
// // msg = sources.ErrNoLink
// // return
// // }
// // outlink = data.PlayURL
// // }
// // outlink = data.PlayBackupURL
// case sources.S_tx:
// if !env.Config.Source.Enable_Tx {
// msg = sources.ErrDisable
// return
// }
// if len(c.MusicID) != 14 {
// msg = sources.E_VefMusicId
// return
// }
// outlink, msg = tx.Url(c.MusicID, c.Quality)
// // if emsg != `` {
// // msg = emsg
// // return
// // }
// // outlink = ourl
// // case `otx`:
// // resp := tx_pool.Get().(*res_tx)
// // defer tx_pool.Put(resp)
// sep := c.Split()
// url := ztool.Str_FastConcat(api_tx,
// `{"comm":{"ct":24,"cv":0,"format":"json","uin":"10086"},"req":{"method":"GetCdnDispatch","module":"CDN.SrfCdnDispatchServer","param":{"calltype":0,"guid":"1535153710","userip":""}},"req_0":{"method":"CgiGetVkey","module":"vkey.GetVkeyServer","param":{`,
// func(s string) string {
// if s == `` {
// return ``
// }
// return ztool.Str_FastConcat(`"filename":["`, rquery, s, `.`, c.Extname, `"],`)
// }(sep[1]),
// `"guid":"1535153710","loginflag":1,"platform":"20","songmid":["`, sep[0], `"],"songtype":[0],"uin":"10086"}}}`,
// )
// // jx.Debug(`Tx, Url: %s`, url)
// out, err := ztool.Net_HttpReq(http.MethodGet, url, nil, header_tx, &resp)
// if err != nil {
// jx.Error(`Tx, HttpReq: %s`, err)
// msg = errHttpReq
// return
// }
// jx.Debug(`Tx, Resp: %s`, out)
// if resp.Code != 0 {
// msg = ztool.Str_FastConcat(`Error: `, strconv.Itoa(resp.Code))
// return
// }
// if resp.Req0.Data.Midurlinfo[0].Purl == `` {
// msg = errNoLink
// return
// }
// outlink = ztool.Str_FastConcat(`https://dl.stream.qqmusic.qq.com/`, resp.Req0.Data.Midurlinfo[0].Purl)
default:
msg = `不支持的平台`
return
}
return
}
// // sep := c.Split()
// // url := ztool.Str_FastConcat(api_tx,
// // `{"comm":{"ct":24,"cv":0,"format":"json","uin":"10086"},"req":{"method":"GetCdnDispatch","module":"CDN.SrfCdnDispatchServer","param":{"calltype":0,"guid":"1535153710","userip":""}},"req_0":{"method":"CgiGetVkey","module":"vkey.GetVkeyServer","param":{`,
// // func(s string) string {
// // if s == `` {
// // return ``
// // }
// // return ztool.Str_FastConcat(`"filename":["`, rquery, s, `.`, c.Extname, `"],`)
// // }(sep[1]),
// // `"guid":"1535153710","loginflag":1,"platform":"20","songmid":["`, sep[0], `"],"songtype":[0],"uin":"10086"}}}`,
// // )
// // // jx.Debug(`Tx, Url: %s`, url)
// // out, err := ztool.Net_HttpReq(http.MethodGet, url, nil, header_tx, &resp)
// // if err != nil {
// // jx.Error(`Tx, HttpReq: %s`, err)
// // msg = errHttpReq
// // return
// // }
// // jx.Debug(`Tx, Resp: %s`, out)
// // if resp.Code != 0 {
// // msg = ztool.Str_FastConcat(`Error: `, strconv.Itoa(resp.Code))
// // return
// // }
// // if resp.Req0.Data.Midurlinfo[0].Purl == `` {
// // msg = errNoLink
// // return
// // }
// // outlink = ztool.Str_FastConcat(`https://dl.stream.qqmusic.qq.com/`, resp.Req0.Data.Midurlinfo[0].Purl)
// default:
// msg = `不支持的平台`
// return
// }
// return
// }

View File

@ -1,293 +1,274 @@
package builtin
import (
"lx-source/src/sources"
"github.com/ZxwyWebSite/ztool"
)
type (
// 网易音乐接口 (方格/简繁)
WyApi_Song struct {
Data []struct {
ID int `json:"id"`
URL string `json:"url"`
Br int `json:"br"`
Size int `json:"size"`
Md5 string `json:"md5"`
Code int `json:"code"`
Expi int `json:"expi"`
Type string `json:"type"`
Gain float64 `json:"gain"`
Peak float64 `json:"peak"`
Fee int `json:"fee"`
Uf interface{} `json:"uf"`
Payed int `json:"payed"`
Flag int `json:"flag"`
CanExtend bool `json:"canExtend"`
FreeTrialInfo interface{} `json:"freeTrialInfo"`
Level string `json:"level"`
EncodeType string `json:"encodeType"`
FreeTrialPrivilege struct {
ResConsumable bool `json:"resConsumable"`
UserConsumable bool `json:"userConsumable"`
ListenType interface{} `json:"listenType"`
CannotListenReason interface{} `json:"cannotListenReason"`
PlayReason interface{} `json:"playReason"`
} `json:"freeTrialPrivilege"`
FreeTimeTrialPrivilege struct {
ResConsumable bool `json:"resConsumable"`
UserConsumable bool `json:"userConsumable"`
Type int `json:"type"`
RemainTime int `json:"remainTime"`
} `json:"freeTimeTrialPrivilege"`
URLSource int `json:"urlSource"`
RightSource int `json:"rightSource"`
PodcastCtrp interface{} `json:"podcastCtrp"`
EffectTypes interface{} `json:"effectTypes"`
Time int `json:"time"`
} `json:"data"`
Code int `json:"code"`
}
WyApi_Vef struct {
Code int16 `json:"code"`
Success bool `json:"success"`
Message string `json:"message"`
}
// 咪咕音乐接口
MgApi_Song struct {
Code string `json:"code"`
Msg string `json:"msg"`
Data struct {
PlayURL string `json:"playUrl"`
FormatID string `json:"formatId"`
SalePrice string `json:"salePrice"`
BizType string `json:"bizType"`
BizCode string `json:"bizCode"`
AuditionsLength int `json:"auditionsLength"`
} `json:"data"`
}
// 酷我音乐接口 (波点)
// KwApi_Song struct {
// Code int `json:"code"`
// Msg string `json:"msg"`
// ReqID string `json:"reqId"`
// Data struct {
// Duration int `json:"duration"`
// AudioInfo struct {
// Bitrate string `json:"bitrate"`
// Format string `json:"format"`
// Level string `json:"level"`
// Size string `json:"size"`
// } `json:"audioInfo"`
// URL string `json:"url"`
// } `json:"data"`
// ProfileID string `json:"profileId"`
// CurTime int64 `json:"curTime"`
// }
// 酷狗试听接口
KgApi_Song struct {
Status int `json:"status"`
ErrCode int `json:"err_code"`
Data any `json:"data"`
}
KgApi_Data struct {
// Hash string `json:"hash"`
// Timelength int `json:"timelength"`
// Filesize int `json:"filesize"`
// AudioName string `json:"audio_name"`
// HaveAlbum int `json:"have_album"`
// AlbumName string `json:"album_name"`
// AlbumID any `json:"album_id"`
// Img string `json:"img"`
// HaveMv int `json:"have_mv"`
// VideoID any `json:"video_id"`
// AuthorName string `json:"author_name"`
// SongName string `json:"song_name"`
// Lyrics string `json:"lyrics"`
// AuthorID any `json:"author_id"`
// Privilege int `json:"privilege"`
// Privilege2 string `json:"privilege2"`
PlayURL string `json:"play_url"`
// Authors []struct {
// AuthorID any `json:"author_id"`
// AuthorName string `json:"author_name"`
// IsPublish string `json:"is_publish"`
// SizableAvatar string `json:"sizable_avatar"`
// EAuthorID string `json:"e_author_id"`
// Avatar string `json:"avatar"`
// } `json:"authors"`
// IsFreePart int `json:"is_free_part"`
// Bitrate int `json:"bitrate"`
// RecommendAlbumID string `json:"recommend_album_id"`
// StoreType string `json:"store_type"`
// AlbumAudioID int `json:"album_audio_id"`
// IsPublish int `json:"is_publish"`
// EAuthorID string `json:"e_author_id"`
// AudioID any `json:"audio_id"`
// HasPrivilege bool `json:"has_privilege"`
PlayBackupURL string `json:"play_backup_url"`
// SmallLibrarySong int `json:"small_library_song"`
// EncodeAlbumID string `json:"encode_album_id"`
// EncodeAlbumAudioID string `json:"encode_album_audio_id"`
// EVideoID string `json:"e_video_id"`
}
// 腾讯试听接口
// res_tx struct {
// Code int `json:"code"`
// // Ts int64 `json:"ts"`
// // StartTs int64 `json:"start_ts"`
// // Traceid string `json:"traceid"`
// // Req struct {
// // Code int `json:"code"`
// // Data struct {
// // Expiration int `json:"expiration"`
// // Freeflowsip []string `json:"freeflowsip"`
// // Keepalivefile string `json:"keepalivefile"`
// // Msg string `json:"msg"`
// // Retcode int `json:"retcode"`
// // Servercheck string `json:"servercheck"`
// // Sip []string `json:"sip"`
// // Testfile2G string `json:"testfile2g"`
// // Testfilewifi string `json:"testfilewifi"`
// // Uin string `json:"uin"`
// // Userip string `json:"userip"`
// // Vkey string `json:"vkey"`
// // } `json:"data"`
// // } `json:"req"`
// Req0 struct {
// Code int `json:"code"`
// Data struct {
// // Uin string `json:"uin"`
// // Retcode int `json:"retcode"`
// // VerifyType int `json:"verify_type"`
// // LoginKey string `json:"login_key"`
// // Msg string `json:"msg"`
// // Sip []string `json:"sip"`
// // Thirdip []string `json:"thirdip"`
// // Testfile2G string `json:"testfile2g"`
// // Testfilewifi string `json:"testfilewifi"`
// Midurlinfo []struct {
// // Songmid string `json:"songmid"`
// // Filename string `json:"filename"`
// Purl string `json:"purl"`
// // Errtype string `json:"errtype"`
// // P2Pfromtag int `json:"p2pfromtag"`
// // Qmdlfromtag int `json:"qmdlfromtag"`
// // CommonDownfromtag int `json:"common_downfromtag"`
// // VipDownfromtag int `json:"vip_downfromtag"`
// // Pdl int `json:"pdl"`
// // Premain int `json:"premain"`
// // Hisdown int `json:"hisdown"`
// // Hisbuy int `json:"hisbuy"`
// // UIAlert int `json:"uiAlert"`
// // Isbuy int `json:"isbuy"`
// // Pneedbuy int `json:"pneedbuy"`
// // Pneed int `json:"pneed"`
// // Isonly int `json:"isonly"`
// // Onecan int `json:"onecan"`
// // Result int `json:"result"`
// // Tips string `json:"tips"`
// // Opi48Kurl string `json:"opi48kurl"`
// // Opi96Kurl string `json:"opi96kurl"`
// // Opi192Kurl string `json:"opi192kurl"`
// // Opiflackurl string `json:"opiflackurl"`
// // Opi128Kurl string `json:"opi128kurl"`
// // Opi192Koggurl string `json:"opi192koggurl"`
// // Wififromtag string `json:"wififromtag"`
// // Flowfromtag string `json:"flowfromtag"`
// // Wifiurl string `json:"wifiurl"`
// // Flowurl string `json:"flowurl"`
// // Vkey string `json:"vkey"`
// // Opi30Surl string `json:"opi30surl"`
// // Ekey string `json:"ekey"`
// // AuthSwitch int `json:"auth_switch"`
// // Subcode int `json:"subcode"`
// // Opi96Koggurl string `json:"opi96koggurl"`
// // AuthSwitch2 int `json:"auth_switch2"`
// } `json:"midurlinfo"`
// // Servercheck string `json:"servercheck"`
// // Expiration int `json:"expiration"`
// } `json:"data"`
// } `json:"req_0"`
// }
)
// const (
// // Source
// s_wy = `wy`
// s_mg = `mg`
// s_kw = `kw`
// s_kg = `kg`
// s_tx = `tx`
// // s_lx = `lx`
// import (
// "lx-source/src/sources"
// )
var (
// 音质列表 ( [通用音质][音乐平台]对应音质 )
/*
: kg源使用对应hash匹配音质故为空
*/
qualitys = map[string]map[string]string{
sources.Q_128k: {
sources.S_wy: `standard`,
sources.S_mg: `1`,
sources.S_kw: sources.Q_128k,
sources.S_kg: ``,
sources.S_tx: `M500`,
},
sources.Q_320k: {
sources.S_wy: `exhigh`,
sources.S_mg: `2`,
sources.S_kw: sources.Q_320k,
// sources.S_kg: ``,
sources.S_tx: `M800`,
},
sources.Q_flac: {
sources.S_wy: `lossless`,
sources.S_mg: `3`,
sources.S_kw: `2000k`,
sources.S_tx: `F000`,
},
sources.Q_fl24: {
sources.S_wy: `hires`,
sources.S_mg: `4`,
// sources.S_tx: `RS01`,
},
// `fl24`: {
// s_wy: `hires`,
// s_mg: `4`,
// },
}
// ApiAddr
api_wy string
api_mg string
// api_kw string
api_kg string = `https://wwwapi.kugou.com/yy/index.php?r=play/getdata&platid=4&mid=1`
// api_tx string = `https://u.y.qq.com/cgi-bin/musicu.fcg?data=`
vef_wy string
// Headers
header_wy map[string]string
header_mg map[string]string
// header_kw map[string]string
// header_tx = map[string]string{`Referer`: `https://y.qq.com/`}
)
// type (
// // 网易音乐接口 (方格/简繁)
// // WyApi_Song struct {
// // Data []struct {
// // ID int `json:"id"`
// // URL string `json:"url"`
// // Br int `json:"br"`
// // Size int `json:"size"`
// // Md5 string `json:"md5"`
// // Code int `json:"code"`
// // Expi int `json:"expi"`
// // Type string `json:"type"`
// // Gain float64 `json:"gain"`
// // Peak float64 `json:"peak"`
// // Fee int `json:"fee"`
// // Uf interface{} `json:"uf"`
// // Payed int `json:"payed"`
// // Flag int `json:"flag"`
// // CanExtend bool `json:"canExtend"`
// // FreeTrialInfo interface{} `json:"freeTrialInfo"`
// // Level string `json:"level"`
// // EncodeType string `json:"encodeType"`
// // FreeTrialPrivilege struct {
// // ResConsumable bool `json:"resConsumable"`
// // UserConsumable bool `json:"userConsumable"`
// // ListenType interface{} `json:"listenType"`
// // CannotListenReason interface{} `json:"cannotListenReason"`
// // PlayReason interface{} `json:"playReason"`
// // } `json:"freeTrialPrivilege"`
// // FreeTimeTrialPrivilege struct {
// // ResConsumable bool `json:"resConsumable"`
// // UserConsumable bool `json:"userConsumable"`
// // Type int `json:"type"`
// // RemainTime int `json:"remainTime"`
// // } `json:"freeTimeTrialPrivilege"`
// // URLSource int `json:"urlSource"`
// // RightSource int `json:"rightSource"`
// // PodcastCtrp interface{} `json:"podcastCtrp"`
// // EffectTypes interface{} `json:"effectTypes"`
// // Time int `json:"time"`
// // } `json:"data"`
// // Code int `json:"code"`
// // }
// // WyApi_Vef struct {
// // Code int16 `json:"code"`
// // Success bool `json:"success"`
// // Message string `json:"message"`
// // }
// // 咪咕音乐接口
// MgApi_Song struct {
// Code string `json:"code"`
// Msg string `json:"msg"`
// Data struct {
// PlayURL string `json:"playUrl"`
// FormatID string `json:"formatId"`
// SalePrice string `json:"salePrice"`
// BizType string `json:"bizType"`
// BizCode string `json:"bizCode"`
// AuditionsLength int `json:"auditionsLength"`
// } `json:"data"`
// }
// // 酷我音乐接口 (波点)
// // KwApi_Song struct {
// // Code int `json:"code"`
// // Msg string `json:"msg"`
// // ReqID string `json:"reqId"`
// // Data struct {
// // Duration int `json:"duration"`
// // AudioInfo struct {
// // Bitrate string `json:"bitrate"`
// // Format string `json:"format"`
// // Level string `json:"level"`
// // Size string `json:"size"`
// // } `json:"audioInfo"`
// // URL string `json:"url"`
// // } `json:"data"`
// // ProfileID string `json:"profileId"`
// // CurTime int64 `json:"curTime"`
// // }
// // 酷狗试听接口
// // KgApi_Song struct {
// // Status int `json:"status"`
// // ErrCode int `json:"err_code"`
// // Data any `json:"data"`
// // }
// // KgApi_Data struct {
// // // Hash string `json:"hash"`
// // // Timelength int `json:"timelength"`
// // // Filesize int `json:"filesize"`
// // // AudioName string `json:"audio_name"`
// // // HaveAlbum int `json:"have_album"`
// // // AlbumName string `json:"album_name"`
// // // AlbumID any `json:"album_id"`
// // // Img string `json:"img"`
// // // HaveMv int `json:"have_mv"`
// // // VideoID any `json:"video_id"`
// // // AuthorName string `json:"author_name"`
// // // SongName string `json:"song_name"`
// // // Lyrics string `json:"lyrics"`
// // // AuthorID any `json:"author_id"`
// // // Privilege int `json:"privilege"`
// // // Privilege2 string `json:"privilege2"`
// // PlayURL string `json:"play_url"`
// // // Authors []struct {
// // // AuthorID any `json:"author_id"`
// // // AuthorName string `json:"author_name"`
// // // IsPublish string `json:"is_publish"`
// // // SizableAvatar string `json:"sizable_avatar"`
// // // EAuthorID string `json:"e_author_id"`
// // // Avatar string `json:"avatar"`
// // // } `json:"authors"`
// // // IsFreePart int `json:"is_free_part"`
// // // Bitrate int `json:"bitrate"`
// // // RecommendAlbumID string `json:"recommend_album_id"`
// // // StoreType string `json:"store_type"`
// // // AlbumAudioID int `json:"album_audio_id"`
// // // IsPublish int `json:"is_publish"`
// // // EAuthorID string `json:"e_author_id"`
// // // AudioID any `json:"audio_id"`
// // // HasPrivilege bool `json:"has_privilege"`
// // PlayBackupURL string `json:"play_backup_url"`
// // // SmallLibrarySong int `json:"small_library_song"`
// // // EncodeAlbumID string `json:"encode_album_id"`
// // // EncodeAlbumAudioID string `json:"encode_album_audio_id"`
// // // EVideoID string `json:"e_video_id"`
// // }
// // 腾讯试听接口
// // res_tx struct {
// // Code int `json:"code"`
// // // Ts int64 `json:"ts"`
// // // StartTs int64 `json:"start_ts"`
// // // Traceid string `json:"traceid"`
// // // Req struct {
// // // Code int `json:"code"`
// // // Data struct {
// // // Expiration int `json:"expiration"`
// // // Freeflowsip []string `json:"freeflowsip"`
// // // Keepalivefile string `json:"keepalivefile"`
// // // Msg string `json:"msg"`
// // // Retcode int `json:"retcode"`
// // // Servercheck string `json:"servercheck"`
// // // Sip []string `json:"sip"`
// // // Testfile2G string `json:"testfile2g"`
// // // Testfilewifi string `json:"testfilewifi"`
// // // Uin string `json:"uin"`
// // // Userip string `json:"userip"`
// // // Vkey string `json:"vkey"`
// // // } `json:"data"`
// // // } `json:"req"`
// // Req0 struct {
// // Code int `json:"code"`
// // Data struct {
// // // Uin string `json:"uin"`
// // // Retcode int `json:"retcode"`
// // // VerifyType int `json:"verify_type"`
// // // LoginKey string `json:"login_key"`
// // // Msg string `json:"msg"`
// // // Sip []string `json:"sip"`
// // // Thirdip []string `json:"thirdip"`
// // // Testfile2G string `json:"testfile2g"`
// // // Testfilewifi string `json:"testfilewifi"`
// // Midurlinfo []struct {
// // // Songmid string `json:"songmid"`
// // // Filename string `json:"filename"`
// // Purl string `json:"purl"`
// // // Errtype string `json:"errtype"`
// // // P2Pfromtag int `json:"p2pfromtag"`
// // // Qmdlfromtag int `json:"qmdlfromtag"`
// // // CommonDownfromtag int `json:"common_downfromtag"`
// // // VipDownfromtag int `json:"vip_downfromtag"`
// // // Pdl int `json:"pdl"`
// // // Premain int `json:"premain"`
// // // Hisdown int `json:"hisdown"`
// // // Hisbuy int `json:"hisbuy"`
// // // UIAlert int `json:"uiAlert"`
// // // Isbuy int `json:"isbuy"`
// // // Pneedbuy int `json:"pneedbuy"`
// // // Pneed int `json:"pneed"`
// // // Isonly int `json:"isonly"`
// // // Onecan int `json:"onecan"`
// // // Result int `json:"result"`
// // // Tips string `json:"tips"`
// // // Opi48Kurl string `json:"opi48kurl"`
// // // Opi96Kurl string `json:"opi96kurl"`
// // // Opi192Kurl string `json:"opi192kurl"`
// // // Opiflackurl string `json:"opiflackurl"`
// // // Opi128Kurl string `json:"opi128kurl"`
// // // Opi192Koggurl string `json:"opi192koggurl"`
// // // Wififromtag string `json:"wififromtag"`
// // // Flowfromtag string `json:"flowfromtag"`
// // // Wifiurl string `json:"wifiurl"`
// // // Flowurl string `json:"flowurl"`
// // // Vkey string `json:"vkey"`
// // // Opi30Surl string `json:"opi30surl"`
// // // Ekey string `json:"ekey"`
// // // AuthSwitch int `json:"auth_switch"`
// // // Subcode int `json:"subcode"`
// // // Opi96Koggurl string `json:"opi96koggurl"`
// // // AuthSwitch2 int `json:"auth_switch2"`
// // } `json:"midurlinfo"`
// // // Servercheck string `json:"servercheck"`
// // // Expiration int `json:"expiration"`
// // } `json:"data"`
// // } `json:"req_0"`
// // }
// )
func init() {
// InitBuiltInSource
var initdata = struct {
Api_Wy *string
Api_Mg *string
Vef_Wy *string
Header_Wy *map[string]string
Header_Mg *map[string]string
}{
Api_Wy: &api_wy,
Api_Mg: &api_mg,
Vef_Wy: &vef_wy,
Header_Wy: &header_wy,
Header_Mg: &header_mg,
}
data := []byte{0x4a, 0x7f, 0x3, 0x1, 0x2, 0xff, 0x80, 0x0, 0x1, 0x5, 0x1, 0x6, 0x41, 0x70, 0x69, 0x5f, 0x57, 0x79, 0x1, 0xc, 0x0, 0x1, 0x6, 0x41, 0x70, 0x69, 0x5f, 0x4d, 0x67, 0x1, 0xc, 0x0, 0x1, 0x6, 0x56, 0x65, 0x66, 0x5f, 0x57, 0x79, 0x1, 0xc, 0x0, 0x1, 0x9, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x57, 0x79, 0x1, 0xff, 0x82, 0x0, 0x1, 0x9, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x4d, 0x67, 0x1, 0xff, 0x82, 0x0, 0x0, 0x0, 0x21, 0xff, 0x81, 0x4, 0x1, 0x1, 0x11, 0x6d, 0x61, 0x70, 0x5b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5d, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x1, 0xff, 0x82, 0x0, 0x1, 0xc, 0x1, 0xc, 0x0, 0x0, 0xfe, 0x1, 0xca, 0xff, 0x80, 0x1, 0x23, 0x70, 0x74, 0x2e, 0x73, 0x61, 0x79, 0x71, 0x7a, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x3f, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x61, 0x70, 0x69, 0x53, 0x6f, 0x6e, 0x67, 0x55, 0x72, 0x6c, 0x56, 0x31, 0x1, 0x36, 0x6d, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x2e, 0x6d, 0x69, 0x67, 0x75, 0x2e, 0x63, 0x6e, 0x2f, 0x6d, 0x69, 0x67, 0x75, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x2f, 0x68, 0x35, 0x2f, 0x70, 0x6c, 0x61, 0x79, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2f, 0x67, 0x65, 0x74, 0x53, 0x6f, 0x6e, 0x67, 0x50, 0x6c, 0x61, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x1, 0x24, 0x70, 0x74, 0x2e, 0x73, 0x61, 0x79, 0x71, 0x7a, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x3f, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x63, 0x73, 0x6d, 0x43, 0x68, 0x65, 0x61, 0x6b, 0x4d, 0x75, 0x73, 0x69, 0x63, 0x1, 0x3, 0xa, 0x55, 0x73, 0x65, 0x72, 0x2d, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x5e, 0x4d, 0x6f, 0x7a, 0x69, 0x6c, 0x6c, 0x61, 0x2f, 0x35, 0x2e, 0x30, 0x20, 0x28, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x20, 0x4e, 0x54, 0x20, 0x36, 0x2e, 0x31, 0x3b, 0x20, 0x57, 0x4f, 0x57, 0x36, 0x34, 0x29, 0x20, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x57, 0x65, 0x62, 0x4b, 0x69, 0x74, 0x2f, 0x35, 0x33, 0x37, 0x2e, 0x33, 0x36, 0x20, 0x28, 0x4b, 0x48, 0x54, 0x4d, 0x4c, 0x2c, 0x20, 0x6c, 0x69, 0x6b, 0x65, 0x20, 0x47, 0x65, 0x63, 0x6b, 0x6f, 0x29, 0x20, 0x43, 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x2f, 0x35, 0x30, 0x2e, 0x30, 0x2e, 0x32, 0x36, 0x36, 0x31, 0x2e, 0x38, 0x37, 0x10, 0x58, 0x2d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x2d, 0x57, 0x69, 0x74, 0x68, 0xe, 0x58, 0x4d, 0x4c, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x7, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, 0x15, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x70, 0x74, 0x2e, 0x73, 0x61, 0x79, 0x71, 0x7a, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x1, 0x4, 0x6, 0x43, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x38, 0x53, 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x3d, 0x5a, 0x54, 0x49, 0x77, 0x4f, 0x44, 0x6b, 0x79, 0x4d, 0x44, 0x51, 0x74, 0x4f, 0x54, 0x45, 0x31, 0x4e, 0x53, 0x30, 0x30, 0x4d, 0x44, 0x68, 0x6c, 0x4c, 0x54, 0x68, 0x68, 0x4d, 0x57, 0x45, 0x74, 0x4d, 0x6a, 0x51, 0x30, 0x4e, 0x32, 0x59, 0x32, 0x4d, 0x7a, 0x6b, 0x32, 0x4f, 0x54, 0x41, 0x7a, 0x7, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, 0x1b, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x6d, 0x2e, 0x6d, 0x75, 0x73, 0x69, 0x63, 0x2e, 0x6d, 0x69, 0x67, 0x75, 0x2e, 0x63, 0x6e, 0x2f, 0x76, 0x34, 0x2f, 0x2, 0x42, 0x79, 0x20, 0x30, 0x34, 0x66, 0x38, 0x31, 0x34, 0x36, 0x31, 0x61, 0x39, 0x38, 0x63, 0x37, 0x61, 0x66, 0x35, 0x35, 0x37, 0x66, 0x65, 0x61, 0x33, 0x63, 0x66, 0x32, 0x38, 0x63, 0x34, 0x65, 0x61, 0x31, 0x35, 0x7, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x7, 0x30, 0x31, 0x34, 0x30, 0x30, 0x30, 0x44, 0x0}
ztool.Val_GobDecode(data, &initdata)
}
// // const (
// // // Source
// // s_wy = `wy`
// // s_mg = `mg`
// // s_kw = `kw`
// // s_kg = `kg`
// // s_tx = `tx`
// // // s_lx = `lx`
// // )
// var (
// // 音质列表 ( [通用音质][音乐平台]对应音质 )
// /*
// 注: kg源使用对应hash匹配音质故为空
// */
// qualitys = map[string]map[string]string{
// sources.Q_128k: {
// sources.S_wy: `standard`,
// sources.S_mg: `1`,
// sources.S_kw: sources.Q_128k,
// sources.S_kg: ``,
// sources.S_tx: `M500`,
// },
// sources.Q_320k: {
// sources.S_wy: `exhigh`,
// sources.S_mg: `2`,
// sources.S_kw: sources.Q_320k,
// sources.S_kg: ``,
// sources.S_tx: `M800`,
// },
// sources.Q_flac: {
// sources.S_wy: `lossless`,
// sources.S_mg: `3`,
// sources.S_kw: `2000k`,
// sources.S_kg: ``,
// sources.S_tx: `F000`,
// },
// sources.Q_fl24: {
// sources.S_wy: `hires`,
// sources.S_mg: `4`,
// sources.S_kg: ``,
// // sources.S_tx: `RS01`,
// },
// // `fl24`: {
// // s_wy: `hires`,
// // s_mg: `4`,
// // },
// }
// // ApiAddr
// // api_wy string
// // api_mg string
// // api_kw string
// // api_kg string = `https://wwwapi.kugou.com/yy/index.php?r=play/getdata&platid=4&mid=1`
// // api_tx string = `https://u.y.qq.com/cgi-bin/musicu.fcg?data=`
// // vef_wy string
// // Headers
// // header_wy map[string]string
// // header_mg map[string]string
// // header_kw map[string]string
// // header_tx = map[string]string{`Referer`: `https://y.qq.com/`}
// )

View File

@ -0,0 +1,3 @@
package custom
// type CSource struct{}

View File

@ -1,2 +1,134 @@
// 账号解析源
package custom
import (
"lx-source/src/env"
"lx-source/src/sources"
"lx-source/src/sources/custom/kg"
"lx-source/src/sources/custom/kw"
"lx-source/src/sources/custom/mg"
"lx-source/src/sources/custom/tx"
"lx-source/src/sources/custom/wy"
"strconv"
"strings"
)
type (
// 源定义
UrlFunc func(string, string) (string, string)
LrcFunc func(string) (string, string)
PicFunc func(string) (string, string)
VefFunc func(*string) bool
// 源接口
Source interface {
Url(string, string) (string, string) // 外链
Lrc(string) (string, string) // 歌词
Pic(string) (string, string) // 封面
Vef(*string) bool // 验证
Exp() int // 缓存
}
)
func notSupport(string) (string, string) { return ``, `不支持的平台或功能` }
// 接口封装
type WrapSource struct {
UrlFunc
LrcFunc
PicFunc
VefFunc
ExpData int
}
func (ws *WrapSource) Url(songMid, quality string) (string, string) {
return ws.UrlFunc(songMid, quality)
}
func (ws *WrapSource) Lrc(songMid string) (string, string) {
return ws.LrcFunc(songMid)
}
func (ws *WrapSource) Pic(songMid string) (string, string) {
return ws.PicFunc(songMid)
}
func (ws *WrapSource) Vef(songMid *string) bool {
return ws.VefFunc(songMid)
}
func (ws *WrapSource) Exp() int {
return ws.ExpData
}
var (
WySource Source
MgSource Source
KwSource Source
KgSource Source
TxSource Source
LxSource Source
)
func init() {
env.Inits.Add(func() {
if env.Config.Source.Enable_Wy {
WySource = &WrapSource{
UrlFunc: wy.Url,
LrcFunc: notSupport,
PicFunc: notSupport,
VefFunc: func(songMid *string) bool {
_, err := strconv.ParseUint(*songMid, 10, 0)
return err == nil
},
ExpData: sources.C_wy,
}
}
if env.Config.Source.Enable_Mg {
MgSource = &WrapSource{
UrlFunc: mg.Url,
LrcFunc: notSupport,
PicFunc: notSupport,
VefFunc: func(songMid *string) bool {
return len(*songMid) == 11
},
ExpData: sources.C_mg,
}
}
if env.Config.Source.Enable_Kw {
KwSource = &WrapSource{
UrlFunc: kw.Url,
LrcFunc: notSupport,
PicFunc: notSupport,
VefFunc: func(songMid *string) bool {
_, err := strconv.ParseUint(*songMid, 10, 0)
return err == nil
},
ExpData: sources.C_kw,
}
}
if env.Config.Source.Enable_Kg {
KgSource = &WrapSource{
UrlFunc: kg.Url,
LrcFunc: notSupport,
PicFunc: notSupport,
VefFunc: func(songMid *string) (ok bool) {
if ok = len(*songMid) == 32; ok {
*songMid = strings.ToUpper(*songMid)
}
return
},
ExpData: sources.C_kg,
}
}
if env.Config.Source.Enable_Tx {
TxSource = &WrapSource{
UrlFunc: tx.Url,
LrcFunc: notSupport,
PicFunc: notSupport,
VefFunc: func(songMid *string) bool {
return len(*songMid) == 14
},
ExpData: sources.C_tx,
}
}
if env.Config.Source.Enable_Lx {
LxSource = nil
}
})
}

View File

@ -0,0 +1,70 @@
package kg
import (
"encoding/gob"
"lx-source/src/env"
"lx-source/src/sources"
"net/http"
"strconv"
"strings"
"time"
"github.com/ZxwyWebSite/ztool"
)
func init() {
gob.Register(musicInfo{})
}
func getMusicInfo(hash_ string) (info musicInfo, emsg string) {
cquery := strings.Join([]string{`kg`, hash_, `info`}, `/`)
if cdata, ok := env.Cache.Get(cquery); ok {
if cinfo, ok := cdata.(musicInfo); ok {
info = cinfo
return
}
}
body := ztool.Str_FastConcat(
`{"area_code":"1","show_privilege":"1","show_album_info":"1","is_publish":"","appid":1005,"clientver":11451,"mid":"211008","dfid":"-","clienttime":"`,
strconv.FormatInt(time.Now().Unix(), 10),
`","key":"OIlwlieks28dk2k092lksi2UIkp","data":[{"hash":"`,
hash_,
`"}]}`,
)
var infoResp struct {
Status int `json:"status"`
ErrorCode int `json:"error_code"`
Errmsg string `json:"errmsg"`
Data [][]musicInfo `json:"data"`
}
err := ztool.Net_Request(
http.MethodPost,
`http://gateway.kugou.com/v3/album_audio/audio`,
strings.NewReader(body),
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeader(map[string]string{
`KG-THash`: `13a3164`,
`KG-RC`: `1`,
`KG-Fake`: `0`,
`KG-RF`: `00869891`,
`User-Agent`: `Android712-AndroidPhone-11451-376-0-FeeCacheUpdate-wifi`,
`x-router`: `kmr.service.kugou.com`,
})},
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&infoResp)},
)
if err != nil {
emsg = err.Error()
return
}
if len(infoResp.Data) == 0 {
if infoResp.Errmsg != `` {
emsg = infoResp.Errmsg
} else {
emsg = `No Data`
}
return
}
info = infoResp.Data[0][0]
emsg = infoResp.Errmsg
env.Cache.Set(cquery, info, sources.C_lx)
return
}

View File

@ -0,0 +1,104 @@
package kg
import (
"lx-source/src/env"
"lx-source/src/sources"
"net/http"
"strconv"
"strings"
"time"
)
func Url(songMid, quality string) (ourl, msg string) {
loger := env.Loger.NewGroup(`Kg`)
defer loger.Free()
rquality, ok := qualityMap[quality]
if !ok {
msg = sources.E_QNotSupport
return
}
info, emsg := getMusicInfo(strings.ToLower(songMid))
if emsg != `` {
loger.Error(`GetInfo: %v`, emsg)
msg = emsg
return
}
loger.Debug(`Info: %+v`, info)
var tHash string
switch quality {
case sources.Q_128k:
tHash = info.AudioInfo.Hash128
case sources.Q_320k:
tHash = info.AudioInfo.Hash320
case sources.Q_flac:
tHash = info.AudioInfo.HashFlac
case sources.Q_fl24:
tHash = info.AudioInfo.HashHigh
}
if tHash == `` {
msg = sources.E_QNotMatch
return
}
tHash = strings.ToLower(tHash)
now := time.Now()
params := map[string]string{
`album_id`: info.AlbumInfo.AlbumID,
`userid`: env.Config.Custom.Kg_userId,
`area_code`: `1`,
`hash`: tHash,
`module`: ``,
`mid`: mid,
`appid`: env.Config.Custom.Kg_Client_AppId,
`ssa_flag`: `is_fromtrack`,
`clientver`: env.Config.Custom.Kg_Client_Version,
`open_time`: now.Format(`20060102`),
`vipType`: `6`,
`ptype`: `0`,
`token`: env.Config.Custom.Kg_token,
`auth`: ``,
`mtype`: `0`,
`album_audio_id`: info.AlbumAudioID,
`behavior`: `play`,
`clienttime`: strconv.FormatInt(now.Unix(), 10),
`pid`: `2`,
`key`: getKey(tHash),
`dfid`: `-`,
`pidversion`: `3001`,
`quality`: rquality,
// `IsFreePart`: `1`,
}
if !env.Config.Custom.Kg_Enable {
params[`IsFreePart`] = `1` // 仅游客登录时允许获取试听
}
headers := map[string]string{
`User-Agent`: `Android712-AndroidPhone-8983-18-0-NetMusic-wifi`,
`KG-THash`: `3e5ec6b`,
`KG-Rec`: `1`,
`KG-RC`: `1`,
`x-router`: `tracker.kugou.com`,
}
var resp playInfo
err := signRequest(http.MethodGet, url, ``, params, headers, &resp)
if err != nil {
loger.Error(`Request: %s`, err)
msg = sources.ErrHttpReq
return
}
loger.Debug(`Resp: %+v`, resp)
switch resp.Status {
case 3:
msg = `该歌曲在酷狗没有版权,请换源播放`
case 2:
msg = `链接获取失败:请检查账号是否有会员或数字专辑是否购买`
}
if resp.Status != 1 {
if msg == `` {
msg = `链接获取失败可能是数字专辑或者api失效Status: ` + strconv.Itoa(resp.Status)
}
return
}
ourl = resp.URL[len(resp.URL)-1]
return
}

View File

@ -0,0 +1,265 @@
package kg
import (
"crypto/aes"
"crypto/cipher"
"errors"
"lx-source/src/env"
"math/rand"
"net/http"
"strconv"
"time"
"github.com/ZxwyWebSite/ztool"
"github.com/ZxwyWebSite/ztool/logs"
"github.com/ZxwyWebSite/ztool/x/bytesconv"
"github.com/ZxwyWebSite/ztool/x/json"
"github.com/ZxwyWebSite/ztool/zcypt"
)
// 通过TOP500榜单获取随机歌曲的mixsongmid
func randomMixSongMid() (mid string, err error) {
// 声明榜单url
const rankUrl = `http://mobilecdnbj.kugou.com/api/v3/rank/song?version=9108&ranktype=1&plat=0&pagesize=100&area_code=1&page=1&rankid=8888&with_res_tag=0&show_portrait_mv=1`
// 请求
var res rankInfo
err = ztool.Net_Request(
http.MethodGet,
rankUrl, nil,
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders()},
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&res)},
)
if err != nil {
return
}
// fmt.Printf("%#v\n", res)
if res.Status != 1 {
err = errors.New(res.Error)
return
}
// 随机选择一首歌曲
randomSong := res.Data.Info[rand.Intn(len(res.Data.Info))]
// fmt.Printf("%#v\n", randomSong)
// 因为排行榜api不会返回mixsongmid
// 所以需要进行一次搜索接口来获取
var body searchInfo
err = ztool.Net_Request(
http.MethodGet,
ztool.Str_FastConcat(
`https://songsearch.kugou.com/song_search_v2?`,
ztool.Net_Values(map[string]string{
`keyword`: randomSong.Filename,
`area_code`: `1`,
`page`: `1`,
`pagesize`: `1`,
`userid`: `0`,
`clientver`: ``,
`platform`: `WebFilter`,
`filter`: `2`,
`iscorrection`: `1`,
`privilege_filter`: `0`,
}),
), nil,
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(map[string]string{
`Referer`: `https://www.kugou.com`,
})},
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&body)},
)
if err != nil {
return
}
// fmt.Printf("%#v\n", body)
if body.Status != 1 {
err = errors.New(body.ErrorMsg)
return
}
if body.Data.Total == 0 || len(body.Data.Lists) == 0 {
err = errors.New(`歌曲搜索失败`)
return
}
mid = body.Data.Lists[0].MixSongID
return
}
// 签到主函数传入userinfo响应None就是成功报错即为不成功
func do_account_signin(loger *logs.Logger, now int64) (err error) {
// 时间检测
if now < env.Config.Custom.Kg_Lite_Interval {
loger.Debug(`Key未过期跳过...`)
return nil
}
// 检查用户配置文件获取mixsongmid
mixid := env.Config.Custom.Kg_Lite_MixId //`582534238`
if mixid == `auto` || mixid == `` {
for i := 0; true; i++ {
mixid, err = randomMixSongMid()
if err != nil {
loger.Error(`ReTry: %v, Err: %s`, i, err)
if i >= 2 {
return
}
time.Sleep(time.Second)
continue
}
break
}
// mixid, err = randomMixSongMid()
// if err != nil {
// return
// }
loger.Info(`成功获取MixSongMid: ` + mixid)
} else {
loger.Info(`使用固定MixSongMid: ` + mixid)
}
// 声明变量
headers := map[string]string{
`User-Agent`: ztool.Str_FastConcat(
`Android712-AndroidPhone-`,
env.Config.Custom.Kg_Client_Version,
`-18-0-NetMusic-wifi`,
),
`KG-THash`: `3e5ec6b`,
`KG-Rec`: `1`,
`KG-RC`: `1`,
`x-router`: `youth.kugou.com`,
}
body := ztool.Str_FastConcat(
`{"mixsongid":"`, mixid, `"}`,
)
tnow := time.Now()
params := map[string]string{
`userid`: env.Config.Custom.Kg_userId,
`token`: env.Config.Custom.Kg_token,
`appid`: env.Config.Custom.Kg_Client_AppId,
`clientver`: env.Config.Custom.Kg_Client_Version,
`clienttime`: strconv.FormatInt(tnow.Unix(), 10),
`mid`: mid,
`uuid`: zcypt.HexToString(zcypt.RandomBytes(16)),
`dfid`: `-`,
}
// 发送请求
var out refreshInfo
err = signRequest(
http.MethodPost,
`https://gateway.kugou.com/v2/report/listen_song`,
body, params, headers, &out,
)
if err != nil {
return err
}
loger.Debug(`Resp: %+v`, out)
if out.Status != 1 || out.ErrorCode != 0 {
switch out.ErrorCode {
case 130012:
loger.Info(`今日已签到过,明天再来吧`)
case 51002:
panic(`登录过期啦请重新获取账号Token`)
default:
return errors.New(strconv.Itoa(out.ErrorCode) + `: ` + out.ErrorMsg)
}
} else {
loger.Info(`Lite签到成功`)
}
tomorrow := time.Date(tnow.Year(), tnow.Month(), tnow.Day()+1, 0, 0, 0, 0, tnow.Location())
env.Config.Custom.Kg_Lite_Interval = tomorrow.Unix()
return env.Cfg.Save(``)
}
// 刷新Token
func login_by_token(loger *logs.Logger, now int64) (err error) {
// 前置到期检测
if now < env.Config.Custom.Kg_Refresh_Interval {
loger.Debug(`Key未过期跳过...`)
return
}
// 获取加密参数
var aeskey []byte
switch env.Config.Custom.Kg_Client_AppId {
case `1005`:
aeskey = []byte(`90b8382a1bb4ccdcf063102053fd75b8`)
case `3116`:
aeskey = []byte(`c24f74ca2820225badc01946dba4fdf7`)
default:
panic(`当前应用AppId暂不支持此功能`)
}
// 生成请求数据
tnow := time.Now()
pbyte, _ := json.Marshal(map[string]any{
`clienttime`: tnow.Unix(),
`token`: env.Config.Custom.Kg_token,
})
block, _ := aes.NewCipher(aeskey)
encrypter := cipher.NewCBCEncrypter(block, aeskey[block.BlockSize():])
padata := zcypt.PKCS7Padding(pbyte, block.BlockSize())
encrypted := make([]byte, len(padata))
encrypter.CryptBlocks(encrypted, padata)
encstr := zcypt.HexToString(encrypted)
bodys, _ := json.Marshal(map[string]any{
`t1`: 0,
`t2`: 0,
`p3`: encstr,
`userid`: env.Config.Custom.Kg_userId,
`clienttime_ms`: tnow.UnixMilli(),
})
params := map[string]string{
`dfid`: `-`,
`mid`: `20211008`,
`clientver`: env.Config.Custom.Kg_Client_Version,
`clienttime`: strconv.FormatInt(tnow.Unix(), 10),
`appid`: env.Config.Custom.Kg_Client_AppId,
}
headers := map[string]string{
`User-Agent`: `Android711-1070-10860-14-0-LOGIN-wifi`,
`KG-THash`: `7af653c`,
`KG-Rec`: `1`,
`KG-RC`: `1`,
}
// 请求对应接口
var res loginInfo
err = signRequest(
http.MethodPost,
`http://login.user.kugou.com/v4/login_by_token`,
bytesconv.BytesToString(bodys),
params, headers, &res,
)
if err != nil {
return errors.New(`接口请求失败: ` + err.Error())
}
loger.Info(`获取数据成功`)
loger.Debug(`Resp: %+v`, res)
if res.ErrorCode != 0 {
return errors.New(`刷新登录失败: ` + strconv.Itoa(res.ErrorCode))
}
env.Config.Custom.Kg_token = res.Data.Token
env.Config.Custom.Kg_userId = strconv.Itoa(res.Data.Userid)
next := time.Date(tnow.Year(), tnow.Month(), tnow.Day()+25, 0, 0, 0, 0, tnow.Location())
env.Config.Custom.Kg_Refresh_Interval = next.Unix()
loger.Info(`刷新登录成功`)
return env.Cfg.Save(``)
}
func init() {
env.Inits.Add(func() {
if env.Config.Custom.Kg_token != `` {
if env.Config.Custom.Kg_Lite_Enable && env.Config.Custom.Kg_Client_AppId == `3116` {
env.Tasker.Add(`kg_litsign`, do_account_signin, 86000, true)
}
if env.Config.Custom.Kg_Refresh_Enable {
env.Tasker.Add(`kg_refresh`, login_by_token, 86000, true)
}
}
/*if env.Config.Custom.Kg_Lite_Enable {
if env.Config.Custom.Kg_Client_AppId == `3116` && env.Config.Custom.Kg_token != `` {
env.Tasker.Add(`kg_litsign`, do_account_signin, 86000, true)
}
}
if env.Config.Custom.Kg_Refresh_Enable && env.Config.Custom.Kg_token != `` {
env.Tasker.Add(`kg_refresh`, login_by_token, 86000, true)
}*/
})
}

View File

@ -0,0 +1,503 @@
package kg
type (
musicInfo struct {
AlbumAudioID string `json:"album_audio_id"`
// AudioID string `json:"audio_id"`
// AuthorName string `json:"author_name"`
// OriAudioName string `json:"ori_audio_name"`
// OfficialSongname string `json:"official_songname"`
// Songname string `json:"songname"`
// Remark string `json:"remark"`
// SuffixAudioName string `json:"suffix_audio_name"`
// IsSearch string `json:"is_search"`
// IsOriginal string `json:"is_original"`
// IsPublish string `json:"is_publish"`
// MixsongType string `json:"mixsong_type"`
// Version string `json:"version"`
// Language string `json:"language"`
// Bpm string `json:"bpm"`
// BpmType string `json:"bpm_type"`
// BpmDesc string `json:"bpm_desc"`
// PublishDate string `json:"publish_date"`
// Extname string `json:"extname"`
AlbumInfo struct {
AlbumID string `json:"album_id"`
// AlbumName string `json:"album_name"`
// PublishDate string `json:"publish_date"`
// Category string `json:"category"`
// IsPublish string `json:"is_publish"`
// SizableCover string `json:"sizable_cover"`
} `json:"album_info"`
// Classification []struct {
// Usage string `json:"usage"`
// Status string `json:"status"`
// Level string `json:"level"`
// ResType string `json:"res_type"`
// Type string `json:"type"`
// ID string `json:"id"`
// ResID string `json:"res_id"`
// } `json:"classification"`
AudioInfo struct {
// IsFileHead string `json:"is_file_head"`
// IsFileHead320 string `json:"is_file_head_320"`
// AudioID string `json:"audio_id"`
// Hash string `json:"hash"`
// Filesize string `json:"filesize"`
// Timelength string `json:"timelength"`
// Bitrate string `json:"bitrate"`
Hash128 string `json:"hash_128"`
// Filesize128 string `json:"filesize_128"`
// Timelength128 string `json:"timelength_128"`
Hash320 string `json:"hash_320"`
// Filesize320 string `json:"filesize_320"`
// Timelength320 string `json:"timelength_320"`
HashFlac string `json:"hash_flac"`
// FilesizeFlac string `json:"filesize_flac"`
// TimelengthFlac string `json:"timelength_flac"`
// BitrateFlac string `json:"bitrate_flac"`
HashHigh string `json:"hash_high"`
// FilesizeHigh string `json:"filesize_high"`
// TimelengthHigh string `json:"timelength_high"`
// BitrateHigh string `json:"bitrate_high"`
HashSuper string `json:"hash_super"`
// FilesizeSuper string `json:"filesize_super"`
// TimelengthSuper string `json:"timelength_super"`
// BitrateSuper string `json:"bitrate_super"`
// HashVinylrecord string `json:"hash_vinylrecord"`
// FilesizeVinylrecord string `json:"filesize_vinylrecord"`
// TimelengthVinylrecord string `json:"timelength_vinylrecord"`
// BitrateVinylrecord string `json:"bitrate_vinylrecord"`
// HashMultichannel string `json:"hash_multichannel"`
// FilesizeMultichannel string `json:"filesize_multichannel"`
// TimelengthMultichannel string `json:"timelength_multichannel"`
// BitrateMultichannel string `json:"bitrate_multichannel"`
// HashDolby448 string `json:"hash_dolby_448"`
// FilesizeDolby448 string `json:"filesize_dolby_448"`
// TimelengthDolby448 string `json:"timelength_dolby_448"`
// BitrateDolby448 string `json:"bitrate_dolby_448"`
// HashDolby640 string `json:"hash_dolby_640"`
// FilesizeDolby640 string `json:"filesize_dolby_640"`
// TimelengthDolby640 string `json:"timelength_dolby_640"`
// BitrateDolby640 string `json:"bitrate_dolby_640"`
// HashDolby768 string `json:"hash_dolby_768"`
// FilesizeDolby768 string `json:"filesize_dolby_768"`
// TimelengthDolby768 string `json:"timelength_dolby_768"`
// BitrateDolby768 string `json:"bitrate_dolby_768"`
// AudioGroupID string `json:"audio_group_id"`
// ExtnameSuper string `json:"extname_super"`
// Extname string `json:"extname"`
// FailProcess int `json:"fail_process"`
// PayType int `json:"pay_type"`
// Type string `json:"type"`
// OldCpy int `json:"old_cpy"`
// Privilege string `json:"privilege"`
// Privilege128 string `json:"privilege_128"`
// Privilege320 string `json:"privilege_320"`
// PrivilegeFlac string `json:"privilege_flac"`
// PrivilegeHigh string `json:"privilege_high"`
// PrivilegeSuper string `json:"privilege_super"`
// PrivilegeVinylrecord string `json:"privilege_vinylrecord"`
// PrivilegeMultichannel string `json:"privilege_multichannel"`
// PrivilegeDolby448 string `json:"privilege_dolby_448"`
// PrivilegeDolby640 string `json:"privilege_dolby_640"`
// PrivilegeDolby768 string `json:"privilege_dolby_768"`
// TransParam struct {
// HashOffset struct {
// StartByte int `json:"start_byte"`
// EndByte int `json:"end_byte"`
// StartMs int `json:"start_ms"`
// EndMs int `json:"end_ms"`
// OffsetHash string `json:"offset_hash"`
// FileType int `json:"file_type"`
// ClipHash string `json:"clip_hash"`
// } `json:"hash_offset"`
// MusicpackAdvance int `json:"musicpack_advance"`
// PayBlockTpl int `json:"pay_block_tpl"`
// Display int `json:"display"`
// DisplayRate int `json:"display_rate"`
// CpyGrade int `json:"cpy_grade"`
// CpyLevel int `json:"cpy_level"`
// Cid int `json:"cid"`
// CpyAttr0 int `json:"cpy_attr0"`
// Classmap struct {
// Attr0 int `json:"attr0"`
// } `json:"classmap"`
// InitPubDay int `json:"init_pub_day"`
// Qualitymap struct {
// Attr0 int `json:"attr0"`
// } `json:"qualitymap"`
// Language string `json:"language"`
// } `json:"trans_param"`
} `json:"audio_info"`
}
playInfo struct {
// Hash string `json:"hash"`
// Classmap struct {
// Attr0 int `json:"attr0"`
// } `json:"classmap"`
Status int `json:"status"`
// Volume float64 `json:"volume"`
// StdHashTime int `json:"std_hash_time"`
URL []string `json:"url"`
// StdHash string `json:"std_hash"`
// TransParam struct {
// Display int `json:"display"`
// DisplayRate int `json:"display_rate"`
// } `json:"trans_param"`
// FileHead int `json:"fileHead"`
// VolumePeak float64 `json:"volume_peak"`
// BitRate int `json:"bitRate"`
// TimeLength int `json:"timeLength"`
// VolumeGain int `json:"volume_gain"`
// Q int `json:"q"`
// FileName string `json:"fileName"`
// ExtName string `json:"extName"`
// FileSize int `json:"fileSize"`
}
)
type rankInfo struct {
Data struct {
// Timestamp int `json:"timestamp"`
// Total int `json:"total"`
Info []struct {
// LastSort int `json:"last_sort"`
// Authors []struct {
// SizableAvatar string `json:"sizable_avatar"`
// IsPublish int `json:"is_publish"`
// AuthorName string `json:"author_name"`
// AuthorID int `json:"author_id"`
// } `json:"authors"`
// RankCount int `json:"rank_count"`
// RankIDPublishDate string `json:"rank_id_publish_date"`
// Songname string `json:"songname"`
// TopicURL320 string `json:"topic_url_320"`
// Sqhash string `json:"sqhash"`
// FailProcess int `json:"fail_process"`
// PayType int `json:"pay_type"`
// RecommendReason string `json:"recommend_reason"`
// RpType string `json:"rp_type"`
// AlbumID string `json:"album_id"`
// PrivilegeHigh int `json:"privilege_high"`
// TopicURLSq string `json:"topic_url_sq"`
// RankCid int `json:"rank_cid"`
// Inlist int `json:"inlist"`
// Three20Filesize int `json:"320filesize"`
// PkgPrice320 int `json:"pkg_price_320"`
// Feetype int `json:"feetype"`
// Price320 int `json:"price_320"`
// DurationHigh int `json:"duration_high"`
// FailProcess320 int `json:"fail_process_320"`
// Zone string `json:"zone"`
// TopicURL string `json:"topic_url"`
// RpPublish int `json:"rp_publish"`
// TransObj struct {
// RankShowSort int `json:"rank_show_sort"`
// } `json:"trans_obj"`
// Hash string `json:"hash"`
// Sqfilesize int `json:"sqfilesize"`
// Sqprivilege int `json:"sqprivilege"`
// PayTypeSq int `json:"pay_type_sq"`
// Bitrate int `json:"bitrate"`
// PkgPriceSq int `json:"pkg_price_sq"`
// HasAccompany int `json:"has_accompany"`
// Musical interface{} `json:"musical"`
// PayType320 int `json:"pay_type_320"`
// Issue int `json:"issue"`
// ExtnameSuper string `json:"extname_super"`
// DurationSuper int `json:"duration_super"`
// BitrateSuper int `json:"bitrate_super"`
// HashHigh string `json:"hash_high"`
// Duration int `json:"duration"`
// Three20Hash string `json:"320hash"`
// PriceSq int `json:"price_sq"`
// OldCpy int `json:"old_cpy"`
// AlbumAudioID int `json:"album_audio_id"`
// M4Afilesize int `json:"m4afilesize"`
// PkgPrice int `json:"pkg_price"`
// First int `json:"first"`
// AudioID int `json:"audio_id"`
// HashSuper string `json:"hash_super"`
// Addtime string `json:"addtime"`
// FilesizeHigh int `json:"filesize_high"`
// Price int `json:"price"`
// Privilege int `json:"privilege"`
// AlbumSizableCover string `json:"album_sizable_cover"`
// Mvdata []struct {
// Typ int `json:"typ"`
// Trk string `json:"trk"`
// Hash string `json:"hash"`
// ID string `json:"id"`
// } `json:"mvdata,omitempty"`
// Sort int `json:"sort"`
// TransParam struct {
// CpyLevel int `json:"cpy_level"`
// Classmap struct {
// Attr0 int `json:"attr0"`
// } `json:"classmap"`
// CpyGrade int `json:"cpy_grade"`
// Qualitymap struct {
// Attr0 int `json:"attr0"`
// } `json:"qualitymap"`
// PayBlockTpl int `json:"pay_block_tpl"`
// Cid int `json:"cid"`
// Language string `json:"language"`
// HashMultitrack string `json:"hash_multitrack"`
// CpyAttr0 int `json:"cpy_attr0"`
// Ipmap struct {
// Attr0 int `json:"attr0"`
// } `json:"ipmap"`
// AppidBlock string `json:"appid_block"`
// MusicpackAdvance int `json:"musicpack_advance"`
// Display int `json:"display"`
// DisplayRate int `json:"display_rate"`
// } `json:"trans_param"`
// FilesizeSuper int `json:"filesize_super"`
Filename string `json:"filename"`
// BitrateHigh int `json:"bitrate_high"`
// Remark string `json:"remark"`
// Extname string `json:"extname"`
// Filesize int `json:"filesize"`
// Isfirst int `json:"isfirst"`
// Mvhash string `json:"mvhash"`
// Three20Privilege int `json:"320privilege"`
// PrivilegeSuper int `json:"privilege_super"`
// FailProcessSq int `json:"fail_process_sq"`
} `json:"info"`
} `json:"data"`
// Errcode int `json:"errcode"`
Status int `json:"status"`
Error string `json:"error"`
}
type searchInfo struct {
Data struct {
// AlgPath string `json:"AlgPath"`
// Aggregation struct {
// } `json:"aggregation"`
// Allowerr int `json:"allowerr"`
// Chinesecount int `json:"chinesecount"`
// Correctionforce int `json:"correctionforce"`
// Correctionrelate string `json:"correctionrelate"`
// Correctionsubject string `json:"correctionsubject"`
// Correctiontip string `json:"correctiontip"`
// Correctiontype int `json:"correctiontype"`
// From int `json:"from"`
// Isshareresult int `json:"isshareresult"`
// Istag int `json:"istag"`
// Istagresult int `json:"istagresult"`
Lists []struct {
// A320Privilege int `json:"A320Privilege"`
// ASQPrivilege int `json:"ASQPrivilege"`
// Accompany int `json:"Accompany"`
// AlbumAux string `json:"AlbumAux"`
// AlbumID string `json:"AlbumID"`
// AlbumName string `json:"AlbumName"`
// AlbumPrivilege int `json:"AlbumPrivilege"`
// AudioCdn int `json:"AudioCdn"`
// Audioid int `json:"Audioid"`
// Auxiliary string `json:"Auxiliary"`
// Bitrate int `json:"Bitrate"`
// Category int `json:"Category"`
// Duration int `json:"Duration"`
// ExtName string `json:"ExtName"`
// FailProcess int `json:"FailProcess"`
// FileHash string `json:"FileHash"`
// FileName string `json:"FileName"`
// FileSize int `json:"FileSize"`
// FoldType int `json:"FoldType"`
// Grp []interface{} `json:"Grp"`
// HQBitrate int `json:"HQBitrate"`
// HQDuration int `json:"HQDuration"`
// HQExtName string `json:"HQExtName"`
// HQFailProcess int `json:"HQFailProcess"`
// HQFileHash string `json:"HQFileHash"`
// HQFileSize int `json:"HQFileSize"`
// HQPayType int `json:"HQPayType"`
// HQPkgPrice int `json:"HQPkgPrice"`
// HQPrice int `json:"HQPrice"`
// HQPrivilege int `json:"HQPrivilege"`
// HasAlbum int `json:"HasAlbum"`
// HeatLevel int `json:"HeatLevel"`
// HiFiQuality int `json:"HiFiQuality"`
// ID string `json:"ID"`
// Image string `json:"Image"`
// IsOriginal int `json:"IsOriginal"`
// M4ASize int `json:"M4aSize"`
// MatchFlag int `json:"MatchFlag"`
MixSongID string `json:"MixSongID"`
// MvHash string `json:"MvHash"`
// MvTrac int `json:"MvTrac"`
// MvType int `json:"MvType"`
// OldCpy int `json:"OldCpy"`
// OriOtherName string `json:"OriOtherName"`
// OriSongName string `json:"OriSongName"`
// OtherName string `json:"OtherName"`
// OwnerCount int `json:"OwnerCount"`
// PayType int `json:"PayType"`
// PkgPrice int `json:"PkgPrice"`
// Price int `json:"Price"`
// Privilege int `json:"Privilege"`
// Publish int `json:"Publish"`
// PublishAge int `json:"PublishAge"`
// PublishTime string `json:"PublishTime"`
// QualityLevel int `json:"QualityLevel"`
// RankID int `json:"RankId"`
// Res struct {
// FailProcess int `json:"FailProcess"`
// PayType int `json:"PayType"`
// PkgPrice int `json:"PkgPrice"`
// Price int `json:"Price"`
// Privilege int `json:"Privilege"`
// } `json:"Res"`
// ResBitrate int `json:"ResBitrate"`
// ResDuration int `json:"ResDuration"`
// ResFileHash string `json:"ResFileHash"`
// ResFileSize int `json:"ResFileSize"`
// SQBitrate int `json:"SQBitrate"`
// SQDuration int `json:"SQDuration"`
// SQExtName string `json:"SQExtName"`
// SQFailProcess int `json:"SQFailProcess"`
// SQFileHash string `json:"SQFileHash"`
// SQFileSize int `json:"SQFileSize"`
// SQPayType int `json:"SQPayType"`
// SQPkgPrice int `json:"SQPkgPrice"`
// SQPrice int `json:"SQPrice"`
// SQPrivilege int `json:"SQPrivilege"`
// Scid int `json:"Scid"`
// ShowingFlag int `json:"ShowingFlag"`
// SingerID []int `json:"SingerId"`
// SingerName string `json:"SingerName"`
// Singers []struct {
// ID int `json:"id"`
// IPID int `json:"ip_id"`
// Name string `json:"name"`
// } `json:"Singers"`
// SongLabel string `json:"SongLabel"`
// SongName string `json:"SongName"`
// Source string `json:"Source"`
// SourceID int `json:"SourceID"`
// Suffix string `json:"Suffix"`
// SuperBitrate int `json:"SuperBitrate"`
// SuperDuration int `json:"SuperDuration"`
// SuperExtName string `json:"SuperExtName"`
// SuperFileHash string `json:"SuperFileHash"`
// SuperFileSize int `json:"SuperFileSize"`
// TagContent string `json:"TagContent"`
// TagDetails []struct {
// Content string `json:"content"`
// Rankid int `json:"rankid"`
// Type int `json:"type"`
// Version int `json:"version"`
// } `json:"TagDetails"`
// TopID int `json:"TopID"`
// TopicRemark string `json:"TopicRemark"`
// TopicURL string `json:"TopicUrl"`
// Type string `json:"Type"`
// Uploader string `json:"Uploader"`
// UploaderContent string `json:"UploaderContent"`
// MvTotal int `json:"mvTotal"`
// Mvdata []struct {
// Hash string `json:"hash"`
// ID string `json:"id"`
// Trk string `json:"trk"`
// Typ int `json:"typ"`
// } `json:"mvdata"`
// RecommendType int `json:"recommend_type"`
// TransParam struct {
// AppidBlock string `json:"appid_block"`
// Cid int `json:"cid"`
// Classmap struct {
// Attr0 int `json:"attr0"`
// } `json:"classmap"`
// CpyAttr0 int `json:"cpy_attr0"`
// CpyGrade int `json:"cpy_grade"`
// CpyLevel int `json:"cpy_level"`
// Display int `json:"display"`
// DisplayRate int `json:"display_rate"`
// HashMultitrack string `json:"hash_multitrack"`
// Ipmap struct {
// Attr0 int `json:"attr0"`
// } `json:"ipmap"`
// Language string `json:"language"`
// MusicpackAdvance int `json:"musicpack_advance"`
// PayBlockTpl int `json:"pay_block_tpl"`
// Qualitymap struct {
// Attr0 int `json:"attr0"`
// } `json:"qualitymap"`
// SongnameSuffix string `json:"songname_suffix"`
// } `json:"trans_param"`
// Vvid string `json:"vvid"`
} `json:"lists"`
// Page int `json:"page"`
// Pagesize int `json:"pagesize"`
// Searchfull int `json:"searchfull"`
// SecAggre struct {
// } `json:"sec_aggre"`
// SecAggreV2 []interface{} `json:"sec_aggre_v2"`
// SectagInfo struct {
// IsSectag int `json:"is_sectag"`
// } `json:"sectag_info"`
// Size int `json:"size"`
// Subjecttype int `json:"subjecttype"`
Total int `json:"total"`
} `json:"data"`
// ErrorCode int `json:"error_code"`
ErrorMsg string `json:"error_msg"`
Status int `json:"status"`
}
type refreshInfo struct {
// Data string `json:"data"`
ErrorCode int `json:"error_code"`
ErrorMsg string `json:"error_msg"`
Status int `json:"status"`
}
type loginInfo struct {
Data struct {
// ArttoyAvatar string `json:"arttoy_avatar"`
// BcCode string `json:"bc_code"`
// Birthday string `json:"birthday"`
// BirthdayMmdd string `json:"birthday_mmdd"`
// Exp int `json:"exp"`
// IsVip int `json:"is_vip"`
// MBeginTime string `json:"m_begin_time"`
// MEndTime string `json:"m_end_time"`
// MIsOld int `json:"m_is_old"`
// MType int `json:"m_type"`
// Mobile int `json:"mobile"`
// Nickname string `json:"nickname"`
// Pic string `json:"pic"`
// Qq int `json:"qq"`
// RegTime string `json:"reg_time"`
// RoamBeginTime string `json:"roam_begin_time"`
// RoamEndTime string `json:"roam_end_time"`
// RoamList struct {
// } `json:"roam_list"`
// RoamType int `json:"roam_type"`
// Score int `json:"score"`
// Servertime string `json:"servertime"`
// Sex int `json:"sex"`
// SuVipBeginTime string `json:"su_vip_begin_time"`
// SuVipClearday string `json:"su_vip_clearday"`
// SuVipEndTime string `json:"su_vip_end_time"`
// SuVipYEndtime string `json:"su_vip_y_endtime"`
// T1 string `json:"t1"`
Token string `json:"token"`
// TotpServerTimestamp int `json:"totp_server_timestamp"`
// UserType int `json:"user_type"`
// UserYType int `json:"user_y_type"`
Userid int `json:"userid"`
// Username string `json:"username"`
// VipBeginTime string `json:"vip_begin_time"`
// VipEndTime string `json:"vip_end_time"`
// VipToken string `json:"vip_token"`
// VipType int `json:"vip_type"`
// Wechat int `json:"wechat"`
// YType int `json:"y_type"`
} `json:"data"`
ErrorCode int `json:"error_code"`
Status int `json:"status"`
}

View File

@ -0,0 +1,123 @@
package kg
import (
"lx-source/src/env"
"lx-source/src/sources"
"strings"
"github.com/ZxwyWebSite/ztool"
"github.com/ZxwyWebSite/ztool/x/slices"
"github.com/ZxwyWebSite/ztool/zcypt"
)
var (
// qualityHashMap = map[string]string{
// sources.Q_128k: `hash_128`,
// sources.Q_320k: `hash_320`,
// sources.Q_flac: `hash_flac`,
// sources.Q_fl24: `hash_high`,
// }
qualityMap = map[string]string{
sources.Q_128k: `128`,
sources.Q_320k: `320`,
sources.Q_flac: sources.Q_flac,
sources.Q_fl24: `high`,
sources.Q_master: `viper_atmos`,
}
)
const (
// signkey = `OIlwieks28dk2k092lksi2UIkp`
// pidversec = `57ae12eb6890223e355ccfcb74edf70d`
// clientver = `12029`
url = `https://gateway.kugou.com/v5/url`
// appid = `1005`
mid = `211008`
)
func sortDict(dictionary map[string]string) ([]string, int) {
length := len(dictionary)
var keys = make([]string, 0, length)
for k := range dictionary {
keys = append(keys, k)
}
slices.Sort(keys)
return keys, length
}
// func sign(params map[string]string, body string) string {
// keys, lens := sortDict(params)
// var b strings.Builder
// for i := 0; i < lens; i++ {
// b.WriteString(keys[i])
// b.WriteByte('=')
// b.WriteString(params[keys[i]])
// }
// // b.WriteString(body)
// return zcypt.MD5EncStr(ztool.Str_FastConcat(signkey, b.String(), signkey))
// }
func signRequest(method string, url string, body string, params, headers map[string]string, out any) error {
keys, lens := sortDict(params)
// buildSignatureParams
var b strings.Builder
for i := 0; i < lens; i++ {
b.WriteString(keys[i])
b.WriteByte('=')
b.WriteString(params[keys[i]])
}
b.WriteString(body)
// buildRequestParams
var c strings.Builder
for j := 0; j < lens; j++ {
c.WriteString(keys[j])
c.WriteByte('=')
c.WriteString(params[keys[j]])
c.WriteByte('&')
}
c.WriteString(`signature`)
c.WriteByte('=')
c.WriteString(zcypt.MD5EncStr(ztool.Str_FastConcat(
env.Config.Custom.Kg_Client_SignKey,
b.String(), env.Config.Custom.Kg_Client_SignKey,
)))
url = ztool.Str_FastConcat(url, `?`, c.String())
// ztool.Cmd_FastPrintln(url)
return ztool.Net_Request(
method, url, strings.NewReader(body),
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeader(headers)},
[]ztool.Net_ResHandlerFunc{ //func(res *http.Response) error {
// body, err := io.ReadAll(res.Body)
// fmt.Printf("%s, %s, %s\n", body, err, res.Status)
// return ztool.Err_EsContinue
// },
ztool.Net_ResToStruct(out),
},
)
}
func getKey(hash_ string) string {
return zcypt.MD5EncStr(ztool.Str_FastConcat(
strings.ToLower(hash_), env.Config.Custom.Kg_Client_PidVerSec,
env.Config.Custom.Kg_Client_AppId, mid, env.Config.Custom.Kg_userId,
))
}
// 解析版权字段 (Copilot)
/*
// 定义一个函数,接受一个整数作为参数,返回一个布尔值的切片,表示每一位的状态
依次为: 下载是否付费 \ 下载是否禁止 \ 播放是否付费 \ 播放是否禁止
https://open.kugou.com/docs/open-player/#/android-sdk?v=1&id=%e6%ad%8c%e6%9b%b2%e5%ad%97%e6%ae%b5%e8%a7%a3%e6%9e%90
*/
// func parsePrivilege(privilege int) []bool {
// result := make([]bool, 4) // 创建一个长度为4的切片
// for i := 0; i < 4; i++ {
// // 用位运算符&来判断每一位是否为1如果是则将对应的切片元素设为true
// if privilege&(1<<i) != 0 {
// result[i] = true
// }
// }
// return result
// }

View File

@ -9,6 +9,7 @@ import (
"encoding/base64"
"github.com/ZxwyWebSite/ztool/x/bytesconv"
"github.com/ZxwyWebSite/ztool/zcypt"
)
// 常量和数组
@ -250,8 +251,9 @@ func encrypt(msg []byte, key []byte) []byte {
}
// base64编码函数
func Base64_encrypt(msg string) string {
func base64_encrypt(msg string) string {
b1 := encrypt(bytesconv.StringToBytes(msg), SECRET_KEY)
s := base64.StdEncoding.EncodeToString(b1)
return s //strings.ReplaceAll(s, "\n", ``)
return zcypt.Base64ToString(base64.StdEncoding, b1)
// s := base64.StdEncoding.EncodeToString(b1)
// return s //strings.ReplaceAll(s, "\n", ``)
}

View File

@ -1,15 +1,19 @@
package kw
import (
"errors"
"io"
"lx-source/src/env"
"lx-source/src/sources"
"lx-source/src/sources/custom/utils"
"net/http"
"strconv"
"strings"
"sync"
"github.com/ZxwyWebSite/ztool"
"github.com/ZxwyWebSite/ztool/x/bytesconv"
"github.com/ZxwyWebSite/ztool/zcypt"
)
var (
@ -19,6 +23,7 @@ var (
parsemod bool
convtype string
desource string
// desParse func([]byte, *playInfo) error
// desParse func(any) ztool.Net_ResHandlerFunc
)
@ -28,7 +33,7 @@ func init() {
loger := env.Loger.NewGroup(`KwInit`)
switch env.Config.Custom.Kw_Mode {
case `0`, `bdapi`:
loger.Debug(`Use bdapi`)
loger.Debug(`use bdapi`)
if ztool.Chk_IsNilStr(
env.Config.Custom.Kw_Bd_Uid,
env.Config.Custom.Kw_Bd_Token,
@ -36,36 +41,47 @@ func init() {
) {
loger.Fatal(`使用bdapi且验证参数为空`)
}
// bdheader[`token`] = env.Config.Custom.Kw_Bd_Token
bdheader[`uid`] = env.Config.Custom.Kw_Bd_Uid
bdheader[`devId`] = env.Config.Custom.Kw_Bd_DevId
kw_pool = &sync.Pool{New: func() any { return new(kwApi_Song) }}
Url = bdapi
case `1`, `kwdes`:
Url = kwdes
switch env.Config.Custom.Kw_Des_Type {
case `0`, `text`:
loger.Debug(`Use kwdes_text`)
loger.Debug(`use kwdes text`)
convtype = `convert_url2`
// desParse = txtParse
case `1`, `json`:
loger.Debug(`Use kwdes_json`)
loger.Debug(`use kwdes json`)
convtype = `convert_url_with_sign`
// desParse = ztool.Net_ResToStruct
parsemod = true
case `2`, `anti`:
loger.Debug(`use kwdes anti`)
Url = manti
default:
loger.Fatal(`未定义的返回格式,请检查配置 [Custom].Kw_Des_Type`)
}
desheader[`User-Agent`] = env.Config.Custom.Kw_Des_Header
kw_pool = &sync.Pool{New: func() any { return new(playInfo) }}
Url = kwdes
if env.Config.Custom.Kw_Des_Source != `` {
desource = env.Config.Custom.Kw_Des_Source
} else {
dec, _ := zcypt.HexDecode([]byte{0x36, 0x62, 0x37, 0x37, 0x37, 0x30, 0x36, 0x63, 0x36, 0x31, 0x37, 0x39, 0x36, 0x35, 0x37, 0x32, 0x36, 0x38, 0x36, 0x34, 0x35, 0x66, 0x36, 0x31, 0x37, 0x32, 0x35, 0x66, 0x33, 0x35, 0x32, 0x65, 0x33, 0x31, 0x32, 0x65, 0x33, 0x30, 0x32, 0x65, 0x33, 0x30, 0x35, 0x66, 0x34, 0x32, 0x35, 0x66, 0x36, 0x61, 0x36, 0x39, 0x36, 0x31, 0x36, 0x62, 0x36, 0x66, 0x36, 0x65, 0x36, 0x37, 0x35, 0x66, 0x37, 0x36, 0x36, 0x38, 0x32, 0x65, 0x36, 0x31, 0x37, 0x30, 0x36, 0x62})
desource = bytesconv.BytesToString(dec)
}
default:
loger.Fatal(`未定义的接口模式,请检查配置 [Custom].Kw_Mode`)
}
loger = nil
loger.Free()
})
}
func bdapi(songMid, quality string) (ourl, msg string) {
loger := env.Loger.NewGroup(`Kw`)
defer loger.Free()
info, ok := fileInfo[quality]
if !ok {
msg = sources.E_QNotSupport
@ -78,7 +94,7 @@ func bdapi(songMid, quality string) (ourl, msg string) {
`https://bd-api.kuwo.cn/api/service/music/downloadInfo/`, songMid,
`?isMv=0&format=`, info.E,
`&br=`, info.H, info.E, //`&level=`,
`&uin=`, env.Config.Custom.Kw_Bd_Uid,
`&uid=`, env.Config.Custom.Kw_Bd_Uid,
`&token=`, env.Config.Custom.Kw_Bd_Token,
)
// jx.Debug(`Kw, Url: %s`, url)
@ -91,7 +107,7 @@ func bdapi(songMid, quality string) (ourl, msg string) {
loger.Debug(`Resp: %+v`, resp)
if resp.Code != 200 || resp.Data.AudioInfo.Bitrate == `1` {
// jx.Debug(`Kw, Err: %#v`, resp)
msg = ztool.Str_FastConcat(`failed: `, resp.Msg)
msg = ztool.Str_FastConcat(strconv.Itoa(resp.Code), `: `, resp.Msg)
return
}
ourl = utils.DelQuery(resp.Data.URL) //strings.Split(resp.Data.URL, `?`)[0]
@ -100,6 +116,7 @@ func bdapi(songMid, quality string) (ourl, msg string) {
func kwdes(songMid, quality string) (ourl, msg string) {
loger := env.Loger.NewGroup(`Kw`)
defer loger.Free()
infoFile, ok := fileInfo[quality]
if !ok {
msg = sources.E_QNotSupport
@ -107,13 +124,13 @@ func kwdes(songMid, quality string) (ourl, msg string) {
}
target_url := ztool.Str_FastConcat(
`https://mobi.kuwo.cn/mobi.s?f=kuwo&q=`,
Base64_encrypt(ztool.Str_FastConcat(
`user=0&android_id=0&prod=kwplayer_ar_8.5.5.0&corp=kuwo&newver=3&vipver=8.5.5.0&source=kwplayer_ar_8.5.5.0_apk_keluze.apk&p2p=1&notrace=0`,
base64_encrypt(ztool.Str_FastConcat(
`corp=kuwo&p2p=1&sig=0&notrace=0&priority=bitrate&network=WIFI&mode=down`,
`&source=`, desource,
`&type=`, convtype,
`&br=`, infoFile.H, infoFile.E,
`&format=`, infoFile.E,
`&rid=`, songMid,
`&priority=bitrate&loginUid=0&network=WIFI&loginSid=0&mode=down`,
)),
)
if parsemod {
@ -129,6 +146,7 @@ func kwdes(songMid, quality string) (ourl, msg string) {
msg = sources.ErrHttpReq
return
}
resp.Data.URL = utils.DelQuery(resp.Data.URL)
loger.Debug(`Resp: %+v`, resp)
if resp.Code != http.StatusOK {
msg = ztool.Str_FastConcat(`failed: `, resp.Msg)
@ -136,11 +154,13 @@ func kwdes(songMid, quality string) (ourl, msg string) {
return
}
realQuality := strconv.Itoa(resp.Data.Bitrate)
if realQuality != infoFile.H[:len(infoFile.H)-1] {
if realQuality != infoFile.H[:len(infoFile.H)-1] /*&& resp.Data.Bitrate != 1*/ {
msg = sources.E_QNotMatch
if !env.Config.Source.ForceFallback {
return
}
ourl = utils.DelQuery(resp.Data.URL) //resp.Data.URL[:strings.Index(resp.Data.URL, `?`)]
}
ourl = resp.Data.URL //resp.Data.URL[:strings.Index(resp.Data.URL, `?`)]
return
}
ztool.Net_Request(http.MethodGet, target_url, nil,
@ -164,8 +184,10 @@ func kwdes(songMid, quality string) (ourl, msg string) {
realQuality := infoData[`bitrate`]
if realQuality != infoFile.H[:len(infoFile.H)-1] {
msg = sources.E_QNotMatch
if !env.Config.Source.ForceFallback {
return
}
}
ourl = utils.DelQuery(infoData[`url`]) //infoData[`url`][:strings.Index(infoData[`url`], `?`)]
return
},
@ -173,3 +195,133 @@ func kwdes(songMid, quality string) (ourl, msg string) {
)
return
}
// 一种替代方案仅在试听可用时获取cdn前缀缺点是无法获取不能试听的歌曲
func manti(songMid, quality string) (ourl, msg string) {
loger := env.Loger.NewGroup(`Kw`)
defer loger.Free()
infoFile, ok := fileInfo[quality]
if !ok {
msg = sources.E_QNotSupport
return
}
// 获取CDN地址
// var out_a string
/*var out_c struct {
// Timestamp int `json:"timestamp"`
Songs []struct {
ID int `json:"id"`
Duration int `json:"duration"`
URL string `json:"url"`
HTTPS string `json:"https"`
CarURL string `json:"car_url"`
CarURLHTTPS string `json:"car_url_https"`
Format string `json:"format"`
Br int `json:"br"`
OverseasCopyright string `json:"overseas_copyright"`
Start int `json:"start"`
End int `json:"end"`
Group string `json:"group"`
} `json:"songs"`
IP string `json:"ip"`
Country string `json:"country"`
Region string `json:"region"`
Locationid int `json:"locationid"`
Code int `json:"code"`
Result string `json:"result"`
}
err := ztool.Net_Request(
http.MethodGet,
`https://musicpay30.kuwo.cn/audi.tion?op=query&ids=`+songMid,
nil,
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders()},
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&out_c)},
)
if err != nil {
loger.Error(`Request: %s`, err)
msg = sources.ErrHttpReq
return
}*/
// 获取音频路径
if quality != sources.Q_128k {
ourl, msg = manti(songMid, sources.Q_128k)
if msg != `` {
return
}
if i := strings.LastIndexByte(ourl, '/'); i != -1 {
if ourl[i+1:] == `2272659253.mp3` {
msg = sources.ErrNoLink
return
}
}
target_url := ztool.Str_FastConcat(
`https://mobi.kuwo.cn/mobi.s?f=web&type=convert_url`,
`&br=`, infoFile.H, infoFile.E,
`&format=`, infoFile.E,
`&rid=`, songMid,
)
var out_u, realQuality string
err := ztool.Net_Request(
http.MethodGet, target_url, nil,
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders()},
[]ztool.Net_ResHandlerFunc{func(res *http.Response) (err error) {
var data []byte
data, err = io.ReadAll(res.Body)
if err != nil {
return
}
if res.StatusCode != http.StatusOK {
return errors.New(`failed: ` + res.Status)
}
infoData := mkMap(data)
loger.Debug(`uData: %+v`, infoData)
realQuality = infoData[`bitrate`]
out_u = utils.DelQuery(infoData[`url`])
return
}},
)
if err != nil {
loger.Error(`Request: %s`, err)
msg = sources.ErrHttpReq
return
}
if realQuality != infoFile.H[:len(infoFile.H)-1] {
msg = sources.E_QNotMatch
if !env.Config.Source.ForceFallback {
return
}
}
if ourl != `` && out_u != `` {
ourl = ourl[:24] + out_u
}
return
}
resp := kw_pool.Get().(*playInfo)
defer kw_pool.Put(resp)
err := ztool.Net_Request(
http.MethodGet, `https://mobi.kuwo.cn/mobi.s?f=web&type=convert_url_with_sign&br=128kmp3&format=mp3&rid=`+songMid, nil,
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeader(desheader)},
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&resp)},
)
if err != nil {
loger.Error(`Request: %s`, err)
msg = sources.ErrHttpReq
return
}
loger.Debug(`tData: %+v`, resp)
if resp.Code != http.StatusOK {
msg = ztool.Str_FastConcat(`failed: `, resp.Msg)
loger.Debug(msg)
return
}
realQuality := strconv.Itoa(resp.Data.Bitrate)
if realQuality != infoFile.H[:len(infoFile.H)-1] && resp.Data.Bitrate != 1 {
msg = sources.E_QNotMatch
if !env.Config.Source.ForceFallback {
return
}
}
ourl = utils.DelQuery(resp.Data.URL)
return
}

View File

@ -13,7 +13,7 @@ type (
Format string `json:"format"`
P2PAudiosourceid string `json:"p2p_audiosourceid"`
Rid int `json:"rid"`
Source string `json:"source"`
// Source string `json:"source"`
URL string `json:"url"`
} `json:"data"`
Msg string `json:"msg"`

View File

@ -13,11 +13,11 @@ var (
H string // 专用音质
}{
sources.Q_128k: {
E: `mp3`,
E: sources.X_mp3,
H: sources.Q_128k,
},
sources.Q_320k: {
E: `mp3`,
E: sources.X_mp3,
H: sources.Q_320k,
},
sources.Q_flac: {
@ -40,25 +40,64 @@ var (
// `User-Agent`: `okhttp/3.10.0`,
}
bdheader = map[string]string{
`channel`: `qq`,
`channel`: `guanfang`,
`plat`: `ar`,
`net`: `wifi`,
`ver`: `3.1.2`,
// `uid`: ``,
// `devId`: `0`,
`ver`: `3.1.4`,
`api-ver`: `application/json`,
`user-agent`: `Dart/2.18 (dart:io)`, //`Dalvik/2.1.0 (Linux; U; Android 7.1.1; OPPO R9sk Build/NMF26F)`,
}
// bdsreg = regexp.MustCompile(`[^a-zA-Z0-9]`)
)
func mkMap(data []byte) map[string]string {
out := make(map[string]string)
sep := bytes.Split(data, []byte{13, 10})
for i, r := 0, len(sep); i < r; i++ {
pat := bytes.Split(sep[i], []byte{61})
if len(pat) == 2 {
var s = sep[i]
if p := bytes.IndexByte(s, '='); p != -1 {
out[bytesconv.BytesToString(s[:p])] = bytesconv.BytesToString(s[p+1:])
continue
} else {
out[`_`] += bytesconv.BytesToString(s) + `;`
}
/*pat := bytes.Split(sep[i], []byte{61})
if len(pat) >= 2 {
out[bytesconv.BytesToString(pat[0])] = bytesconv.BytesToString(pat[1])
continue
}
out[`_`] += bytesconv.BytesToString(pat[0]) + `;`
out[`_`] += bytesconv.BytesToString(pat[0]) + `;`*/
}
return out
}
// 波点签名算法
/*func Bdsign(str string, m, m2 map[string]string) *strings.Builder {
var b strings.Builder
b.WriteString(`uid=`)
b.WriteString(env.Config.Custom.Kw_Bd_Uid)
b.WriteByte('&')
b.WriteString(`token=`)
b.WriteString(env.Config.Custom.Kw_Bd_Token)
b.WriteByte('&')
b.WriteString(`timestamp=`)
b.WriteString(strconv.FormatInt(time.Now().UnixMilli(), 10))
for k, v := range m2 {
b.WriteByte('&')
b.WriteString(k)
b.WriteByte('=')
b.WriteString(url.QueryEscape(v))
}
// 取 strings.Builder.buf []byte 地址
pb := (*[]byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&b)) + unsafe.Sizeof((*strings.Builder)(nil))))
charArray := bdsreg.ReplaceAll(*pb, []byte{})
slices.Sort(charArray)
str3 := string(charArray)
fmt.Println(str3)
lowerCase := zcypt.MD5EncStr(`kuwotest` + str3 + `/api/play/music/v2/audioUrl`)
b.WriteByte('&')
b.WriteString(`sign=`)
b.WriteString(lowerCase)
return &b
}*/

View File

@ -0,0 +1,41 @@
package mg
import (
"encoding/gob"
"errors"
"lx-source/src/env"
"lx-source/src/sources"
"net/http"
"strings"
"github.com/ZxwyWebSite/ztool"
)
func init() {
gob.Register(albumInfo{})
}
func getAlbumInfo(aid string) (info albumInfo, err error) {
cquery := strings.Join([]string{`mg`, aid, `ainfo`}, `/`)
if cdata, ok := env.Cache.Get(cquery); ok {
if cinfo, ok := cdata.(albumInfo); ok {
info = cinfo
return
}
}
err = ztool.Net_Request(
http.MethodGet, ztool.Str_FastConcat(
`https://app.c.nf.migu.cn/MIGUM2.0/v1.0/content/resourceinfo.do?needSimple=01&resourceId=`, aid, `&resourceType=2003`,
), nil,
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders()},
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&info)},
)
if err == nil {
if len(info.Resource) == 0 {
err = errors.New(`no Album Resource`)
} else {
env.Cache.Set(cquery, info, sources.C_lx)
}
}
return
}

View File

@ -0,0 +1,41 @@
package mg
import (
"encoding/gob"
"errors"
"lx-source/src/env"
"lx-source/src/sources"
"net/http"
"strings"
"github.com/ZxwyWebSite/ztool"
)
func init() {
gob.Register(musicInfo{})
}
func getMusicInfo(cid string) (info musicInfo, err error) {
cquery := strings.Join([]string{`mg`, cid, `minfo`}, `/`)
if cdata, ok := env.Cache.Get(cquery); ok {
if cinfo, ok := cdata.(musicInfo); ok {
info = cinfo
return
}
}
err = ztool.Net_Request(
http.MethodGet, ztool.Str_FastConcat(
`https://c.musicapp.migu.cn/MIGUM2.0/v1.0/content/resourceinfo.do?copyrightId=`, cid, `&resourceType=2`,
), nil,
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders()},
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&info)},
)
if err == nil {
if len(info.Resource) == 0 {
err = errors.New(`no Music Resource`)
} else {
env.Cache.Set(cquery, info, sources.C_lx)
}
}
return
}

View File

@ -0,0 +1,185 @@
package mg
import (
"lx-source/src/env"
"lx-source/src/sources"
"lx-source/src/sources/custom/utils"
"lx-source/src/sources/example"
"net/http"
"sync"
"github.com/ZxwyWebSite/ztool"
)
var (
Url func(string, string) (string, string)
mg_pool *sync.Pool
)
func init() {
env.Inits.Add(func() {
loger := env.Loger.NewGroup(`MgInit`)
switch env.Config.Custom.Mg_Mode {
case `0`, `builtin`:
loger.Debug(`use builtin`)
mg_pool = &sync.Pool{New: func() any { return new(mgApi_Song) }}
Url = builtin
case `1`, `custom`:
loger.Debug(`use custom`)
if ztool.Chk_IsNilStr(
// env.Config.Custom.Mg_Usr_VerId,
// env.Config.Custom.Mg_Usr_Token,
env.Config.Custom.Mg_Usr_OSVer,
env.Config.Custom.Mg_Usr_ReqUA,
) {
loger.Fatal(`使用自定义账号且用户参数为空`)
}
mg_pool = &sync.Pool{New: func() any { return new(playInfo) }}
Url = mcustom
case `2`, `malbum`:
loger.Debug(`use malbum`)
Url = malbum
default:
loger.Fatal(`未定义的接口模式,请检查配置 [Custom].Mg_Mode`)
}
loger.Free()
})
}
func builtin(songMid, quality string) (ourl, msg string) {
loger := env.Loger.NewGroup(`Mg`)
defer loger.Free()
rquality, ok := qualitys[quality]
if !ok {
msg = sources.E_QNotSupport
return
}
resp := mg_pool.Get().(*mgApi_Song)
defer mg_pool.Put(resp)
url := ztool.Str_FastConcat(`https://`, example.Api_mg, `?copyrightId=`, songMid, `&type=`, rquality)
err := ztool.Net_Request(
http.MethodGet, url, nil,
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(example.Header_mg)},
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&resp)},
)
if err != nil {
loger.Error(`HttpReq: %s`, err)
msg = sources.ErrHttpReq
return
}
loger.Debug(`Resp: %+v`, resp)
// ourl, msg = resp.Url(rquality)
if resp.Data.PlayURL != `` {
ourl = `https:` + utils.DelQuery(resp.Data.PlayURL)
} else {
msg = ztool.Str_FastConcat(resp.Code, `: `, resp.Msg)
}
return
}
func mcustom(songMid, quality string) (ourl, msg string) {
loger := env.Loger.NewGroup(`Mg`)
defer loger.Free()
rquality, ok := qualityMap[quality]
if !ok {
msg = sources.E_QNotSupport
return
}
url := ztool.Str_FastConcat(
`https://app.c.nf.migu.cn/MIGUM2.0/strategy/listen-url/v2.4?toneFlag=`, rquality,
`&songId=`, songMid,
`&resourceType=2`,
)
resp := mg_pool.Get().(*playInfo)
defer mg_pool.Put(resp)
err := ztool.Net_Request(
http.MethodGet, url, nil,
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(map[string]string{
`User-Agent`: env.Config.Custom.Mg_Usr_ReqUA,
`aversionid`: env.Config.Custom.Mg_Usr_VerId,
`token`: env.Config.Custom.Mg_Usr_Token,
`channel`: `0146832`,
`language`: `Chinese`,
`ua`: `Android_migu`,
`mode`: `android`,
`os`: `Android ` + env.Config.Custom.Mg_Usr_OSVer,
})},
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&resp)},
)
if err != nil {
loger.Error(`Request: %s`, err)
msg = sources.ErrHttpReq
return
}
loger.Debug(`Resp: %+v`, resp)
if resp.Code != `000000` {
msg = resp.Info
return
}
if resp.Data.URL == `` {
msg = `No Data: 无返回链接`
return
}
if resp.Data.AudioFormatType != rquality {
msg = ztool.Str_FastConcat(`实际音质不匹配: `, rquality, ` <= `, resp.Data.AudioFormatType)
if !env.Config.Source.ForceFallback {
return
}
}
ourl = utils.DelQuery(resp.Data.URL)
return
}
func malbum(songMid, quality string) (ourl, msg string) {
loger := env.Loger.NewGroup(`Mg`)
defer loger.Free()
rquality, ok := qualityMap[quality]
if !ok {
msg = sources.E_QNotSupport
return
}
minfo, err := getMusicInfo(songMid)
if err != nil {
msg = err.Error()
return
}
loger.Debug(`mInfo: %+v`, minfo)
var hasQuality bool
for _, v := range minfo.Resource[0].NewRateFormats {
if hasQuality = v.FormatType == rquality; hasQuality {
break
}
}
if !hasQuality {
msg = sources.E_QNotMatch
return
}
ainfo, err := getAlbumInfo(minfo.Resource[0].AlbumID)
if err != nil {
msg = err.Error()
return
}
loger.Debug(`aInfo: %+v`, ainfo)
for _, v := range ainfo.Resource[0].SongItems {
if v.CopyrightID == songMid {
for _, w := range v.NewRateFormats {
if w.FormatType == rquality {
if rquality == `PQ` || rquality == `HQ` {
ourl = w.URL
} else {
ourl = w.AndroidURL
}
break
}
}
break
}
}
if ourl == `` {
msg = sources.E_NoLink
} else {
ourl = `https://freetyst.nf.migu.cn` + ourl[24:]
}
return
}

View File

@ -0,0 +1,37 @@
package mg
import (
"errors"
"lx-source/src/env"
"net/http"
"github.com/ZxwyWebSite/ztool"
"github.com/ZxwyWebSite/ztool/logs"
)
func refresh(loger *logs.Logger, now int64) error {
var out map[string]any
err := ztool.Net_Request(
http.MethodPost,
`https://m.music.migu.cn/migumusic/h5/user/auth/userActiveNotice`,
nil,
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(mgheader)},
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&out)},
)
if err == nil {
if out[`code`].(int) != http.StatusOK {
return errors.New(out[`msg`].(string))
} else {
loger.Info(`咪咕session保活成功`)
}
}
return err
}
func init() {
env.Inits.Add(func() {
if env.Config.Custom.Mg_Refresh_Enable && false {
env.Tasker.Add(`mg_refresh`, refresh, 86000, true)
}
})
}

View File

@ -0,0 +1,533 @@
package mg
type playInfo struct {
Code string `json:"code"`
Data struct {
AudioFormatType string `json:"audioFormatType"`
FreeListenType string `json:"freeListenType"`
HaveVisualMv bool `json:"haveVisualMv"`
LrcURL string `json:"lrcUrl"`
MrcURL string `json:"mrcUrl"`
Song struct {
Album string `json:"album"`
AlbumID string `json:"albumId"`
AlbumPinyin string `json:"albumPinyin"`
ContentID string `json:"contentId"`
CopyrightID string `json:"copyrightId"`
CopyrightType int `json:"copyrightType"`
Duration int `json:"duration"`
ForeverListen bool `json:"foreverListen"`
HaveShockRing int `json:"haveShockRing"`
Img1 string `json:"img1"`
Img2 string `json:"img2"`
Img3 string `json:"img3"`
MvCopyrightType int `json:"mvCopyrightType"`
ResourceType string `json:"resourceType"`
RestrictType int `json:"restrictType"`
RingToneID string `json:"ringToneId"`
ShowTags []string `json:"showTags"`
SingerList []struct {
ID string `json:"id"`
Img string `json:"img"`
Name string `json:"name"`
NameSpelling string `json:"nameSpelling"`
} `json:"singerList"`
SongID string `json:"songId"`
SongName string `json:"songName"`
SongPinyin string `json:"songPinyin"`
} `json:"song"`
TrcURL string `json:"trcUrl"`
URL string `json:"url"`
Version string `json:"version"`
} `json:"data"`
Info string `json:"info"`
}
// func (resp *playInfo) Url(rquality string) (ourl, emsg string) {
// if resp.Code != `000000` {
// emsg = resp.Info
// return
// }
// if resp.Data.URL == `` {
// emsg = `No Data: 无返回链接`
// return
// }
// if resp.Data.AudioFormatType != rquality {
// emsg = ztool.Str_FastConcat(`实际音质不匹配: `, rquality, ` <= `, resp.Data.AudioFormatType)
// if !env.Config.Source.ForceFallback {
// return
// }
// }
// ourl = utils.DelQuery(resp.Data.URL)
// return
// }
type mgApi_Song struct {
Code string `json:"code"`
Msg string `json:"msg"`
Data struct {
PlayURL string `json:"playUrl"`
FormatID string `json:"formatId"`
SalePrice string `json:"salePrice"`
BizType string `json:"bizType"`
BizCode string `json:"bizCode"`
AuditionsLength int `json:"auditionsLength"`
} `json:"data"`
}
// func (resp *mgApi_Song) Url(string) (ourl, emsg string) {
// if resp.Data.PlayURL != `` {
// ourl = `https:` + utils.DelQuery(resp.Data.PlayURL)
// } else {
// emsg = ztool.Str_FastConcat(resp.Code, `: `, resp.Msg)
// }
// return
// }
type musicInfo struct {
// Code string `json:"code"`
// Info string `json:"info"`
Resource []struct {
// ResourceType string `json:"resourceType"`
// RefID string `json:"refId"`
// CopyrightID string `json:"copyrightId"`
// ContentID string `json:"contentId"`
// SongID string `json:"songId"`
// SongName string `json:"songName"`
// SingerID string `json:"singerId"`
// Singer string `json:"singer"`
AlbumID string `json:"albumId"`
// Album string `json:"album"`
// AlbumImgs []struct {
// ImgSizeType string `json:"imgSizeType"`
// Img string `json:"img"`
// FileID string `json:"fileId"`
// WebpImg string `json:"webpImg"`
// } `json:"albumImgs"`
// OpNumItem struct {
// PlayNum int `json:"playNum"`
// PlayNumDesc string `json:"playNumDesc"`
// KeepNum int `json:"keepNum"`
// KeepNumDesc string `json:"keepNumDesc"`
// CommentNum int `json:"commentNum"`
// CommentNumDesc string `json:"commentNumDesc"`
// ShareNum int `json:"shareNum"`
// ShareNumDesc string `json:"shareNumDesc"`
// OrderNumByWeek int `json:"orderNumByWeek"`
// OrderNumByWeekDesc string `json:"orderNumByWeekDesc"`
// OrderNumByTotal int `json:"orderNumByTotal"`
// OrderNumByTotalDesc string `json:"orderNumByTotalDesc"`
// ThumbNum int `json:"thumbNum"`
// ThumbNumDesc string `json:"thumbNumDesc"`
// FollowNum int `json:"followNum"`
// FollowNumDesc string `json:"followNumDesc"`
// SubscribeNum int `json:"subscribeNum"`
// SubscribeNumDesc string `json:"subscribeNumDesc"`
// LivePlayNum int `json:"livePlayNum"`
// LivePlayNumDesc string `json:"livePlayNumDesc"`
// PopularNum int `json:"popularNum"`
// PopularNumDesc string `json:"popularNumDesc"`
// BookingNum int `json:"bookingNum"`
// BookingNumDesc string `json:"bookingNumDesc"`
// SettingNum int `json:"settingNum"`
// SettingNumDesc string `json:"settingNumDesc"`
// CallNum int `json:"callNum"`
// CallNumDesc string `json:"callNumDesc"`
// CallingPlayNum int `json:"callingPlayNum"`
// CallingPlayNumDesc string `json:"callingPlayNumDesc"`
// CallingPlayDuration int `json:"callingPlayDuration"`
// CallingPlayDurationDesc string `json:"callingPlayDurationDesc"`
// CalledPlayDuration int `json:"calledPlayDuration"`
// CalledPlayDurationDesc string `json:"calledPlayDurationDesc"`
// RingtoneAppPlayNum int `json:"ringtoneAppPlayNum"`
// RingtoneAppPlayNumDesc string `json:"ringtoneAppPlayNumDesc"`
// RingtoneAppSettingNum int `json:"ringtoneAppSettingNum"`
// RingtoneAppSettingNumDesc string `json:"ringtoneAppSettingNumDesc"`
// } `json:"opNumItem"`
// ToneControl string `json:"toneControl"`
// RelatedSongs []struct {
// ResourceType string `json:"resourceType"`
// ResourceTypeName string `json:"resourceTypeName"`
// CopyrightID string `json:"copyrightId"`
// ProductID string `json:"productId"`
// } `json:"relatedSongs"`
// RateFormats []struct {
// ResourceType string `json:"resourceType"`
// FormatType string `json:"formatType"`
// Format string `json:"format"`
// Size string `json:"size"`
// FileType string `json:"fileType,omitempty"`
// Price string `json:"price"`
// AndroidFileType string `json:"androidFileType,omitempty"`
// IosFileType string `json:"iosFileType,omitempty"`
// IosSize string `json:"iosSize,omitempty"`
// AndroidSize string `json:"androidSize,omitempty"`
// IosFormat string `json:"iosFormat,omitempty"`
// AndroidFormat string `json:"androidFormat,omitempty"`
// IosAccuracyLevel string `json:"iosAccuracyLevel,omitempty"`
// AndroidAccuracyLevel string `json:"androidAccuracyLevel,omitempty"`
// } `json:"rateFormats"`
NewRateFormats []struct {
// ResourceType string `json:"resourceType"`
FormatType string `json:"formatType"`
// Format string `json:"format"`
// Size string `json:"size"`
// FileType string `json:"fileType,omitempty"`
// Price string `json:"price"`
// AndroidFileType string `json:"androidFileType,omitempty"`
// IosFileType string `json:"iosFileType,omitempty"`
// IosSize string `json:"iosSize,omitempty"`
// AndroidSize string `json:"androidSize,omitempty"`
// IosFormat string `json:"iosFormat,omitempty"`
// AndroidFormat string `json:"androidFormat,omitempty"`
// IosAccuracyLevel string `json:"iosAccuracyLevel,omitempty"`
// AndroidAccuracyLevel string `json:"androidAccuracyLevel,omitempty"`
} `json:"newRateFormats"`
// LrcURL string `json:"lrcUrl"`
// TagList []struct {
// ResourceType string `json:"resourceType"`
// TagID string `json:"tagId"`
// TagName string `json:"tagName"`
// TagDesc string `json:"tagDesc,omitempty"`
// } `json:"tagList"`
// DigitalColumnID string `json:"digitalColumnId"`
// Copyright string `json:"copyright"`
// ValidStatus bool `json:"validStatus"`
// SongDescs string `json:"songDescs"`
// SongAliasName string `json:"songAliasName"`
// IsInDAlbum string `json:"isInDAlbum"`
// IsInSideDalbum string `json:"isInSideDalbum"`
// IsInSalesPeriod string `json:"isInSalesPeriod"`
// SongType string `json:"songType"`
// MrcURL string `json:"mrcUrl"`
// InvalidateDate string `json:"invalidateDate"`
// DalbumID string `json:"dalbumId"`
// TrcURL string `json:"trcUrl"`
// VipType string `json:"vipType"`
// ScopeOfcopyright string `json:"scopeOfcopyright"`
// AuditionsType string `json:"auditionsType"`
// FirstIcon string `json:"firstIcon"`
// TranslateName string `json:"translateName"`
// ChargeAuditions string `json:"chargeAuditions"`
// OldChargeAuditions string `json:"oldChargeAuditions"`
// SongIcon string `json:"songIcon"`
// CodeRate struct {
// PQ struct {
// CodeRateChargeAuditions string `json:"codeRateChargeAuditions"`
// IsCodeRateDownload string `json:"isCodeRateDownload"`
// CodeRateFileSize string `json:"codeRateFileSize"`
// } `json:"PQ"`
// HQ struct {
// CodeRateChargeAuditions string `json:"codeRateChargeAuditions"`
// IsCodeRateDownload string `json:"isCodeRateDownload"`
// } `json:"HQ"`
// SQ struct {
// CodeRateChargeAuditions string `json:"codeRateChargeAuditions"`
// IsCodeRateDownload string `json:"isCodeRateDownload"`
// ContentIDSQ string `json:"contentIdSQ"`
// } `json:"SQ"`
// } `json:"codeRate"`
// IsDownload string `json:"isDownload"`
// CopyrightType string `json:"copyrightType"`
// HasMv string `json:"hasMv"`
// TopQuality string `json:"topQuality"`
// PreSale string `json:"preSale"`
// IsShare string `json:"isShare"`
// IsCollection string `json:"isCollection"`
// Length string `json:"length"`
// SingerImg struct {
// Num421 struct {
// SingerName string `json:"singerName"`
// MiguImgItems []struct {
// ImgSizeType string `json:"imgSizeType"`
// Img string `json:"img"`
// FileID string `json:"fileId"`
// WebpImg string `json:"webpImg"`
// } `json:"miguImgItems"`
// } `json:"421"`
// } `json:"singerImg"`
// SongNamePinyin string `json:"songNamePinyin"`
// AlbumNamePinyin string `json:"albumNamePinyin"`
// Artists []struct {
// ID string `json:"id"`
// Name string `json:"name"`
// NameSpelling string `json:"nameSpelling"`
// } `json:"artists"`
// LandscapImg string `json:"landscapImg"`
// VipLogo string `json:"vipLogo"`
// VipDownload string `json:"vipDownload"`
// FirstPublish string `json:"firstPublish"`
// ShowTag []string `json:"showTag"`
// MaterialValidStatus bool `json:"materialValidStatus"`
// NeedEncrypt string `json:"needEncrypt"`
// ForeverListen bool `json:"foreverListen"`
// HasAssociatedRing bool `json:"hasAssociatedRing"`
} `json:"resource"`
}
type albumInfo struct {
// Code string `json:"code"`
// Info string `json:"info"`
Resource []struct {
// ResourceType string `json:"resourceType"`
// AlbumID string `json:"albumId"`
// ImgItems []struct {
// ImgSizeType string `json:"imgSizeType"`
// Img string `json:"img"`
// FileID string `json:"fileId"`
// WebpImg string `json:"webpImg"`
// } `json:"imgItems"`
// Title string `json:"title"`
// Singer string `json:"singer"`
// SingerID string `json:"singerId"`
// SingerImgs []struct {
// ImgSizeType string `json:"imgSizeType"`
// Img string `json:"img"`
// FileID string `json:"fileId"`
// WebpImg string `json:"webpImg"`
// } `json:"singerImgs"`
// Summary string `json:"summary"`
// TotalCount string `json:"totalCount"`
// PublishTime string `json:"publishTime"`
// PublishCorp string `json:"publishCorp"`
// OpNumItem struct {
// PlayNum int `json:"playNum"`
// PlayNumDesc string `json:"playNumDesc"`
// KeepNum int `json:"keepNum"`
// KeepNumDesc string `json:"keepNumDesc"`
// CommentNum int `json:"commentNum"`
// CommentNumDesc string `json:"commentNumDesc"`
// ShareNum int `json:"shareNum"`
// ShareNumDesc string `json:"shareNumDesc"`
// OrderNumByWeek int `json:"orderNumByWeek"`
// OrderNumByWeekDesc string `json:"orderNumByWeekDesc"`
// OrderNumByTotal int `json:"orderNumByTotal"`
// OrderNumByTotalDesc string `json:"orderNumByTotalDesc"`
// ThumbNum int `json:"thumbNum"`
// ThumbNumDesc string `json:"thumbNumDesc"`
// FollowNum int `json:"followNum"`
// FollowNumDesc string `json:"followNumDesc"`
// SubscribeNum int `json:"subscribeNum"`
// SubscribeNumDesc string `json:"subscribeNumDesc"`
// LivePlayNum int `json:"livePlayNum"`
// LivePlayNumDesc string `json:"livePlayNumDesc"`
// PopularNum int `json:"popularNum"`
// PopularNumDesc string `json:"popularNumDesc"`
// BookingNum int `json:"bookingNum"`
// BookingNumDesc string `json:"bookingNumDesc"`
// SettingNum int `json:"settingNum"`
// SettingNumDesc string `json:"settingNumDesc"`
// CallNum int `json:"callNum"`
// CallNumDesc string `json:"callNumDesc"`
// CallingPlayNum int `json:"callingPlayNum"`
// CallingPlayNumDesc string `json:"callingPlayNumDesc"`
// CallingPlayDuration int `json:"callingPlayDuration"`
// CallingPlayDurationDesc string `json:"callingPlayDurationDesc"`
// CalledPlayDuration int `json:"calledPlayDuration"`
// CalledPlayDurationDesc string `json:"calledPlayDurationDesc"`
// RingtoneAppPlayNum int `json:"ringtoneAppPlayNum"`
// RingtoneAppPlayNumDesc string `json:"ringtoneAppPlayNumDesc"`
// RingtoneAppSettingNum int `json:"ringtoneAppSettingNum"`
// RingtoneAppSettingNumDesc string `json:"ringtoneAppSettingNumDesc"`
// } `json:"opNumItem"`
// Tags []struct {
// ResourceType string `json:"resourceType"`
// TagID string `json:"tagId"`
// TagName string `json:"tagName"`
// TagDesc string `json:"tagDesc,omitempty"`
// } `json:"tags"`
// AlbumAliasName string `json:"albumAliasName"`
// AlbumClass string `json:"albumClass"`
// Language string `json:"language"`
// PublishCompany string `json:"publishCompany"`
// PublishDate string `json:"publishDate"`
// TranslateName string `json:"translateName"`
SongItems []struct {
// ResourceType string `json:"resourceType"`
// RefID string `json:"refId"`
CopyrightID string `json:"copyrightId"`
// ContentID string `json:"contentId"`
// SongID string `json:"songId"`
// SongName string `json:"songName"`
// SingerID string `json:"singerId"`
// Singer string `json:"singer"`
// AlbumID string `json:"albumId"`
// Album string `json:"album"`
// AlbumImgs []struct {
// ImgSizeType string `json:"imgSizeType"`
// Img string `json:"img"`
// FileID string `json:"fileId"`
// WebpImg string `json:"webpImg"`
// } `json:"albumImgs"`
// OpNumItem struct {
// PlayNum int `json:"playNum"`
// PlayNumDesc string `json:"playNumDesc"`
// KeepNum int `json:"keepNum"`
// KeepNumDesc string `json:"keepNumDesc"`
// CommentNum int `json:"commentNum"`
// CommentNumDesc string `json:"commentNumDesc"`
// ShareNum int `json:"shareNum"`
// ShareNumDesc string `json:"shareNumDesc"`
// OrderNumByWeek int `json:"orderNumByWeek"`
// OrderNumByWeekDesc string `json:"orderNumByWeekDesc"`
// OrderNumByTotal int `json:"orderNumByTotal"`
// OrderNumByTotalDesc string `json:"orderNumByTotalDesc"`
// ThumbNum int `json:"thumbNum"`
// ThumbNumDesc string `json:"thumbNumDesc"`
// FollowNum int `json:"followNum"`
// FollowNumDesc string `json:"followNumDesc"`
// SubscribeNum int `json:"subscribeNum"`
// SubscribeNumDesc string `json:"subscribeNumDesc"`
// LivePlayNum int `json:"livePlayNum"`
// LivePlayNumDesc string `json:"livePlayNumDesc"`
// PopularNum int `json:"popularNum"`
// PopularNumDesc string `json:"popularNumDesc"`
// BookingNum int `json:"bookingNum"`
// BookingNumDesc string `json:"bookingNumDesc"`
// SettingNum int `json:"settingNum"`
// SettingNumDesc string `json:"settingNumDesc"`
// CallNum int `json:"callNum"`
// CallNumDesc string `json:"callNumDesc"`
// CallingPlayNum int `json:"callingPlayNum"`
// CallingPlayNumDesc string `json:"callingPlayNumDesc"`
// CallingPlayDuration int `json:"callingPlayDuration"`
// CallingPlayDurationDesc string `json:"callingPlayDurationDesc"`
// CalledPlayDuration int `json:"calledPlayDuration"`
// CalledPlayDurationDesc string `json:"calledPlayDurationDesc"`
// RingtoneAppPlayNum int `json:"ringtoneAppPlayNum"`
// RingtoneAppPlayNumDesc string `json:"ringtoneAppPlayNumDesc"`
// RingtoneAppSettingNum int `json:"ringtoneAppSettingNum"`
// RingtoneAppSettingNumDesc string `json:"ringtoneAppSettingNumDesc"`
// } `json:"opNumItem"`
// ToneControl string `json:"toneControl"`
// RelatedSongs []struct {
// ResourceType string `json:"resourceType"`
// ResourceTypeName string `json:"resourceTypeName"`
// CopyrightID string `json:"copyrightId"`
// ProductID string `json:"productId"`
// } `json:"relatedSongs"`
// RateFormats []struct {
// ResourceType string `json:"resourceType"`
// FormatType string `json:"formatType"`
// URL string `json:"url,omitempty"`
// Format string `json:"format"`
// Size string `json:"size"`
// FileType string `json:"fileType,omitempty"`
// Price string `json:"price"`
// IosURL string `json:"iosUrl,omitempty"`
// AndroidURL string `json:"androidUrl,omitempty"`
// AndroidFileType string `json:"androidFileType,omitempty"`
// IosFileType string `json:"iosFileType,omitempty"`
// IosSize string `json:"iosSize,omitempty"`
// AndroidSize string `json:"androidSize,omitempty"`
// IosFormat string `json:"iosFormat,omitempty"`
// AndroidFormat string `json:"androidFormat,omitempty"`
// IosAccuracyLevel string `json:"iosAccuracyLevel,omitempty"`
// AndroidAccuracyLevel string `json:"androidAccuracyLevel,omitempty"`
// } `json:"rateFormats"`
NewRateFormats []struct {
// ResourceType string `json:"resourceType"`
FormatType string `json:"formatType"`
URL string `json:"url,omitempty"`
// Format string `json:"format"`
// Size string `json:"size"`
// FileType string `json:"fileType,omitempty"`
// Price string `json:"price"`
// IosURL string `json:"iosUrl,omitempty"`
AndroidURL string `json:"androidUrl,omitempty"`
// AndroidFileType string `json:"androidFileType,omitempty"`
// IosFileType string `json:"iosFileType,omitempty"`
// IosSize string `json:"iosSize,omitempty"`
// AndroidSize string `json:"androidSize,omitempty"`
// IosFormat string `json:"iosFormat,omitempty"`
// AndroidFormat string `json:"androidFormat,omitempty"`
// IosAccuracyLevel string `json:"iosAccuracyLevel,omitempty"`
// AndroidAccuracyLevel string `json:"androidAccuracyLevel,omitempty"`
} `json:"newRateFormats"`
// LrcURL string `json:"lrcUrl"`
// TagList []struct {
// ResourceType string `json:"resourceType"`
// TagID string `json:"tagId"`
// TagName string `json:"tagName"`
// TagDesc string `json:"tagDesc,omitempty"`
// } `json:"tagList"`
// DigitalColumnID string `json:"digitalColumnId"`
// Copyright string `json:"copyright"`
// ValidStatus bool `json:"validStatus"`
// SongDescs string `json:"songDescs"`
// SongAliasName string `json:"songAliasName"`
// IsInDAlbum string `json:"isInDAlbum"`
// IsInSideDalbum string `json:"isInSideDalbum"`
// IsInSalesPeriod string `json:"isInSalesPeriod"`
// SongType string `json:"songType"`
// MrcURL string `json:"mrcUrl"`
// InvalidateDate string `json:"invalidateDate"`
// DalbumID string `json:"dalbumId"`
// TrackNumber string `json:"trackNumber"`
// TrcURL string `json:"trcUrl"`
// Disc string `json:"disc"`
// VipType string `json:"vipType"`
// ScopeOfcopyright string `json:"scopeOfcopyright"`
// AuditionsType string `json:"auditionsType"`
// FirstIcon string `json:"firstIcon"`
// TranslateName string `json:"translateName,omitempty"`
// ChargeAuditions string `json:"chargeAuditions"`
// OldChargeAuditions string `json:"oldChargeAuditions"`
// SongIcon string `json:"songIcon"`
// CodeRate struct {
// PQ struct {
// CodeRateChargeAuditions string `json:"codeRateChargeAuditions"`
// IsCodeRateDownload string `json:"isCodeRateDownload"`
// CodeRateFileSize string `json:"codeRateFileSize"`
// } `json:"PQ"`
// HQ struct {
// CodeRateChargeAuditions string `json:"codeRateChargeAuditions"`
// IsCodeRateDownload string `json:"isCodeRateDownload"`
// } `json:"HQ"`
// SQ struct {
// CodeRateChargeAuditions string `json:"codeRateChargeAuditions"`
// IsCodeRateDownload string `json:"isCodeRateDownload"`
// ContentIDSQ string `json:"contentIdSQ"`
// } `json:"SQ"`
// } `json:"codeRate"`
// IsDownload string `json:"isDownload"`
// CopyrightType string `json:"copyrightType"`
// HasMv string `json:"hasMv"`
// TopQuality string `json:"topQuality"`
// PreSale string `json:"preSale"`
// IsShare string `json:"isShare"`
// IsCollection string `json:"isCollection"`
// Length string `json:"length"`
// SingerImg struct {
// Num421 struct {
// SingerName string `json:"singerName"`
// MiguImgItems []struct {
// ImgSizeType string `json:"imgSizeType"`
// Img string `json:"img"`
// FileID string `json:"fileId"`
// WebpImg string `json:"webpImg"`
// } `json:"miguImgItems"`
// } `json:"421"`
// } `json:"singerImg"`
// SongNamePinyin string `json:"songNamePinyin"`
// AlbumNamePinyin string `json:"albumNamePinyin"`
// Artists []struct {
// ID string `json:"id"`
// Name string `json:"name"`
// NameSpelling string `json:"nameSpelling"`
// } `json:"artists"`
// LandscapImg string `json:"landscapImg"`
// VipLogo string `json:"vipLogo"`
// VipDownload string `json:"vipDownload"`
// FirstPublish string `json:"firstPublish"`
// ShowTag []string `json:"showTag"`
// MaterialValidStatus bool `json:"materialValidStatus"`
// NeedEncrypt string `json:"needEncrypt"`
// ForeverListen bool `json:"foreverListen"`
// HasAssociatedRing bool `json:"hasAssociatedRing"`
// LoginListenFlag string `json:"loginListenFlag,omitempty"`
// MvCopyright string `json:"mvCopyright,omitempty"`
// ForeverListenFlag string `json:"foreverListenFlag,omitempty"`
} `json:"songItems"`
} `json:"resource"`
}

View File

@ -0,0 +1,46 @@
package mg
import "lx-source/src/sources"
// const (
// q_128k = `PQ`
// q_320k = `HQ`
// q_flac = `SQ`
// q_fl24 = `ZQ`
// )
var (
qualityMap = map[string]string{
sources.Q_128k: `PQ`,
sources.Q_320k: `HQ`,
sources.Q_flac: `SQ`,
sources.Q_fl24: `ZQ`,
}
// qualityMapReverse = map[string]string{
// q_128k: sources.Q_128k,
// q_320k: sources.Q_320k,
// q_flac: sources.Q_flac,
// q_fl24: sources.Q_fl24,
// }
qualitys = map[string]string{
sources.Q_128k: `1`,
sources.Q_320k: `2`,
sources.Q_flac: `3`,
sources.Q_fl24: `4`,
sources.Q_master: `5`,
}
// qualitysReverse = map[string]string {
// `000009`: sources.Q_128k,
// `020010`: sources.Q_320k,
// `011002`: sources.Q_flac,
// `011005`: sources.Q_fl24,
// }
mgheader = map[string]string{
`Origin`: `https://m.music.migu.cn`,
`Referer`: `https://m.music.migu.cn/v4/`,
`By`: ``,
`channel`: ``,
`Cookie`: ``,
}
)

View File

@ -1,11 +1,5 @@
package tx
import (
"lx-source/src/env"
"github.com/ZxwyWebSite/ztool/logs"
)
// func Info(songMid string) (info any, msg string) {
// req, emsg := getMusicInfo(songMid)
// if emsg != `` {
@ -29,14 +23,3 @@ import (
// }
// return
// }
func init() {
env.Inits.Add(func() {
if env.Config.Custom.Tx_Refresh_Enable {
env.Tasker.Add(`refresh_login`, func(l *logs.Logger) error {
refresh(l)
return nil
}, 86000, true)
}
})
}

View File

@ -0,0 +1,355 @@
package tx
import (
"bytes"
"errors"
"fmt"
"io"
"lx-source/src/env"
"math/rand"
"net/http"
"net/http/cookiejar"
"net/url"
"strconv"
"strings"
"time"
_ "unsafe"
"github.com/ZxwyWebSite/ztool"
"github.com/ZxwyWebSite/ztool/logs"
"github.com/ZxwyWebSite/ztool/x/json"
"github.com/google/uuid"
)
//go:linkname request github.com/ZxwyWebSite/ztool.request
func request(client *http.Client, method, url string, body io.Reader, reqh []ztool.Net_ReqHandlerFunc, resh []ztool.Net_ResHandlerFunc) error
// QQ快速登录 - 直接使用本机已登录账号
func Qlogin_graph(l *logs.Logger) error {
// 参考文章: https://learnku.com/articles/33970
jar, _ := cookiejar.New(nil)
client := &http.Client{
Timeout: time.Second * 10,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
Jar: jar,
}
// Step0 - 获取本机QQ服务地址
// (应该没有人会占用4301端口导致qq切换备用端口吧先不写了...懒)
err := request(
client, http.MethodGet, `https://localhost.ptlogin2.qq.com:4301/pc_querystatus`, nil,
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders()},
[]ztool.Net_ResHandlerFunc{func(res *http.Response) error {
_, err := io.Copy(io.Discard, res.Body)
return err
}},
)
if err != nil {
return errors.New(`step0: 无法连接本机QQ服务`)
}
// Step1 - 获取 pt_local_token
var pt_local_token string
err = request(
client, http.MethodGet,
`https://xui.ptlogin2.qq.com/cgi-bin/xlogin?appid=716027609&daid=383&style=33&login_text=%E7%99%BB%E5%BD%95&hide_title_bar=1&hide_border=1&target=self&s_url=https://graph.qq.com/oauth2.0/login_jump&pt_3rd_aid=100497308&pt_feedback_link=https://support.qq.com/products/77942?customInfo=.appid100497308&theme=2&verify_theme=`,
nil,
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(map[string]string{
`Referer`: `https://graph.qq.com/`,
})},
[]ztool.Net_ResHandlerFunc{func(res *http.Response) error {
for _, v := range res.Cookies() {
if v.Name == `pt_local_token` {
pt_local_token = v.Value
break
}
}
_, err := io.Copy(io.Discard, res.Body)
return err
}},
)
if err != nil {
return err
}
l.Info(`pt_local_token: %v`, pt_local_token)
// Step2 - 获取本机登录的 QQ 号
var url2 strings.Builder
url2.WriteString(`https://localhost.ptlogin2.qq.com:4301/pt_get_uins?callback=ptui_getuins_CB&r=`)
url2.WriteString(strconv.FormatFloat(rand.Float64(), 'f', -1, 64))
url2.WriteString(`&pt_local_tk=`)
url2.WriteString(pt_local_token)
var out2 []struct {
Uin int `json:"uin"`
// FaceIndex int `json:"face_index"`
// Gender int `json:"gender"`
Nickname string `json:"nickname"`
// ClientType int `json:"client_type"`
// UinFlag int `json:"uin_flag"`
// Account int `json:"account"`
}
var header2 = map[string]string{
`Referer`: `https://xui.ptlogin2.qq.com/`,
// `Cookie`: `pt_local_token=` + pt_local_token,
}
err = request(
client, http.MethodGet, url2.String(), nil,
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(header2)},
[]ztool.Net_ResHandlerFunc{func(res *http.Response) error {
data, err := io.ReadAll(res.Body)
if err != nil {
return err
}
sep_b := bytes.IndexByte(data, '[')
sep_e := bytes.LastIndexByte(data, ']')
if sep_b == -1 || sep_e == -1 {
return errors.New(`step2: 无法解析返回数据`)
}
sep := data[sep_b : sep_e+1]
return json.Unmarshal(sep, &out2)
}},
)
if err != nil {
return err
}
var uin string
length := len(out2)
switch length {
case 0:
return errors.New(`step2: 无可用账号`)
case 1:
uin = strconv.Itoa(out2[0].Uin)
default:
fmt.Println(`请选择要登录的账号:`)
for i, v := range out2 {
fmt.Println(i, v.Nickname, v.Uin)
}
for {
fmt.Print(`输入序号: `)
var input string
fmt.Scanln(&input)
i, err := strconv.Atoi(input)
if err != nil {
l.Error(`err: %v`, err)
continue
}
if i >= length {
l.Error(`err: 下标越界`)
continue
}
uin = strconv.Itoa(out2[i].Uin)
break
}
}
l.Info(`uin: %v`, uin)
// Step3 - 获取 clientkey
var url3 strings.Builder
url3.WriteString(`https://localhost.ptlogin2.qq.com:4301/pt_get_st?clientuin=`)
url3.WriteString(uin)
url3.WriteString(`&r=`)
url3.WriteString(strconv.FormatFloat(rand.Float64(), 'f', -1, 64))
url3.WriteString(`&pt_local_tk=`)
url3.WriteString(pt_local_token)
url3.WriteString(`&callback=__jp0`)
// var clientkey string
err = request(
client, http.MethodGet, url3.String(), nil,
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(header2)},
[]ztool.Net_ResHandlerFunc{func(res *http.Response) error {
/*for _, v := range res.Cookies() {
if v.Name == `clientkey` {
clientkey = v.Value
break
}
}*/
_, err := io.Copy(io.Discard, res.Body)
return err
}},
)
if err != nil {
return err
}
// Step4 - 获取 skey
var url4 strings.Builder
url4.WriteString(`https://ssl.ptlogin2.qq.com/jump?clientuin=`)
url4.WriteString(uin)
url4.WriteString(`&keyindex=9&pt_aid=716027609&daid=383&u1=https://graph.qq.com/oauth2.0/login_jump&pt_local_tk=`)
url4.WriteString(pt_local_token)
url4.WriteString(`&pt_3rd_aid=100497308&ptopt=1&style=40`)
/*var cookie4 strings.Builder
cookie4.WriteString(`pt_local_token=`)
cookie4.WriteString(pt_local_token)
cookie4.WriteByte(';')
cookie4.WriteString(`clientuin=`)
cookie4.WriteString(uin)
cookie4.WriteByte(';')
cookie4.WriteString(`clientkey=`)
cookie4.WriteString(clientkey)*/
var jurl string
err = request(
client, http.MethodGet, url4.String(), nil,
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders( /*map[string]string{
`Referer`: `https://xui.ptlogin2.qq.com/`,
`Cookie`: cookie4.String(),
}*/header2)},
[]ztool.Net_ResHandlerFunc{func(res *http.Response) error {
data, err := io.ReadAll(res.Body)
if err != nil {
return err
}
sep_b := bytes.IndexByte(data, ',')
sep_e := bytes.LastIndexByte(data, ' ')
if sep_b == -1 || sep_e == -1 {
return errors.New(`step4: 无法解析返回数据`)
}
jurl = string(data[sep_b+3 : sep_e-2])
return nil
}},
)
if err != nil {
return err
}
// Step5 - 获取 p_skey
var p_skey string
err = request(
client, http.MethodGet, jurl, nil,
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(header2)},
[]ztool.Net_ResHandlerFunc{func(res *http.Response) error {
for _, v := range res.Cookies() {
if v.Name == `p_skey` && v.Value != `` {
p_skey = v.Value
break
}
}
_, err := io.Copy(io.Discard, res.Body)
return err
}},
)
if err != nil {
return err
}
// Step6 - 登录账号
getGtk := func(skey string) string {
var hash = 5381
for _, v := range skey {
hash += (hash << 5) + int(v)
}
return strconv.Itoa(hash & 0x7fffffff)
}
now := time.Now()
var authcode string
err = request(
client, http.MethodPost,
`https://graph.qq.com/oauth2.0/authorize`,
strings.NewReader(ztool.Str_FastConcat(
`response_type=code&client_id=100497308&redirect_uri=https%3A%2F%2Fy.qq.com%2Fportal%2Fwx_redirect.html%3Flogin_type%3D1%26surl%3Dhttps%3A%2F%2Fy.qq.com%2F&scope=get_user_info%2Cget_app_friends&state=state&switch=&from_ptlogin=1&src=1&update_auth=1&openapi=1010_1030`,
`&g_tk=`, getGtk(p_skey),
`&auth_time=`, strconv.FormatInt(now.UnixMilli(), 10),
`&ui=`, strings.ToUpper(uuid.NewString()),
)),
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(map[string]string{
`Referer`: `https://graph.qq.com/oauth2.0/show?which=Login&display=pc&response_type=code&client_id=100497308&redirect_uri=https%3A%2F%2Fy.qq.com%2Fportal%2Fwx_redirect.html%3Flogin_type%3D1%26surl%3Dhttps%3A%2F%2Fy.qq.com%2F&state=state&display=pc&scope=get_user_info%2Cget_app_friends`,
`Content-Type`: `application/x-www-form-urlencoded`,
})},
[]ztool.Net_ResHandlerFunc{func(res *http.Response) error {
/*if res.StatusCode != 302 {
return errors.New(`step6: not redirect`)
}*/
location := res.Header[`Location`][0]
l.Info(`loc: %v`, location)
loc, err := url.Parse(location)
if err != nil {
return err
}
authcode = loc.Query()[`code`][0]
return nil
}},
)
if err != nil {
return err
}
l.Info(`authcode: %v`, authcode)
var out6 struct {
Code int `json:"code"`
// Ts int64 `json:"ts"`
// StartTs int64 `json:"start_ts"`
// Traceid string `json:"traceid"`
Req struct {
Code int `json:"code"`
Data struct {
// Openid string `json:"openid"`
RefreshToken string `json:"refresh_token"`
AccessToken string `json:"access_token"`
ExpiredAt int `json:"expired_at"`
// Musicid int `json:"musicid"`
Musickey string `json:"musickey"`
// MusickeyCreateTime int `json:"musickeyCreateTime"`
// FirstLogin int `json:"first_login"`
// ErrMsg string `json:"errMsg"`
// SessionKey string `json:"sessionKey"`
// Unionid string `json:"unionid"`
StrMusicid string `json:"str_musicid"`
// Errtip string `json:"errtip"`
// Nick string `json:"nick"`
// Logo string `json:"logo"`
// FeedbackURL string `json:"feedbackURL"`
// EncryptUin string `json:"encryptUin"`
// Userip string `json:"userip"`
// LastLoginTime int `json:"lastLoginTime"`
// KeyExpiresIn int `json:"keyExpiresIn"`
// RefreshKey string `json:"refresh_key"`
// LoginType int `json:"loginType"`
// Prompt2Bind int `json:"prompt2bind"`
// LogoffStatus int `json:"logoffStatus"`
// OtherAccounts []interface{} `json:"otherAccounts"`
// OtherPhoneNo string `json:"otherPhoneNo"`
// Token string `json:"token"`
// IsPrized int `json:"isPrized"`
// IsShowDevManage int `json:"isShowDevManage"`
// ErrTip2 string `json:"errTip2"`
// Tip3 string `json:"tip3"`
// EncryptedPhoneNo string `json:"encryptedPhoneNo"`
// PhoneNo string `json:"phoneNo"`
// BindAccountType int `json:"bindAccountType"`
// NeedRefreshKeyIn int `json:"needRefreshKeyIn"`
} `json:"data"`
} `json:"req"`
}
err = request(
client, http.MethodPost,
`https://u.y.qq.com/cgi-bin/musicu.fcg`,
strings.NewReader(ztool.Str_FastConcat(
`{"comm":{"g_tk":5381,"platform":"yqq","ct":24,"cv":0},"req":{"module":"QQConnectLogin.LoginServer","method":"QQLogin","param":{"code":"`, authcode, `"}}}`,
)),
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(map[string]string{
`Referer`: `https://y.qq.com/`,
`Content-Type`: `application/x-www-form-urlencoded`,
})},
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&out6)},
)
if err != nil {
return err
}
l.Info(`res: %+v`, out6)
l.Info(`登录成功`)
env.Config.Custom.Tx_Enable = true
env.Config.Custom.Tx_Uuin = out6.Req.Data.StrMusicid
env.Config.Custom.Tx_Ukey = out6.Req.Data.Musickey
env.Config.Custom.Tx_Refresh_Enable = false
// env.Config.Custom.Tx_Refresh_Interval = time.Date(now.Year(), now.Month(), now.Day()+5, 0, 0, 0, 0, now.Location()).Unix()
// env.Config.Custom.Tx_RefreshToken = out6.Req.Data.RefreshToken
// env.Config.Custom.Tx_AccessToken = out6.Req.Data.AccessToken
return env.Cfg.Save(``)
}
// QQ扫码登录(todo)
// func qlogin_qr_()

View File

@ -1,200 +1,27 @@
package tx
import (
"encoding/gob"
"lx-source/src/env"
"lx-source/src/sources"
"strings"
"github.com/ZxwyWebSite/ztool"
"github.com/ZxwyWebSite/ztool/x/bytesconv"
)
type musicInfo struct {
// Info struct {
// Company struct {
// Title string `json:"title"`
// Type string `json:"type"`
// Content []struct {
// ID int `json:"id"`
// Value string `json:"value"`
// Mid string `json:"mid"`
// Type int `json:"type"`
// ShowType int `json:"show_type"`
// IsParent int `json:"is_parent"`
// Picurl string `json:"picurl"`
// ReadCnt int `json:"read_cnt"`
// Author string `json:"author"`
// Jumpurl string `json:"jumpurl"`
// OriPicurl string `json:"ori_picurl"`
// } `json:"content"`
// Pos int `json:"pos"`
// More int `json:"more"`
// Selected string `json:"selected"`
// UsePlatform int `json:"use_platform"`
// } `json:"company"`
// Genre struct {
// Title string `json:"title"`
// Type string `json:"type"`
// Content []struct {
// ID int `json:"id"`
// Value string `json:"value"`
// Mid string `json:"mid"`
// Type int `json:"type"`
// ShowType int `json:"show_type"`
// IsParent int `json:"is_parent"`
// Picurl string `json:"picurl"`
// ReadCnt int `json:"read_cnt"`
// Author string `json:"author"`
// Jumpurl string `json:"jumpurl"`
// OriPicurl string `json:"ori_picurl"`
// } `json:"content"`
// Pos int `json:"pos"`
// More int `json:"more"`
// Selected string `json:"selected"`
// UsePlatform int `json:"use_platform"`
// } `json:"genre"`
// Lan struct {
// Title string `json:"title"`
// Type string `json:"type"`
// Content []struct {
// ID int `json:"id"`
// Value string `json:"value"`
// Mid string `json:"mid"`
// Type int `json:"type"`
// ShowType int `json:"show_type"`
// IsParent int `json:"is_parent"`
// Picurl string `json:"picurl"`
// ReadCnt int `json:"read_cnt"`
// Author string `json:"author"`
// Jumpurl string `json:"jumpurl"`
// OriPicurl string `json:"ori_picurl"`
// } `json:"content"`
// Pos int `json:"pos"`
// More int `json:"more"`
// Selected string `json:"selected"`
// UsePlatform int `json:"use_platform"`
// } `json:"lan"`
// } `json:"info"`
// Extras struct {
// Name string `json:"name"`
// Transname string `json:"transname"`
// Subtitle string `json:"subtitle"`
// From string `json:"from"`
// Wikiurl string `json:"wikiurl"`
// } `json:"extras"`
TrackInfo struct {
ID int `json:"id"`
Type int `json:"type"`
Mid string `json:"mid"`
Name string `json:"name"`
Title string `json:"title"`
Subtitle string `json:"subtitle"`
Singer []struct {
ID int `json:"id"`
Mid string `json:"mid"`
Name string `json:"name"`
Title string `json:"title"`
Type int `json:"type"`
Uin int `json:"uin"`
} `json:"singer"`
Album struct {
ID int `json:"id"`
Mid string `json:"mid"`
Name string `json:"name"`
Title string `json:"title"`
Subtitle string `json:"subtitle"`
TimePublic string `json:"time_public"`
Pmid string `json:"pmid"`
} `json:"album"`
Mv struct {
ID int `json:"id"`
Vid string `json:"vid"`
Name string `json:"name"`
Title string `json:"title"`
Vt int `json:"vt"`
} `json:"mv"`
Interval int `json:"interval"`
Isonly int `json:"isonly"`
Language int `json:"language"`
Genre int `json:"genre"`
IndexCd int `json:"index_cd"`
IndexAlbum int `json:"index_album"`
TimePublic string `json:"time_public"`
Status int `json:"status"`
Fnote int `json:"fnote"`
File struct {
MediaMid string `json:"media_mid"`
Size24Aac int `json:"size_24aac"`
Size48Aac int `json:"size_48aac"`
Size96Aac int `json:"size_96aac"`
Size192Ogg int `json:"size_192ogg"`
Size192Aac int `json:"size_192aac"`
Size128Mp3 int `json:"size_128mp3"`
Size320Mp3 int `json:"size_320mp3"`
SizeApe int `json:"size_ape"`
SizeFlac int `json:"size_flac"`
SizeDts int `json:"size_dts"`
SizeTry int `json:"size_try"`
TryBegin int `json:"try_begin"`
TryEnd int `json:"try_end"`
URL string `json:"url"`
SizeHires int `json:"size_hires"`
HiresSample int `json:"hires_sample"`
HiresBitdepth int `json:"hires_bitdepth"`
B30S int `json:"b_30s"`
E30S int `json:"e_30s"`
Size96Ogg int `json:"size_96ogg"`
Size360Ra []interface{} `json:"size_360ra"`
SizeDolby int `json:"size_dolby"`
SizeNew []int `json:"size_new"`
} `json:"file"`
Pay struct {
PayMonth int `json:"pay_month"`
PriceTrack int `json:"price_track"`
PriceAlbum int `json:"price_album"`
PayPlay int `json:"pay_play"`
PayDown int `json:"pay_down"`
PayStatus int `json:"pay_status"`
TimeFree int `json:"time_free"`
} `json:"pay"`
Action struct {
Switch int `json:"switch"`
Msgid int `json:"msgid"`
Alert int `json:"alert"`
Icons int `json:"icons"`
Msgshare int `json:"msgshare"`
Msgfav int `json:"msgfav"`
Msgdown int `json:"msgdown"`
Msgpay int `json:"msgpay"`
Switch2 int `json:"switch2"`
Icon2 int `json:"icon2"`
} `json:"action"`
Ksong struct {
ID int `json:"id"`
Mid string `json:"mid"`
} `json:"ksong"`
Volume struct {
Gain float64 `json:"gain"`
Peak float64 `json:"peak"`
Lra float64 `json:"lra"`
} `json:"volume"`
Label string `json:"label"`
URL string `json:"url"`
Bpm int `json:"bpm"`
Version int `json:"version"`
Trace string `json:"trace"`
DataType int `json:"data_type"`
ModifyStamp int `json:"modify_stamp"`
Pingpong string `json:"pingpong"`
Ppurl string `json:"ppurl"`
Tid int `json:"tid"`
Ov int `json:"ov"`
Sa int `json:"sa"`
Es string `json:"es"`
Vs []string `json:"vs"`
Vi []int `json:"vi"`
Ktag string `json:"ktag"`
Vf []float64 `json:"vf"`
} `json:"track_info"`
func init() {
gob.Register(musicInfo{})
}
func getMusicInfo(songMid string) (infoBody musicInfo, emsg string) {
cquery := strings.Join([]string{`tx`, songMid, `info`}, `/`)
if cdata, ok := env.Cache.Get(cquery); ok {
if cinfo, ok := cdata.(musicInfo); ok {
infoBody = cinfo
return
}
}
infoReqBody := ztool.Str_FastConcat(`{"comm":{"ct":"19","cv":"1859","uin":"0"},"req":{"method":"get_song_detail_yqq","module":"music.pf_song_detail_svr","param":{"song_mid":"`, songMid, `","song_type":0}}}`)
var infoResp struct {
Code int `json:"code"`
@ -216,5 +43,6 @@ func getMusicInfo(songMid string) (infoBody musicInfo, emsg string) {
return //nil, `获取音乐信息失败`
}
infoBody = infoResp.Req.Data
env.Cache.Set(cquery, infoBody, sources.C_lx)
return //infoBody.Req.Data, ``
}

View File

@ -8,62 +8,6 @@ import (
"github.com/ZxwyWebSite/ztool/x/bytesconv"
)
type playInfo struct {
Code int `json:"code"`
Data struct {
Uin string `json:"uin"`
Retcode int `json:"retcode"`
VerifyType int `json:"verify_type"`
LoginKey string `json:"login_key"`
Msg string `json:"msg"`
Sip []string `json:"sip"`
Thirdip []string `json:"thirdip"`
Testfile2G string `json:"testfile2g"`
Testfilewifi string `json:"testfilewifi"`
Midurlinfo []struct {
Songmid string `json:"songmid"`
Filename string `json:"filename"`
Purl string `json:"purl"`
Errtype string `json:"errtype"`
P2Pfromtag int `json:"p2pfromtag"`
Qmdlfromtag int `json:"qmdlfromtag"`
CommonDownfromtag int `json:"common_downfromtag"`
VipDownfromtag int `json:"vip_downfromtag"`
Pdl int `json:"pdl"`
Premain int `json:"premain"`
Hisdown int `json:"hisdown"`
Hisbuy int `json:"hisbuy"`
UIAlert int `json:"uiAlert"`
Isbuy int `json:"isbuy"`
Pneedbuy int `json:"pneedbuy"`
Pneed int `json:"pneed"`
Isonly int `json:"isonly"`
Onecan int `json:"onecan"`
Result int `json:"result"`
Tips string `json:"tips"`
Opi48Kurl string `json:"opi48kurl"`
Opi96Kurl string `json:"opi96kurl"`
Opi192Kurl string `json:"opi192kurl"`
Opiflackurl string `json:"opiflackurl"`
Opi128Kurl string `json:"opi128kurl"`
Opi192Koggurl string `json:"opi192koggurl"`
Wififromtag string `json:"wififromtag"`
Flowfromtag string `json:"flowfromtag"`
Wifiurl string `json:"wifiurl"`
Flowurl string `json:"flowurl"`
Vkey string `json:"vkey"`
Opi30Surl string `json:"opi30surl"`
Ekey string `json:"ekey"`
AuthSwitch int `json:"auth_switch"`
Subcode int `json:"subcode"`
Opi96Koggurl string `json:"opi96koggurl"`
AuthSwitch2 int `json:"auth_switch2"`
} `json:"midurlinfo"`
Servercheck string `json:"servercheck"`
Expiration int `json:"expiration"`
} `json:"data"`
}
/*
音乐URL获取逻辑
if需要付费播放and无账号信息:
@ -87,33 +31,54 @@ type playInfo struct {
返回结果
更新
可通过 goto loop 实现但可能会导致逻辑混乱 (想使用账号获取正常链接却返回试听链接)
2024-03-16:
正常获取->128k获取->试听获取
*/
func Url(songMid, quality string) (ourl, msg string) {
loger := env.Loger.NewGroup(`Tx`)
defer loger.Free()
infoFile, ok := fileInfo[quality]
if !ok || (!env.Config.Custom.Tx_Enable && quality != sources.Q_128k) {
msg = sources.E_QNotSupport //`不支持的音质`
return
}
infoBody, emsg := getMusicInfo(songMid)
loger.Debug(`infoBody: %+v`, infoBody)
if emsg != `` {
loger.Error(`GetInfo: %v`, emsg)
msg = emsg
return
}
loger.Debug(`infoBody: %+v`, infoBody)
var uauthst, uuin string = env.Config.Custom.Tx_Ukey, env.Config.Custom.Tx_Uuin
if uuin == `` || !env.Config.Custom.Tx_Enable {
uuin = `1535153710`
}
var strFileName string
tryLink := infoBody.TrackInfo.Pay.PayPlay == 1 && /*uauthst == ``&&*/ !env.Config.Custom.Tx_Enable
Loop:
if tryLink {
strFileName = ztool.Str_FastConcat(`RS02`, infoBody.TrackInfo.Vs[0], `.mp3`)
} else {
strFileName = ztool.Str_FastConcat(infoFile.H, infoBody.TrackInfo.File.MediaMid, infoFile.E)
if infoBody.TrackInfo.Vs[0] == `` {
msg = sources.ErrNoLink
return
}
requestBody := ztool.Str_FastConcat(`{"comm":{"authst":"`, uauthst, `","ct":"26","cv":"2010101","qq":"`, uuin, `","v":"2010101"},"req_0":{"method":"CgiGetVkey","module":"vkey.GetVkeyServer","param":{"filename":["`, strFileName, `"],"guid":"114514","loginflag":1,"platform":"20","songmid":["`, songMid, `"],"songtype":[0],"uin":"10086"}}}`)
strFileName = ztool.Str_FastConcat(`RS02`, infoBody.TrackInfo.Vs[0], `.`, sources.X_mp3)
} else {
strFileName = ztool.Str_FastConcat(infoFile.H, infoBody.TrackInfo.File.MediaMid, `.`, infoFile.E)
}
requestBody := ztool.Str_FastConcat(
`{"comm":{"authst":"`,
uauthst,
`","ct":"26","cv":"2010101","qq":"`,
uuin,
`","v":"2010101"},"req_0":{"method":"CgiGetVkey","module":"vkey.GetVkeyServer","param":{"filename":["`,
strFileName,
`"],"guid":"20211008","loginflag":1,"platform":"20","songmid":["`,
songMid,
`"],"songtype":[0],"uin":"`,
uuin,
`"}}}`,
)
var infoResp struct {
Code int `json:"code"`
// Ts int64 `json:"ts"`
@ -123,12 +88,28 @@ func Url(songMid, quality string) (ourl, msg string) {
}
err := signRequest(bytesconv.StringToBytes(requestBody), &infoResp)
if err != nil {
loger.Error(`Request: %s`, err)
msg = err.Error()
return
}
loger.Debug(`infoResp: %+v`, infoResp)
if len(infoResp.Req0.Data.Midurlinfo) == 0 {
msg = `No Data: 无返回数据`
return
}
infoData := infoResp.Req0.Data.Midurlinfo[0]
if infoData.Purl == `` {
if env.Config.Source.ForceFallback && !tryLink {
if quality != sources.Q_128k && infoBody.TrackInfo.Pay.PayPlay == 0 {
msg = `Fallback to 128k`
infoFile = fileInfo[sources.Q_128k]
quality = sources.Q_128k
} else {
msg = `Fallbacked`
tryLink = true
}
goto Loop
}
msg = sources.E_NoLink //`无法获取音乐链接`
return
}
@ -139,8 +120,10 @@ func Url(songMid, quality string) (ourl, msg string) {
// }
if realQuality != infoFile.H && !tryLink {
msg = sources.E_QNotMatch
if !env.Config.Source.ForceFallback {
return
}
ourl = ztool.Str_FastConcat(`https://ws.stream.qqmusic.qq.com/`, infoData.Purl)
}
ourl = env.Config.Custom.Tx_CDNUrl + infoData.Purl
return
}

View File

@ -1,6 +1,8 @@
package tx
import (
"errors"
"fmt"
"lx-source/src/env"
"net/http"
"strings"
@ -78,25 +80,15 @@ type refreshData struct {
第一次载入时会刷新一次测试可用性&同步过期时间 (默认7天)
*/
func refresh(loger *logs.Logger) {
// loger := env.Loger.NewGroup(`refresh_login`)
if env.Config.Custom.Tx_Ukey == `` || !env.Config.Custom.Tx_Refresh_Enable {
return
}
if time.Now().Unix() < env.Config.Custom.Tx_Refresh_Interval {
func refresh(loger *logs.Logger, now int64) error {
// 前置检测
if now < env.Config.Custom.Tx_Refresh_Interval {
loger.Debug(`Key未过期跳过...`)
return
return nil
}
// 刷新逻辑 (注QQ登录最常用所以先判断QHL开头)
var body, surl string
if strings.HasPrefix(env.Config.Custom.Tx_Ukey, `W_X`) {
body = ztool.Str_FastConcat(
`{"comm":{"authst":"","ct":"11","cv":"12080008","fPersonality":"0","qq":"","tmeAppID":"qqmusic","tmeLoginMethod":"1","tmeLoginType":"1","v":"12080008"},"req1":{"method":"Login","module":"music.login.LoginServer","param":{"code":"","loginMode":2,"musickey":"`,
env.Config.Custom.Tx_Ukey,
`","openid":"","refresh_key":"","refresh_token":"","str_musicid":"`,
env.Config.Custom.Tx_Uuin,
`","unionid":""}}}`,
)
} else if strings.HasPrefix(env.Config.Custom.Tx_Ukey, `Q_H_L`) {
if strings.HasPrefix(env.Config.Custom.Tx_Ukey, `Q_H_L`) {
body = ztool.Str_FastConcat(
`{"req1":{"method":"QQLogin","module":"QQConnectLogin.LoginServer","param":{"expired_in":7776000,"musicid":`,
env.Config.Custom.Tx_Uuin,
@ -105,43 +97,69 @@ func refresh(loger *logs.Logger) {
`"}}}`,
)
surl = `6`
} else if strings.HasPrefix(env.Config.Custom.Tx_Ukey, `W_X`) {
body = ztool.Str_FastConcat(
`{"comm":{"authst":"","ct":"11","cv":"12080008","fPersonality":"0","qq":"","tmeAppID":"qqmusic","tmeLoginMethod":"1","tmeLoginType":"1","v":"12080008"},"req1":{"method":"Login","module":"music.login.LoginServer","param":{"code":"","loginMode":2,"musickey":"`,
env.Config.Custom.Tx_Ukey,
`","openid":"","refresh_key":"","refresh_token":"","str_musicid":"`,
env.Config.Custom.Tx_Uuin,
`","unionid":""}}}`,
)
} else {
loger.Error(`未知的qqmusic_key格式`)
env.Config.Custom.Tx_Refresh_Enable = false // 本次启动阻止继续执行
return
// 致命错误(删除任务)
panic(`未知的 qqmusic_key 格式, 请检查配置 [Custom].Tx_Ukey`)
}
loger.Debug(`Body: %v`, body)
var resp refreshData
signature := sign(bytesconv.StringToBytes(body))
err := ztool.Net_Request(http.MethodPost,
err := ztool.Net_Request(
http.MethodPost,
ztool.Str_FastConcat(`https://u`, surl, `.y.qq.com/cgi-bin/musics.fcg?sign=`, signature),
strings.NewReader(body),
[]ztool.Net_ReqHandlerFunc{
ztool.Net_ReqAddHeaders(header),
},
[]ztool.Net_ResHandlerFunc{
ztool.Net_ResToStruct(&resp),
},
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(header)},
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&resp)},
)
if err != nil {
loger.Error(`请求Api失败: %s`, err)
return
// loger.Error(`请求Api失败: %s`, err)
return errors.New(`请求Api失败: ` + err.Error())
}
loger.Debug(`Resp: %+v`, resp)
if resp.Req1.Code != 0 {
loger.Warn("刷新登录失败, code: %v\n响应体: %+v", resp.Req1.Code, resp)
return
switch resp.Req1.Code {
case 1000:
return fmt.Errorf(`%v: Token无效或已过期`, resp.Req1.Code)
case 2000:
return fmt.Errorf(`%v: 该Token不支持刷新`, resp.Req1.Code)
default:
return fmt.Errorf(`%v: 刷新登录失败`, resp.Req1.Code)
}
// loger.Warn("刷新登录失败, code: %v\n响应体: %+v", resp.Req1.Code, resp)
// return fmt.Errorf("刷新登录失败, code: %v\n响应体: %+v", resp.Req1.Code, resp)
}
loger.Info(`刷新登录成功`)
env.Config.Custom.Tx_Uuin = resp.Req1.Data.StrMusicId
env.Config.Custom.Tx_Ukey = resp.Req1.Data.MusicKey
env.Config.Custom.Tx_Refresh_Interval = resp.Req1.Data.ExpiredAt - 86000 // 提前一天
tnow := time.Now()
env.Config.Custom.Tx_Refresh_Interval = time.Date(tnow.Year(), tnow.Month(), tnow.Day()+5, 0, 0, 0, 0, tnow.Location()).Unix()
// env.Config.Custom.Tx_Refresh_Interval = now + 432000 //(每5天刷新一次) //1209600 - 86000 // 14天提前一天
loger.Debug(`Resp: %+v`, resp)
loger.Debug(`Uuin: %v, Ukey: %v`, resp.Req1.Data.StrMusicId, resp.Req1.Data.MusicKey)
loger.Debug(`ExpiresAt: %v`, resp.Req1.Data.ExpiredAt)
loger.Debug(`ExpiresAt: %v, Real: %v`, resp.Req1.Data.ExpiredAt, env.Config.Custom.Tx_Refresh_Interval)
err = env.Cfg.Save(``)
if err != nil {
loger.Error(`%s`, err)
return
}
// if err != nil {
// loger.Error(`%s`, err)
// return
// }
if err == nil {
loger.Info(`数据更新成功`) // 已通过相应数据更新uin和qqmusic_key
}
return err
}
func init() {
env.Inits.Add(func() {
if env.Config.Custom.Tx_Refresh_Enable && env.Config.Custom.Tx_Ukey != `` && env.Config.Custom.Tx_Uuin != `` {
env.Tasker.Add(`tx_refresh`, refresh, 86000, true)
}
})
}

View File

@ -0,0 +1,246 @@
package tx
type musicInfo struct {
// Info struct {
// Company struct {
// Title string `json:"title"`
// Type string `json:"type"`
// Content []struct {
// ID int `json:"id"`
// Value string `json:"value"`
// Mid string `json:"mid"`
// Type int `json:"type"`
// ShowType int `json:"show_type"`
// IsParent int `json:"is_parent"`
// Picurl string `json:"picurl"`
// ReadCnt int `json:"read_cnt"`
// Author string `json:"author"`
// Jumpurl string `json:"jumpurl"`
// OriPicurl string `json:"ori_picurl"`
// } `json:"content"`
// Pos int `json:"pos"`
// More int `json:"more"`
// Selected string `json:"selected"`
// UsePlatform int `json:"use_platform"`
// } `json:"company"`
// Genre struct {
// Title string `json:"title"`
// Type string `json:"type"`
// Content []struct {
// ID int `json:"id"`
// Value string `json:"value"`
// Mid string `json:"mid"`
// Type int `json:"type"`
// ShowType int `json:"show_type"`
// IsParent int `json:"is_parent"`
// Picurl string `json:"picurl"`
// ReadCnt int `json:"read_cnt"`
// Author string `json:"author"`
// Jumpurl string `json:"jumpurl"`
// OriPicurl string `json:"ori_picurl"`
// } `json:"content"`
// Pos int `json:"pos"`
// More int `json:"more"`
// Selected string `json:"selected"`
// UsePlatform int `json:"use_platform"`
// } `json:"genre"`
// Lan struct {
// Title string `json:"title"`
// Type string `json:"type"`
// Content []struct {
// ID int `json:"id"`
// Value string `json:"value"`
// Mid string `json:"mid"`
// Type int `json:"type"`
// ShowType int `json:"show_type"`
// IsParent int `json:"is_parent"`
// Picurl string `json:"picurl"`
// ReadCnt int `json:"read_cnt"`
// Author string `json:"author"`
// Jumpurl string `json:"jumpurl"`
// OriPicurl string `json:"ori_picurl"`
// } `json:"content"`
// Pos int `json:"pos"`
// More int `json:"more"`
// Selected string `json:"selected"`
// UsePlatform int `json:"use_platform"`
// } `json:"lan"`
// } `json:"info"`
// Extras struct {
// Name string `json:"name"`
// Transname string `json:"transname"`
// Subtitle string `json:"subtitle"`
// From string `json:"from"`
// Wikiurl string `json:"wikiurl"`
// } `json:"extras"`
TrackInfo struct {
// ID int `json:"id"`
// Type int `json:"type"`
// Mid string `json:"mid"`
// Name string `json:"name"`
// Title string `json:"title"`
// Subtitle string `json:"subtitle"`
// Singer []struct {
// ID int `json:"id"`
// Mid string `json:"mid"`
// Name string `json:"name"`
// Title string `json:"title"`
// Type int `json:"type"`
// Uin int `json:"uin"`
// } `json:"singer"`
// Album struct {
// ID int `json:"id"`
// Mid string `json:"mid"`
// Name string `json:"name"`
// Title string `json:"title"`
// Subtitle string `json:"subtitle"`
// TimePublic string `json:"time_public"`
// Pmid string `json:"pmid"`
// } `json:"album"`
// Mv struct {
// ID int `json:"id"`
// Vid string `json:"vid"`
// Name string `json:"name"`
// Title string `json:"title"`
// Vt int `json:"vt"`
// } `json:"mv"`
// Interval int `json:"interval"`
// Isonly int `json:"isonly"`
// Language int `json:"language"`
// Genre int `json:"genre"`
// IndexCd int `json:"index_cd"`
// IndexAlbum int `json:"index_album"`
// TimePublic string `json:"time_public"`
// Status int `json:"status"`
// Fnote int `json:"fnote"`
File struct {
MediaMid string `json:"media_mid"`
// Size24Aac int `json:"size_24aac"`
// Size48Aac int `json:"size_48aac"`
// Size96Aac int `json:"size_96aac"`
// Size192Ogg int `json:"size_192ogg"`
// Size192Aac int `json:"size_192aac"`
// Size128Mp3 int `json:"size_128mp3"`
// Size320Mp3 int `json:"size_320mp3"`
// SizeApe int `json:"size_ape"`
// SizeFlac int `json:"size_flac"`
// SizeDts int `json:"size_dts"`
// SizeTry int `json:"size_try"`
// TryBegin int `json:"try_begin"`
// TryEnd int `json:"try_end"`
// URL string `json:"url"`
// SizeHires int `json:"size_hires"`
// HiresSample int `json:"hires_sample"`
// HiresBitdepth int `json:"hires_bitdepth"`
// B30S int `json:"b_30s"`
// E30S int `json:"e_30s"`
// Size96Ogg int `json:"size_96ogg"`
// Size360Ra []interface{} `json:"size_360ra"`
// SizeDolby int `json:"size_dolby"`
// SizeNew []int `json:"size_new"`
} `json:"file"`
Pay struct {
// PayMonth int `json:"pay_month"`
// PriceTrack int `json:"price_track"`
// PriceAlbum int `json:"price_album"`
PayPlay int `json:"pay_play"`
// PayDown int `json:"pay_down"`
// PayStatus int `json:"pay_status"`
// TimeFree int `json:"time_free"`
} `json:"pay"`
// Action struct {
// Switch int `json:"switch"`
// Msgid int `json:"msgid"`
// Alert int `json:"alert"`
// Icons int `json:"icons"`
// Msgshare int `json:"msgshare"`
// Msgfav int `json:"msgfav"`
// Msgdown int `json:"msgdown"`
// Msgpay int `json:"msgpay"`
// Switch2 int `json:"switch2"`
// Icon2 int `json:"icon2"`
// } `json:"action"`
// Ksong struct {
// ID int `json:"id"`
// Mid string `json:"mid"`
// } `json:"ksong"`
// Volume struct {
// Gain float64 `json:"gain"`
// Peak float64 `json:"peak"`
// Lra float64 `json:"lra"`
// } `json:"volume"`
// Label string `json:"label"`
// URL string `json:"url"`
// Bpm int `json:"bpm"`
// Version int `json:"version"`
// Trace string `json:"trace"`
// DataType int `json:"data_type"`
// ModifyStamp int `json:"modify_stamp"`
// Pingpong string `json:"pingpong"`
// Ppurl string `json:"ppurl"`
// Tid int `json:"tid"`
// Ov int `json:"ov"`
// Sa int `json:"sa"`
// Es string `json:"es"`
Vs []string `json:"vs"`
// Vi []int `json:"vi"`
// Ktag string `json:"ktag"`
// Vf []float64 `json:"vf"`
} `json:"track_info"`
}
type playInfo struct {
// Code int `json:"code"`
Data struct {
// Uin string `json:"uin"`
// Retcode int `json:"retcode"`
// VerifyType int `json:"verify_type"`
// LoginKey string `json:"login_key"`
// Msg string `json:"msg"`
// Sip []string `json:"sip"`
// Thirdip []string `json:"thirdip"`
// Testfile2G string `json:"testfile2g"`
// Testfilewifi string `json:"testfilewifi"`
Midurlinfo []struct {
// Songmid string `json:"songmid"`
Filename string `json:"filename"`
Purl string `json:"purl"`
// Errtype string `json:"errtype"`
// P2Pfromtag int `json:"p2pfromtag"`
// Qmdlfromtag int `json:"qmdlfromtag"`
// CommonDownfromtag int `json:"common_downfromtag"`
// VipDownfromtag int `json:"vip_downfromtag"`
// Pdl int `json:"pdl"`
// Premain int `json:"premain"`
// Hisdown int `json:"hisdown"`
// Hisbuy int `json:"hisbuy"`
// UIAlert int `json:"uiAlert"`
// Isbuy int `json:"isbuy"`
// Pneedbuy int `json:"pneedbuy"`
// Pneed int `json:"pneed"`
// Isonly int `json:"isonly"`
// Onecan int `json:"onecan"`
// Result int `json:"result"`
// Tips string `json:"tips"`
// Opi48Kurl string `json:"opi48kurl"`
// Opi96Kurl string `json:"opi96kurl"`
// Opi192Kurl string `json:"opi192kurl"`
// Opiflackurl string `json:"opiflackurl"`
// Opi128Kurl string `json:"opi128kurl"`
// Opi192Koggurl string `json:"opi192koggurl"`
// Wififromtag string `json:"wififromtag"`
// Flowfromtag string `json:"flowfromtag"`
// Wifiurl string `json:"wifiurl"`
// Flowurl string `json:"flowurl"`
// Vkey string `json:"vkey"`
// Opi30Surl string `json:"opi30surl"`
// Ekey string `json:"ekey"`
// AuthSwitch int `json:"auth_switch"`
// Subcode int `json:"subcode"`
// Opi96Koggurl string `json:"opi96koggurl"`
// AuthSwitch2 int `json:"auth_switch2"`
} `json:"midurlinfo"`
// Servercheck string `json:"servercheck"`
// Expiration int `json:"expiration"`
} `json:"data"`
}

View File

@ -14,28 +14,28 @@ var (
H string // 专用音质
}{
sources.Q_128k: {
E: `.mp3`,
E: sources.X_mp3,
H: `M500`,
},
sources.Q_320k: {
E: `.mp3`,
E: sources.X_mp3,
H: `M800`,
},
sources.Q_flac: {
E: `.flac`,
E: sources.Q_flac,
H: `F000`,
},
sources.Q_fl24: {
E: `.flac`,
E: sources.Q_flac,
H: `RS01`,
},
`dolby`: {
E: `.flac`,
sources.Q_dolby: {
E: sources.Q_flac,
H: `Q000`,
},
`master`: {
E: `.flac`,
H: `AI00`,
sources.Q_master: {
E: sources.Q_flac,
H: `AI00`, // (~~母带音质大部分都是AI提上去的~~)
},
}
// qualityMapReverse = map[string]string{

View File

@ -1,98 +0,0 @@
package wy
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"github.com/ZxwyWebSite/ztool"
"github.com/ZxwyWebSite/ztool/x/bytesconv"
"github.com/ZxwyWebSite/ztool/x/json"
"github.com/ZxwyWebSite/ztool/zcypt"
)
var (
// __all__ = []string{`weEncrypt`, `linuxEncrypt`, `eEncrypt`}
// MODULUS = ztool.Str_FastConcat(
// `00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7`,
// `b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280`,
// `104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932`,
// `575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b`,
// `3ece0462db0a22b8e7`,
// )
// PUBKEY = `010001`
// NONCE = bytesconv.StringToBytes(`0CoJUm6Qyw8W8jud`)
// LINUXKEY = bytesconv.StringToBytes(`rFgB&h#%2?^eDg:Q`)
eapiKey = bytesconv.StringToBytes(`e82ckenh8dichen8`)
ivKey = bytesconv.StringToBytes(`0102030405060708`)
)
func eapiEncrypt(url, text string) map[string][]string {
digest := zcypt.CreateMD5(bytesconv.StringToBytes(ztool.Str_FastConcat(
`nobody`, url, `use`, text, `md5forencrypt`,
)))
data := ztool.Str_FastConcat(
url, `-36cd479b6b5-`, text, `-36cd479b6b5-`, digest,
)
// 注JSON编码时会自动将[]byte转为string这里省去一步转换
return map[string][]string{
`params`: {bytesconv.BytesToString(aesEncrypt(bytesconv.StringToBytes(data), eapiKey, false))},
}
}
// crypto.js
func aesEncrypt(text, key []byte, iv bool) []byte {
pad := 16 - len(text)%16
text = append(text, bytes.Repeat([]byte{byte(pad)}, pad)...)
block, _ := aes.NewCipher(key)
// if err != nil {
// panic(err)
// }
var encryptor cipher.BlockMode
if iv {
encryptor = cipher.NewCBCEncrypter(block, ivKey)
} else {
encryptor = zcypt.NewECBEncrypter(block)
}
ciphertext := make([]byte, len(text))
encryptor.CryptBlocks(ciphertext, text)
if iv {
return zcypt.Base64Encode(base64.StdEncoding, ciphertext)
}
return bytes.ToUpper(zcypt.HexEncode(ciphertext))
}
func eapi(url string, object map[string]any) map[string][]string {
text, err := json.Marshal(object)
if err != nil {
panic(err)
}
message := ztool.Str_FastConcat(
`nobody`, url, `use`, bytesconv.BytesToString(text), `md5forencrypt`,
)
digest := zcypt.CreateMD5(bytesconv.StringToBytes(message))
data := bytes.Join(
[][]byte{
bytesconv.StringToBytes(url),
text,
bytesconv.StringToBytes(digest),
},
[]byte{45, 51, 54, 99, 100, 52, 55, 57, 98, 54, 98, 53, 45},
)
return map[string][]string{
`params`: {bytesconv.BytesToString(aesEncrypt(data, eapiKey, false))},
}
}
func decrypt(data []byte) (out []byte) {
dec, err := zcypt.HexDecode(data)
if err == nil {
out, err = zcypt.AesDecrypt(dec, eapiKey)
}
if err != nil {
panic(err)
}
return out
}

View File

@ -0,0 +1,133 @@
package wy
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"math/rand"
_ "unsafe"
"github.com/ZxwyWebSite/ztool"
"github.com/ZxwyWebSite/ztool/x/bytesconv"
"github.com/ZxwyWebSite/ztool/x/json"
"github.com/ZxwyWebSite/ztool/zcypt"
)
// crypto.js
var (
ivKey = bytesconv.StringToBytes(`0102030405060708`)
presetKey = bytesconv.StringToBytes(`0CoJUm6Qyw8W8jud`)
linuxapiKey = bytesconv.StringToBytes(`rFgB&h#%2?^eDg:Q`)
base62 = bytesconv.StringToBytes(`abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`)
publicKey = bytesconv.StringToBytes(`-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7clFSs6sXqHauqKWqdtLkF2KexO40H1YTX8z2lSgBBOAxLsvaklV8k4cBFK9snQXE9/DDaFt6Rr7iVZMldczhC0JNgTz+SHXT6CBHuX3e9SdB1Ua44oncaTWz7OBGLbCiK45wIDAQAB
-----END PUBLIC KEY-----`)
eapiKey = bytesconv.StringToBytes(`e82ckenh8dichen8`)
)
func aesEncrypt(text, key []byte, iv bool) []byte {
pad := 16 - len(text)%16
text = append(text, bytes.Repeat([]byte{byte(pad)}, pad)...)
block, _ := aes.NewCipher(key)
// if err != nil {
// panic(err)
// }
var encryptor cipher.BlockMode
if iv {
encryptor = cipher.NewCBCEncrypter(block, ivKey)
} else {
encryptor = zcypt.NewECBEncrypter(block)
}
ciphertext := make([]byte, len(text))
encryptor.CryptBlocks(ciphertext, text)
if iv {
return zcypt.Base64Encode(base64.StdEncoding, ciphertext)
}
return bytes.ToUpper(zcypt.HexEncode(ciphertext))
}
//go:linkname rsaEncryptNone crypto/rsa.encrypt
func rsaEncryptNone(*rsa.PublicKey, []byte) ([]byte, error)
func rsaEncrypt(data []byte) string {
pblock, _ := pem.Decode(publicKey)
pubKey, _ := x509.ParsePKIXPublicKey(pblock.Bytes)
// 注为实现NONE加密手动导出了标准库里的encrypt方法若编译不过添加以下代码
// /usr/local/go/src/crypto/rsa/rsa.go:478
// ```
// var Encrypt = encrypt // export
// ```
// 第二种方式linkname调用不用改库 https://www.jianshu.com/p/7b3638b47845
encData, err := rsaEncryptNone(pubKey.(*rsa.PublicKey), data)
if err != nil {
panic(err)
}
return zcypt.HexToString(encData)
}
func weapi(object map[string]any) map[string][]string {
text, err := json.Marshal(object)
if err != nil {
panic(err)
}
secretKey := make([]byte, 16)
for i := 0; i < 16; i++ {
secretKey[i] = base62[rand.Intn(62)]
}
return map[string][]string{
`params`: {bytesconv.BytesToString(aesEncrypt(
aesEncrypt(text, presetKey, true),
secretKey,
true,
))},
`encSecKey`: {rsaEncrypt(ztool.Sort_ReverseNew(secretKey))},
}
}
func linuxapi(object map[string]any) map[string][]string {
text, err := json.Marshal(object)
if err != nil {
panic(err)
}
return map[string][]string{
`eparams`: {bytesconv.BytesToString(aesEncrypt(text, linuxapiKey, false))},
}
}
func eapi(url string, object map[string]any) map[string][]string {
text, err := json.Marshal(object)
if err != nil {
panic(err)
}
message := ztool.Str_FastConcat(
`nobody`, url, `use`, bytesconv.BytesToString(text), `md5forencrypt`,
)
digest := zcypt.CreateMD5(bytesconv.StringToBytes(message))
data := bytes.Join(
[][]byte{
bytesconv.StringToBytes(url),
text,
bytesconv.StringToBytes(digest),
},
[]byte{45, 51, 54, 99, 100, 52, 55, 57, 98, 54, 98, 53, 45},
)
return map[string][]string{
`params`: {bytesconv.BytesToString(aesEncrypt(data, eapiKey, false))},
}
}
func decrypt(data []byte) (out []byte) {
dec, err := zcypt.HexDecode(data)
if err == nil {
out, err = zcypt.AesDecrypt(dec, eapiKey)
}
if err != nil {
panic(err)
}
return out
}

View File

@ -20,7 +20,11 @@ import (
const anonymous_token = `1f5fa7b6a6a9f81a11886e5186fde7fb98e25cf0036d7afd055b980b2261f5464b7f5273fc3921d1262bfec66a19a30c41d8da00c3685f5ace96f0d5a48b6db334d974731083682e3324751bcc9aaf44c3061cd1`
var wapiReg = regexp.MustCompile(`\w*api`)
var (
wapiReg = regexp.MustCompile(`\w*api`)
csrfReg = regexp.MustCompile(`_csrf=([^(;|$)]+)`)
domaReg = regexp.MustCompile(`\s*Domain=[^(;|$)]+;*`)
)
var userAgentMap = map[string]string{
`mobile`: `Mozilla/5.0 (iPhone; CPU iPhone OS 17_2_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1`,
@ -28,24 +32,30 @@ var userAgentMap = map[string]string{
}
type (
// reqCookie struct{}
ReqQuery struct {
Cookie map[string]string
RealIP string
Ids string
Br string
Level string
}
reqOptions struct {
UA string
Headers map[string]string
UA string
RealIP string
IP string
Cookie interface{}
Crypto string
Url string
Cookie interface{}
}
reqAnswer struct {
ReqAnswer struct {
Status int
Body map[string]any
Cookie []string
}
)
func createRequest(method, url string, data map[string]any, options reqOptions) (*reqAnswer, error) {
func createRequest(method, url string, data map[string]any, options reqOptions) (*ReqAnswer, error) {
if options.Headers == nil {
options.Headers = make(map[string]string)
}
@ -96,26 +106,24 @@ func createRequest(method, url string, data map[string]any, options reqOptions)
var form stdurl.Values
switch options.Crypto {
case `weapi`:
panic(`not support`)
// options.Headers[`User-Agent`] = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69`
// reg := regexp.MustCompile(`_csrf=([^(;|$)]+)`)
// csrfToken := reg.FindStringSubmatch(options.Headers[`Cookie`])
// if len(csrfToken) > 1 {
// data[`csrf_token`] = csrfToken[1]
// } else {
// data[`csrf_token`] = ``
// }
// data = weapi(data)
// url = wapiReg.ReplaceAllString(url, `weapi`)
options.Headers[`User-Agent`] = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69`
csrfToken := csrfReg.FindStringSubmatch(options.Headers[`Cookie`])
if len(csrfToken) > 1 {
data[`csrf_token`] = csrfToken[1]
} else {
data[`csrf_token`] = ``
}
form = weapi(data)
// fmt.Println(form.Encode())
url = wapiReg.ReplaceAllString(url, `weapi`)
case `linuxapi`:
panic(`not support`)
// data = linuxapi(
// method,
// wapiReg.ReplaceAllString(url, `weapi`),
// data,
// )
// options.Headers[`User-Agent`] = `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36`
// url = `https://music.163.com/api/linux/forward`
form = linuxapi(map[string]any{
`method`: method,
`url`: wapiReg.ReplaceAllString(url, `weapi`),
`params`: data,
})
options.Headers[`User-Agent`] = `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36`
url = `https://music.163.com/api/linux/forward`
case `eapi`:
cookie, ok := options.Cookie.(map[string]string)
if !ok {
@ -168,7 +176,7 @@ func createRequest(method, url string, data map[string]any, options reqOptions)
// for k, v := range data {
// values.Add(k, v)
// }
answer := reqAnswer{Status: 500, Body: map[string]any{} /*, Cookie: []string{}*/}
answer := ReqAnswer{Status: 500, Body: map[string]any{} /*, Cookie: []string{}*/}
err := ztool.Net_Request(method, url,
strings.NewReader(form.Encode()),
[]ztool.Net_ReqHandlerFunc{
@ -178,17 +186,20 @@ func createRequest(method, url string, data map[string]any, options reqOptions)
func(res *http.Response) error {
body, err := io.ReadAll(res.Body)
if err == nil {
answer.Cookie = res.Header[`Set-Cookie`]
reg := regexp.MustCompile(`\s*Domain=[^(;|$)]+;*`)
// fmt.Println(`body:`, string(body), "\nstr:", body)
if len(body) == 0 {
err = errors.New(`nil Body`)
}
if err == nil {
answer.Cookie = res.Header[`Set-Cookie`] //res.Header.Values(`set-cookie`)
for i, v := range answer.Cookie {
answer.Cookie[i] = reg.ReplaceAllString(v, ``)
answer.Cookie[i] = domaReg.ReplaceAllString(v, ``)
}
if options.Crypto == `eapi` && body[0] != '{' {
err = json.Unmarshal(decrypt(body), &answer.Body)
} else {
err = json.Unmarshal(body, &answer.Body)
}
if err == nil {
if code, ok := answer.Body[`code`].(string); ok {
answer.Body[`code`], err = strconv.Atoi(code)
} else {
@ -212,10 +223,10 @@ func createRequest(method, url string, data map[string]any, options reqOptions)
}
if err != nil {
answer.Status = 502
answer.Body = map[string]any{`code`: 502, `msg`: err}
return err
answer.Body = map[string]any{`code`: 502, `msg`: err.Error()}
// return err
}
return nil
return err // nil
},
},
)

View File

@ -0,0 +1,96 @@
package wy
type (
// 音乐URL
PlayInfo struct {
Data []struct {
ID int `json:"id"`
URL string `json:"url"`
Br int `json:"br"`
Size int `json:"size"`
Md5 string `json:"md5"`
Code int `json:"code"`
Expi int `json:"expi"`
Type string `json:"type"`
Gain float64 `json:"gain"`
Peak float64 `json:"peak"`
Fee int `json:"fee"`
Uf interface{} `json:"uf"`
Payed int `json:"payed"`
Flag int `json:"flag"`
CanExtend bool `json:"canExtend"`
FreeTrialInfo *struct {
AlgData interface{} `json:"algData"`
End int `json:"end"`
FragmentType int `json:"fragmentType"`
Start int `json:"start"`
} `json:"freeTrialInfo"`
Level string `json:"level"`
EncodeType string `json:"encodeType"`
FreeTrialPrivilege struct {
ResConsumable bool `json:"resConsumable"`
UserConsumable bool `json:"userConsumable"`
ListenType int `json:"listenType"`
CannotListenReason int `json:"cannotListenReason"`
PlayReason interface{} `json:"playReason"`
} `json:"freeTrialPrivilege"`
FreeTimeTrialPrivilege struct {
ResConsumable bool `json:"resConsumable"`
UserConsumable bool `json:"userConsumable"`
Type int `json:"type"`
RemainTime int `json:"remainTime"`
} `json:"freeTimeTrialPrivilege"`
URLSource int `json:"urlSource"`
RightSource int `json:"rightSource"`
PodcastCtrp interface{} `json:"podcastCtrp"`
EffectTypes interface{} `json:"effectTypes"`
Time int `json:"time"`
} `json:"data"`
Code int `json:"code"`
}
// 音乐是否可用
VerifyInfo struct {
Code int16 `json:"code"`
Success bool `json:"success"`
Message string `json:"message"`
}
// 音质数据
QualityData struct {
Br int `json:"br"` // 比特率 Bit Rate
Fid int `json:"fid"` // ?
Size int `json:"size"` // 文件大小
Vd float64 `json:"vd"` // Volume Delta
Sr int `json:"sr"` // 采样率 Sample Rate
}
// 歌曲音质详情
QualityDetail struct {
Data struct {
SongID int `json:"songId"`
H QualityData `json:"h"` // 高质量文件信息
M QualityData `json:"m"` // 中质量文件信息
L QualityData `json:"l"` // 低质量文件信息
Sq QualityData `json:"sq"` // 无损质量文件信息
Hr QualityData `json:"hr"` // Hi-Res质量文件信息
Db QualityData `json:"db"` // 杜比音质
Jm QualityData `json:"jm"` // jymaster(超清母带)
Je QualityData `json:"je"` // jyeffect(高清环绕声)
Sk QualityData `json:"sk"` // sky(沉浸环绕声)
} `json:"data"`
Code int `json:"code"`
Message string `json:"message"`
Success bool `json:"success"`
Error bool `json:"error"`
}
// 扫码登录请求
QrKey struct {
Code int `json:"code"`
UniKey string `json:"unikey"`
}
// 扫码登录结果
QrCheck struct {
Code int `json:"code"`
Message string `json:"message"`
AvatarUrl string `json:"avatarUrl"`
NickName string `json:"nickname"`
}
)

View File

@ -0,0 +1,19 @@
package wy
import "net/http"
// 二维码检测扫码状态接口
func LoginQrCheck(key string) (*ReqAnswer, error) {
res, err := createRequest(
http.MethodPost,
`https://music.163.com/weapi/login/qrcode/client/login`,
map[string]any{
`key`: key,
`type`: 1,
},
reqOptions{
Crypto: `weapi`,
},
)
return res, err
}

View File

@ -0,0 +1,6 @@
package wy
// 二维码生成接口
func LoginQrCreate(key string) string {
return `https://music.163.com/login?codekey=` + key
}

View File

@ -0,0 +1,19 @@
package wy
import "net/http"
// 二维码 key 生成接口
func LoginQrKey() (*ReqAnswer, error) {
res, err := createRequest(
http.MethodPost,
`https://music.163.com/weapi/login/qrcode/unikey`,
map[string]any{
`type`: 1,
},
reqOptions{
Crypto: `weapi`,
Cookie: nil,
},
)
return res, err
}

View File

@ -0,0 +1,27 @@
package wy
import (
"net/http"
"strings"
)
// 登录刷新
func LoginRefresh(query ReqQuery) (*ReqAnswer, error) {
res, err := createRequest(
http.MethodPost,
`https://music.163.com/weapi/login/token/refresh`,
map[string]any{},
reqOptions{
Crypto: `weapi`,
UA: `pc`,
Cookie: query.Cookie,
RealIP: query.RealIP,
},
)
if code, ok := res.Body[`code`].(int); ok && err == nil {
if code == 200 {
res.Body[`cookie`] = strings.Join(res.Cookie, `;`)
}
}
return res, err
}

View File

@ -0,0 +1,73 @@
package wy
import (
"net/http"
"sort"
"strconv"
"strings"
"github.com/ZxwyWebSite/ztool"
)
// type Query_song_url struct {
// Cookie map[string]string
// Ids string
// Br string
// RealIP string
// }
// 歌曲链接
func SongUrl(query ReqQuery) (*ReqAnswer, error) {
if query.Cookie == nil {
query.Cookie = make(map[string]string)
}
query.Cookie[`os`] = `pc`
if query.Br == `` {
query.Br = `999000`
}
ids := strings.Split(query.Ids, `,`)
// idj, err := json.Marshal(ids)
// if err != nil {
// return nil, err
// }
data := map[string]any{
`ids`: ztool.Str_FastConcat(`["`, strings.Join(ids, `","`), `"]`), //bytesconv.BytesToString(idj), //`["1998644237"]`,
`br`: query.Br, //ztool.Str_Select(query.Br, `999000`),
}
res, err := createRequest(
http.MethodPost,
`https://interface3.music.163.com/eapi/song/enhance/player/url`,
data,
reqOptions{
Crypto: `eapi`,
Cookie: query.Cookie,
RealIP: query.RealIP,
Url: `/api/song/enhance/player/url`,
},
)
// 根据id排序
if length := len(ids); length > 1 && err == nil {
indexOf := make(map[string]int, length)
for i := 0; i < length; i++ {
indexOf[ids[i]] = i
}
if data, ok := res.Body[`data`].([]interface{}); ok {
sort.SliceStable(data, func(a, b int) bool {
da, oa := data[a].(map[string]interface{})
db, ob := data[b].(map[string]interface{})
if oa && ob {
ia, ka := da[`id`].(float64)
ib, kb := db[`id`].(float64)
if ka && kb {
ta := strconv.FormatInt(int64(ia), 10)
tb := strconv.FormatInt(int64(ib), 10)
return indexOf[ta] < indexOf[tb]
}
}
return false
})
res.Body[`data`] = data
}
}
return res, err
}

View File

@ -0,0 +1,37 @@
package wy
import (
"net/http"
"github.com/ZxwyWebSite/ztool"
)
// 歌曲链接 - v1
// 此版本不再采用 br 作为音质区分的标准
// 而是采用 standard, exhigh, lossless, hires, jyeffect(高清环绕声), sky(沉浸环绕声), jymaster(超清母带) 进行音质判断
func SongUrlV1(query ReqQuery) (*ReqAnswer, error) {
if query.Cookie == nil {
query.Cookie = make(map[string]string)
}
query.Cookie[`os`] = `android`
query.Cookie[`appver`] = `8.10.05`
data := map[string]any{
`ids`: ztool.Str_FastConcat(`[`, query.Ids, `]`),
`level`: query.Level,
`encodeType`: `flac`,
}
if query.Level == `sky` /*|| query.Level == `jysky`*/ {
data[`immerseType`] = `c51`
}
return createRequest(
http.MethodPost,
`https://interface.music.163.com/eapi/song/enhance/player/url/v1`,
data,
reqOptions{
Crypto: `eapi`,
Cookie: query.Cookie,
RealIP: query.RealIP,
Url: `/api/song/enhance/player/url/v1`,
},
)
}

View File

@ -1,34 +1,126 @@
package wy
import (
"io"
"lx-source/src/env"
"lx-source/src/sources"
"lx-source/src/sources/builtin"
"lx-source/src/sources/custom/utils"
wm "lx-source/src/sources/custom/wy/modules"
"lx-source/src/sources/example"
"net/http"
"net/url"
"strconv"
"strings"
"sync"
"time"
"github.com/ZxwyWebSite/ztool"
"github.com/ZxwyWebSite/ztool/x/cookie"
)
func Url(songMid, quality string) (ourl, msg string) {
var (
wy_pool = &sync.Pool{New: func() any { return new(wm.PlayInfo) }}
// wv_pool *sync.Pool
Url func(string, string) (string, string)
)
func init() {
env.Inits.Add(func() {
loger := env.Loger.NewGroup(`WyInit`)
switch env.Config.Custom.Wy_Mode {
case `0`, `builtin`:
loger.Debug(`use builtin`)
// if env.Config.Source.MusicIdVerify {
// wv_pool = &sync.Pool{New: func() any { return new(verifyInfo) }}
// }
Url = builtin
case `1`, `163api`:
if env.Config.Custom.Wy_Api_Cookie == `` {
loger.Fatal(`使用163api且Cookie参数为空`)
}
switch env.Config.Custom.Wy_Api_Type {
case `0`, `native`:
loger.Debug(`use 163api module`)
Url = nmModule
case `1`, `remote`:
loger.Debug(`use 163api custom`)
if env.Config.Custom.Wy_Api_Address == `` {
loger.Fatal(`自定义接口地址为空`)
}
if env.Config.Custom.Wy_Api_Address[len(env.Config.Custom.Wy_Api_Address)-1] != '/' {
env.Config.Custom.Wy_Api_Address += "/" // 补全尾部斜杠
}
loger.Info(`使用自定义接口: %v`, env.Config.Custom.Wy_Api_Address)
Url = nmCustom
default:
loger.Fatal(`未定义的调用方式,请检查配置 [Custom].Wy_Api_Type`)
}
default:
loger.Fatal(`未定义的接口模式,请检查配置 [Custom].Wy_Mode`)
}
loger.Free()
})
}
func builtin(songMid, quality string) (ourl, msg string) {
loger := env.Loger.NewGroup(`Wy`)
rquality, ok := brMap[quality]
defer loger.Free()
rquality, ok := qualityMap[quality]
if !ok {
msg = sources.E_QNotSupport
return
}
cookies := cookie.Parse(env.Config.Custom.Wy_Cookie)
answer, err := SongUrl(song_url_query{
resp := wy_pool.Get().(*wm.PlayInfo)
defer wy_pool.Put(resp)
url := ztool.Str_FastConcat(
`https://`, example.Api_wy, `&id=`, songMid, `&level=`, rquality,
`&timestamp=`, strconv.FormatInt(time.Now().UnixMilli(), 10),
)
err := ztool.Net_Request(
http.MethodGet, url, nil,
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(example.Header_wy)},
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&resp)},
)
if err != nil {
loger.Error(`HttpReq: %s`, err)
msg = sources.ErrHttpReq
return
}
loger.Debug(`Resp: %+v`, resp)
if len(resp.Data) == 0 {
msg = `No DataApi接口忙请稍后重试`
return
}
var data = resp.Data[0]
if data.Code != 200 || data.FreeTrialInfo != nil {
msg = `触发风控或专辑单独收费: ` + strconv.Itoa(data.Code)
return
}
if data.Level != rquality {
msg = ztool.Str_FastConcat(`实际音质不匹配: `, rquality, ` <= `, data.Level)
if !env.Config.Source.ForceFallback {
return
}
}
ourl = data.URL
return
}
func nmModule(songMid, quality string) (ourl, msg string) {
loger := env.Loger.NewGroup(`Wy`)
defer loger.Free()
rquality, ok := qualityMap[quality]
if !ok {
msg = sources.E_QNotSupport
return
}
cookies := cookie.Parse(env.Config.Custom.Wy_Api_Cookie)
answer, err := wm.SongUrlV1(wm.ReqQuery{
Cookie: cookie.ToMap(cookies),
Ids: songMid,
Br: rquality,
// Br: rquality,
Level: rquality,
})
var body builtin.WyApi_Song
body := wy_pool.Get().(*wm.PlayInfo)
defer wy_pool.Put(body)
if err == nil {
err = ztool.Val_MapToStruct(answer.Body, &body)
}
@ -43,52 +135,48 @@ func Url(songMid, quality string) (ourl, msg string) {
return
}
data := body.Data[0]
br := strconv.Itoa(data.Br) // 注由于flac返回br值不固定暂无法进行比较
if br != rquality && !ztool.Chk_IsMatch(br, sources.Q_flac, sources.Q_fl24) {
msg = sources.E_QNotMatch
if data.Code != 200 {
msg = `触发风控或专辑单独收费: ` + strconv.Itoa(data.Code)
return
}
if data.Level != rquality {
msg = ztool.Str_FastConcat(`实际音质不匹配: `, rquality, ` <= `, data.Level)
if !env.Config.Source.ForceFallback {
return
}
}
// br := strconv.Itoa(data.Br) // 注由于flac返回br值不固定暂无法进行比较
// if br != rquality && !ztool.Chk_IsMatch(br, sources.Q_flac, sources.Q_fl24) {
// msg = sources.E_QNotMatch
// return
// }
ourl = utils.DelQuery(data.URL)
return
}
func PyUrl(songMid, quality string) (ourl, msg string) {
func nmCustom(songMid, quality string) (ourl, msg string) {
loger := env.Loger.NewGroup(`Wy`)
defer loger.Free()
rquality, ok := qualityMap[quality]
if !ok {
msg = sources.E_QNotSupport
return
}
path := `/api/song/enhance/player/url/v1`
requestUrl := `https://interface.music.163.com/eapi/song/enhance/player/url/v1`
var body builtin.WyApi_Song
text := ztool.Str_FastConcat(
`{"encodeType":"flac","ids":["`, songMid, `"],"level":"`, rquality, `"}`,
)
var form url.Values = eapiEncrypt(path, text)
// form, err := json.Marshal(eapiEncrypt(path, text))
// if err == nil {
body := wy_pool.Get().(*wm.PlayInfo)
defer wy_pool.Put(body)
err := ztool.Net_Request(
http.MethodPost, requestUrl,
strings.NewReader(form.Encode()), //bytes.NewReader(form),
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeader(map[string]string{
`Cookie`: env.Config.Custom.Wy_Cookie,
http.MethodGet,
ztool.Str_FastConcat(
env.Config.Custom.Wy_Api_Address, `song/url/v1`, `?id=`, songMid, `&level=`, rquality,
// `&timestamp=`, strconv.FormatInt(time.Now().UnixMilli(), 10),
), nil,
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(map[string]string{
`Cookie`: env.Config.Custom.Wy_Api_Cookie,
})},
[]ztool.Net_ResHandlerFunc{
func(res *http.Response) error {
body, err := io.ReadAll(res.Body)
if err != nil {
return err
}
loger.Info(`%s`, body)
return ztool.Err_EsContinue
},
ztool.Net_ResToStruct(&body),
},
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&body)},
)
// }
if err != nil {
loger.Error(`Request: %s`, err)
loger.Error(`SongUrl: %s`, err)
msg = sources.ErrHttpReq
return
}
@ -98,10 +186,16 @@ func PyUrl(songMid, quality string) (ourl, msg string) {
return
}
data := body.Data[0]
if data.Level != rquality {
msg = sources.E_QNotMatch
if data.Code != 200 {
msg = `触发风控或专辑单独收费: ` + strconv.Itoa(data.Code)
return
}
if data.Level != rquality {
msg = ztool.Str_FastConcat(`实际音质不匹配: `, rquality, ` <= `, data.Level)
if !env.Config.Source.ForceFallback {
return
}
}
ourl = utils.DelQuery(data.URL)
return
}

View File

@ -0,0 +1,83 @@
package wy
import (
"lx-source/src/env"
wy "lx-source/src/sources/custom/wy/modules"
"time"
// "time"
"github.com/ZxwyWebSite/ztool/logs"
"github.com/ZxwyWebSite/ztool/x/cookie"
)
/*
刷新登录模块 (来自 NeteaseCloudMusicApi)
逻辑
检测返回结果中是否含有"MUSIC_U":
如果有则为正常刷新延时30天
否则延时1天
原代码未提供详细描述无法确定有效结果判断条件暂时先这么写
2024-02-15:
MUSIC_U 改变 6 继续执行
MUSIC_U 不变 1 继续执行
原理
听说隔壁某解析群的账号经常使用Token快一年了也没过期
所以模拟正常使用每天调用一次刷新接口
证明这个Token还在使用类似于给它"续期"
就像SPlayer客户端一样Cookie变了就合并
(这是我随便猜的未经测试仅供参考)
*/
func refresh(loger *logs.Logger, now int64) error {
// 前置检测
// now := time.Now().Unix() //(执行时间已改为从参数获取)
if now < env.Config.Custom.Wy_Refresh_Interval {
loger.Debug(`Key未过期跳过...`)
return nil
}
// 刷新逻辑
cookies := cookie.ToMap(cookie.Parse(env.Config.Custom.Wy_Api_Cookie))
res, err := wy.LoginRefresh(wy.ReqQuery{
Cookie: cookies,
})
loger.Debug(`Resp: %+v`, res)
if err == nil {
if out, ok := res.Body[`cookie`].(string); ok {
loger.Info(`获取数据成功`)
cmap := cookie.ToMap(cookie.Parse(out))
// inline call to maps.Copy
for k, v := range cmap {
cookies[k] = v
}
env.Config.Custom.Wy_Api_Cookie = cookie.Marshal(cookies)
loger.Debug(`Cookie: %#v`, cookies)
// if _, ok := cmap[`MUSIC_U`]; ok {
// // MUSIC_U 改变 则 6天 后 继续执行
// env.Config.Custom.Wy_Refresh_Interval = now + 518400 //2147483647 - 86000
// loger.Debug(`MUSIC_U 改变, 6天 后 继续执行`)
// } else {
// // MUSIC_U 不变 则 1天 后 继续执行
// env.Config.Custom.Wy_Refresh_Interval = now + 86000
// loger.Debug(`MUSIC_U 不变, 1天 后 继续执行`) //`未发现有效结果,将在下次检测时再次尝试`
// }
tnow := time.Now()
env.Config.Custom.Wy_Refresh_Interval = time.Date(tnow.Year(), tnow.Month(), tnow.Day()+1, 0, 0, 0, 0, tnow.Location()).Unix()
// env.Config.Custom.Wy_Refresh_Interval = now + 86000
err = env.Cfg.Save(``)
if err == nil {
loger.Info(`配置更新成功`)
}
}
}
return err
}
func init() {
env.Inits.Add(func() {
if env.Config.Custom.Wy_Refresh_Enable && env.Config.Custom.Wy_Api_Cookie != `` {
env.Tasker.Add(`wy_refresh`, refresh, 86000, true)
}
})
}

View File

@ -1,42 +0,0 @@
package wy
import (
"net/http"
"strings"
"github.com/ZxwyWebSite/ztool"
)
type song_url_query struct {
Cookie map[string]string
Ids string
Br string
RealIP string
}
func SongUrl(query song_url_query) (*reqAnswer, error) {
query.Cookie[`os`] = `pc`
if query.Br == `` {
query.Br = `999000`
}
ids := strings.Split(query.Ids, `,`)
// idj, err := json.Marshal(ids)
// if err != nil {
// return nil, err
// }
data := map[string]any{
`ids`: ztool.Str_FastConcat(`["`, strings.Join(ids, `","`), `"]`), //bytesconv.BytesToString(idj), //`["1998644237"]`,
`br`: query.Br, //ztool.Str_Select(query.Br, `999000`),
}
return createRequest(
http.MethodPost,
`https://interface3.music.163.com/eapi/song/enhance/player/url`,
data,
reqOptions{
Crypto: `eapi`,
Cookie: query.Cookie,
RealIP: query.RealIP,
Url: `/api/song/enhance/player/url`,
},
)
}

View File

@ -3,20 +3,21 @@ package wy
import "lx-source/src/sources"
var (
brMap = map[string]string{
sources.Q_128k: `128000`,
sources.Q_320k: `320000`,
sources.Q_flac: `1000000`, //`743625`,`915752`
sources.Q_fl24: `2000000`, //`1453955`,`1683323`
}
// brMap = map[string]string{
// sources.Q_128k: `128000`,
// sources.Q_320k: `320000`,
// sources.Q_flac: `1000000`, //`743625`,`915752`
// sources.Q_fl24: `2000000`, //`1453955`,`1683323`
// }
qualityMap = map[string]string{
sources.Q_128k: `standard`,
sources.Q_320k: `exhigh`,
sources.Q_flac: `lossless`,
sources.Q_fl24: `hires`,
`dolby`: `jyeffect`,
`sky`: `jysky`,
`master`: `jymaster`,
sources.Q_dolby: `jyeffect`,
sources.Q_sky: sources.Q_sky,
sources.Q_master: `jymaster`,
}
// 优化返回音质与查询音质相同完全可以直接比较不用多一步Reverse
// qualityMapReverse = map[string]string{

View File

@ -0,0 +1,38 @@
package example
import (
"encoding/base64"
"github.com/ZxwyWebSite/ztool"
"github.com/ZxwyWebSite/ztool/zcypt"
)
var (
Api_wy string
Api_mg string
Vef_wy string
Header_wy map[string]string
Header_mg map[string]string
)
func init() {
// InitBuiltInData
var initdata = struct {
Api_Wy *string
Api_Mg *string
Vef_Wy *string
Header_Wy *map[string]string
Header_Mg *map[string]string
}{
Api_Wy: &Api_wy,
Api_Mg: &Api_mg,
Vef_Wy: &Vef_wy,
Header_Wy: &Header_wy,
Header_Mg: &Header_mg,
}
data := []byte{0x53, 0x6e, 0x38, 0x44, 0x41, 0x51, 0x4c, 0x2f, 0x67, 0x41, 0x41, 0x42, 0x42, 0x51, 0x45, 0x47, 0x51, 0x58, 0x42, 0x70, 0x58, 0x31, 0x64, 0x35, 0x41, 0x51, 0x77, 0x41, 0x41, 0x51, 0x5a, 0x42, 0x63, 0x47, 0x6c, 0x66, 0x54, 0x57, 0x63, 0x42, 0x44, 0x41, 0x41, 0x42, 0x42, 0x6c, 0x5a, 0x6c, 0x5a, 0x6c, 0x39, 0x58, 0x65, 0x51, 0x45, 0x4d, 0x41, 0x41, 0x45, 0x4a, 0x53, 0x47, 0x56, 0x68, 0x5a, 0x47, 0x56, 0x79, 0x58, 0x31, 0x64, 0x35, 0x41, 0x66, 0x2b, 0x43, 0x41, 0x41, 0x45, 0x4a, 0x53, 0x47, 0x56, 0x68, 0x5a, 0x47, 0x56, 0x79, 0x58, 0x30, 0x31, 0x6e, 0x41, 0x66, 0x2b, 0x43, 0x41, 0x41, 0x41, 0x41, 0x49, 0x66, 0x2b, 0x42, 0x42, 0x41, 0x45, 0x42, 0x45, 0x57, 0x31, 0x68, 0x63, 0x46, 0x74, 0x7a, 0x64, 0x48, 0x4a, 0x70, 0x62, 0x6d, 0x64, 0x64, 0x63, 0x33, 0x52, 0x79, 0x61, 0x57, 0x35, 0x6e, 0x41, 0x66, 0x2b, 0x43, 0x41, 0x41, 0x45, 0x4d, 0x41, 0x51, 0x77, 0x41, 0x41, 0x50, 0x34, 0x42, 0x72, 0x76, 0x2b, 0x41, 0x41, 0x53, 0x52, 0x6a, 0x63, 0x32, 0x30, 0x75, 0x63, 0x32, 0x46, 0x35, 0x63, 0x58, 0x6f, 0x75, 0x59, 0x32, 0x39, 0x74, 0x4c, 0x32, 0x46, 0x77, 0x61, 0x53, 0x38, 0x2f, 0x64, 0x48, 0x6c, 0x77, 0x5a, 0x54, 0x31, 0x68, 0x63, 0x47, 0x6c, 0x54, 0x62, 0x32, 0x35, 0x6e, 0x56, 0x58, 0x4a, 0x73, 0x56, 0x6a, 0x45, 0x42, 0x4e, 0x6d, 0x30, 0x75, 0x62, 0x58, 0x56, 0x7a, 0x61, 0x57, 0x4d, 0x75, 0x62, 0x57, 0x6c, 0x6e, 0x64, 0x53, 0x35, 0x6a, 0x62, 0x69, 0x39, 0x74, 0x61, 0x57, 0x64, 0x31, 0x62, 0x58, 0x56, 0x7a, 0x61, 0x57, 0x4d, 0x76, 0x61, 0x44, 0x55, 0x76, 0x63, 0x47, 0x78, 0x68, 0x65, 0x53, 0x39, 0x68, 0x64, 0x58, 0x52, 0x6f, 0x4c, 0x32, 0x64, 0x6c, 0x64, 0x46, 0x4e, 0x76, 0x62, 0x6d, 0x64, 0x51, 0x62, 0x47, 0x46, 0x35, 0x53, 0x57, 0x35, 0x6d, 0x62, 0x77, 0x45, 0x6c, 0x59, 0x33, 0x4e, 0x74, 0x4c, 0x6e, 0x4e, 0x68, 0x65, 0x58, 0x46, 0x36, 0x4c, 0x6d, 0x4e, 0x76, 0x62, 0x53, 0x39, 0x68, 0x63, 0x47, 0x6b, 0x76, 0x50, 0x33, 0x52, 0x35, 0x63, 0x47, 0x55, 0x39, 0x59, 0x33, 0x4e, 0x74, 0x51, 0x32, 0x68, 0x6c, 0x59, 0x57, 0x74, 0x4e, 0x64, 0x58, 0x4e, 0x70, 0x59, 0x77, 0x45, 0x43, 0x43, 0x6c, 0x56, 0x7a, 0x5a, 0x58, 0x49, 0x74, 0x51, 0x57, 0x64, 0x6c, 0x62, 0x6e, 0x52, 0x65, 0x54, 0x57, 0x39, 0x36, 0x61, 0x57, 0x78, 0x73, 0x59, 0x53, 0x38, 0x31, 0x4c, 0x6a, 0x41, 0x67, 0x4b, 0x46, 0x64, 0x70, 0x62, 0x6d, 0x52, 0x76, 0x64, 0x33, 0x4d, 0x67, 0x54, 0x6c, 0x51, 0x67, 0x4e, 0x69, 0x34, 0x78, 0x4f, 0x79, 0x42, 0x58, 0x54, 0x31, 0x63, 0x32, 0x4e, 0x43, 0x6b, 0x67, 0x51, 0x58, 0x42, 0x77, 0x62, 0x47, 0x56, 0x58, 0x5a, 0x57, 0x4a, 0x4c, 0x61, 0x58, 0x51, 0x76, 0x4e, 0x54, 0x4d, 0x33, 0x4c, 0x6a, 0x4d, 0x32, 0x49, 0x43, 0x68, 0x4c, 0x53, 0x46, 0x52, 0x4e, 0x54, 0x43, 0x77, 0x67, 0x62, 0x47, 0x6c, 0x72, 0x5a, 0x53, 0x42, 0x48, 0x5a, 0x57, 0x4e, 0x72, 0x62, 0x79, 0x6b, 0x67, 0x51, 0x32, 0x68, 0x79, 0x62, 0x32, 0x31, 0x6c, 0x4c, 0x7a, 0x55, 0x77, 0x4c, 0x6a, 0x41, 0x75, 0x4d, 0x6a, 0x59, 0x32, 0x4d, 0x53, 0x34, 0x34, 0x4e, 0x78, 0x42, 0x59, 0x4c, 0x56, 0x4a, 0x6c, 0x63, 0x58, 0x56, 0x6c, 0x63, 0x33, 0x52, 0x6c, 0x5a, 0x43, 0x31, 0x58, 0x61, 0x58, 0x52, 0x6f, 0x44, 0x6c, 0x68, 0x4e, 0x54, 0x45, 0x68, 0x30, 0x64, 0x48, 0x42, 0x53, 0x5a, 0x58, 0x46, 0x31, 0x5a, 0x58, 0x4e, 0x30, 0x41, 0x51, 0x51, 0x48, 0x55, 0x6d, 0x56, 0x6d, 0x5a, 0x58, 0x4a, 0x6c, 0x63, 0x68, 0x74, 0x6f, 0x64, 0x48, 0x52, 0x77, 0x63, 0x7a, 0x6f, 0x76, 0x4c, 0x32, 0x30, 0x75, 0x62, 0x58, 0x56, 0x7a, 0x61, 0x57, 0x4d, 0x75, 0x62, 0x57, 0x6c, 0x6e, 0x64, 0x53, 0x35, 0x6a, 0x62, 0x69, 0x39, 0x32, 0x4e, 0x43, 0x38, 0x43, 0x51, 0x6e, 0x6b, 0x67, 0x4d, 0x44, 0x52, 0x6d, 0x4f, 0x44, 0x45, 0x30, 0x4e, 0x6a, 0x46, 0x68, 0x4f, 0x54, 0x68, 0x6a, 0x4e, 0x32, 0x46, 0x6d, 0x4e, 0x54, 0x55, 0x33, 0x5a, 0x6d, 0x56, 0x68, 0x4d, 0x32, 0x4e, 0x6d, 0x4d, 0x6a, 0x68, 0x6a, 0x4e, 0x47, 0x56, 0x68, 0x4d, 0x54, 0x55, 0x48, 0x59, 0x32, 0x68, 0x68, 0x62, 0x6d, 0x35, 0x6c, 0x62, 0x41, 0x63, 0x77, 0x4d, 0x54, 0x51, 0x77, 0x4d, 0x44, 0x42, 0x45, 0x42, 0x6b, 0x4e, 0x76, 0x62, 0x32, 0x74, 0x70, 0x5a, 0x54, 0x68, 0x54, 0x52, 0x56, 0x4e, 0x54, 0x53, 0x55, 0x39, 0x4f, 0x50, 0x56, 0x70, 0x55, 0x53, 0x58, 0x64, 0x50, 0x52, 0x47, 0x74, 0x35, 0x54, 0x55, 0x52, 0x52, 0x64, 0x45, 0x39, 0x55, 0x52, 0x54, 0x46, 0x4f, 0x55, 0x7a, 0x41, 0x77, 0x54, 0x55, 0x52, 0x6f, 0x62, 0x45, 0x78, 0x55, 0x61, 0x47, 0x68, 0x4e, 0x56, 0x30, 0x56, 0x30, 0x54, 0x57, 0x70, 0x52, 0x4d, 0x45, 0x34, 0x79, 0x57, 0x54, 0x4a, 0x4e, 0x65, 0x6d, 0x73, 0x79, 0x54, 0x31, 0x52, 0x42, 0x65, 0x67, 0x41, 0x3d}
dec, _ := zcypt.Base64Decode(base64.StdEncoding, data)
ztool.Val_GobDecode(dec, &initdata)
}

View File

@ -1,8 +1,8 @@
package sources
import (
"lx-source/src/caches"
)
// import (
// "lx-source/src/caches"
// )
// var Loger = env.Loger.NewGroup(`Sources`) // JieXiApis
@ -13,6 +13,14 @@ const (
Q_320k = `320k`
Q_flac = `flac`
Q_fl24 = `flac24bit`
// 扩展音质
Q_dolby = `dolby`
Q_sky = `sky`
Q_master = `master`
// 文件扩展
// X_aac = `aac`
X_mp3 = `mp3`
// X_flac = Q_flac
// 通用平台
S_wy = `wy` // 小芸
S_mg = `mg` // 小蜜
@ -29,6 +37,26 @@ const (
ErrHttpReq = `无法连接解析接口`
ErrNoLink = `无法获取试听链接`
ErrDisable = `该音乐源已被禁用`
// 缓存时间
C_tx = 80400 // 不知道tx为什么要取一个这么不对劲的数字当过期时长
C_kg = 24 * 60 * 60 // 24 hours
C_kw = 60 * 60 // 60 minutes
C_wy = 20 * 60 // 20 minutes
C_mg = C_kg // 永久直链但最多缓存1天
C_lx = 12 * 60 * 60 // 本地默认缓存12小时
)
const (
I_wy = iota
I_mg
I_kw
I_kg
I_tx
I_lx
)
var (
S_al = []string{S_wy, S_mg, S_kw, S_kg, S_tx, S_lx} // 全部平台
)
// 源查询接口
@ -43,24 +71,58 @@ const (
验证部分放到GetLink里
*/
type Source interface {
Verify(*caches.Query) (string, bool) // 验证是否可用 <查询参数> <rquery,ok>
GetLink(*caches.Query) (string, string) // 查询获取链接 <查询参数> <链接,信息>
}
// type Source interface {
// Verify(*caches.Query) (string, bool) // 验证是否可用 <查询参数> <rquery,ok>
// GetLink(*caches.Query) (string, string) // 查询获取链接 <查询参数> <链接,信息>
// }
// 默认空接口
type NullSource struct{}
// type NullSource struct{}
func (*NullSource) Verify(*caches.Query) (string, bool) { return ``, false }
func (*NullSource) GetLink(*caches.Query) (string, string) { return ``, `NullSource` }
// func (*NullSource) Verify(*caches.Query) (string, bool) { return ``, false }
// func (*NullSource) GetLink(*caches.Query) (string, string) { return ``, `NullSource` }
var UseSource Source = &NullSource{} // = &builtin.Source{}
// var UseSource Source = &NullSource{} // = &builtin.Source{}
// 统一错误
// type Error struct {
// msg string
// type (
// ErrDef struct {
// Typ string
// Msg string
// }
// ErrQul struct {
// Need string
// Real string
// }
// )
// func (e *ErrDef) Error() string {
// return ztool.Str_FastConcat(e.Typ, `: `, e.Msg)
// }
// func (e *ErrQul) Error() string {
// return ztool.Str_FastConcat(`实际音质不匹配: `, e.Need, ` <= `, e.Real)
// }
// func (e *Error) Error() string {
// return ztool.Str_FastConcat(e.msg)
// 验证失败(Verify Failed)
// func ErrVerify(msg string) error {
// return &ErrDef{
// Typ: Err_Verify,
// Msg: msg,
// }
// }
// 实际音质不匹配
// func ErrQuality(need, real string) error {
// return &ErrQul{
// Need: need,
// Real: real,
// }
// }
// 无返回数据(No Data)
// func ErrNoData() error {
// return &ErrDef{
// Typ: `No Data`,
// Msg: ``,
// }
// }

48
src/sources/types.go Normal file
View File

@ -0,0 +1,48 @@
//go:build extapp
package sources
// MusicFree 数据结构
type (
// 其他
IExtra map[string]interface{}
// 音乐
IMusicItem struct {
Artist string `json:"artist"` // 作者
Title string `json:"title"` // 歌曲标题
Duration int `json:"duration,omitempty"` // 时长(s)
Album string `json:"album,omitempty"` // 专辑名
Artwork string `json:"artwork,omitempty"` // 专辑封面图
Url string `json:"url,omitempty"` // 默认音源
Lrc string `json:"lrc,omitempty"` // 歌词URL
RawLrc string `json:"rawLrc,omitempty"` // 歌词文本lrc格式 带时间戳)
Other IExtra `json:"extra,omitempty"` // 其他
}
// 歌单
IMusicSheetItem struct {
Artwork string `json:"artwork,omitempty"` // 封面图
Title string `json:"title"` // 标题
Description string `json:"description,omitempty"` // 描述
WorksNum int `json:"worksNum,omitempty"` // 作品总数
PlayCount int `json:"playCount,omitempty"` // 播放次数
MusicList []IMusicItem `json:"musicList,omitempty"` // 播放列表
CreateAt int64 `json:"createAt,omitempty"` // 歌单创建日期
Artist string `json:"artist,omitempty"` // 歌单作者
Other IExtra `json:"extra,omitempty"` // 其他
}
// 专辑
IAlbumItem IMusicSheetItem
// 作者
IArtistItem struct {
Platform string `json:"platform,omitempty"` // 插件名
ID interface{} `json:"id"` // 唯一id
Name string `json:"name"` // 姓名
Fans int `json:"fans,omitempty"` // 粉丝数
Description string `json:"description,omitempty"` // 简介
Avatar string `json:"avatar,omitempty"` // 头像
WorksNum int `json:"worksNum,omitempty"` // 作品数目
MusicList []IMusicItem `json:"musicList,omitempty"` // 作者的音乐列表
AlbumList []IAlbumItem `json:"albumList,omitempty"` // 作者的专辑列表
Other IExtra `json:"extra,omitempty"` // 其他
}
)

202
update.md
View File

@ -1,5 +1,207 @@
## Lx-Source/更新日志
<!-- #### \# 2024-02-14 v1.0.3-rel (release)
+ **停止更新:感谢这三个月的陪伴,现因无力维护,停止后续更新,发布最后版本,大家有缘再见** -->
#### \# 2024-06-22 v1.0.3.0622 (dev)
+ 临近期末暂缓更新计划假期重构服务端v1.1.0
+ 更新源脚本,使用 WebPack 构建,支持自定义更多参数
+ 体验地址 `/lx-source-script.js`,添加 `?key=请求密钥`
+ 更新编译脚本支持MuslLibC、静态构建支持自动发布Release
#### \# 2024-06-14 v1.0.3.0614 (dev)
+ 更新编译脚本,支持更多架构
#### \# 2024-05-31 v1.0.3.0531 (dev)
+ Wy源优化模块同步部分更改
+ Kw源测试接口无需白名单包名二次甚至三次查询
+ Kw源允许自定义Des_Source参数
#### \# 2024-05-25 Special Ver (dev)
+ 更新Action构建脚本正式兼容Go1.20(windows7)
#### \# 2024-05-18 v1.0.3.0518 (dev)
+ (据用户反馈,当前账号添加方式过于复杂,很多参数不知道怎么填,故增加部分平台简化登录方式)
+ Tx源支持QQ快速登录(beta), 启动参数 `-e txqq`
+ 支持自定义错误音频地址(不填禁用), 位置 [Main].ErrMp3
+ ~~修复若干已知Bug~~
<!-- #### \# 2024-05-12 v1.0.3.0503 (dev)
+ 支持对接Python版ApiServer(二次分销?)
+ 同时开发了一套数据共享接口
(太过逆天,先不发了,容易导致滥用问题) -->
#### \# 2024-05-03 v1.0.3.0503 (dev)
+ 基于 cr-go-sdk 重新支持 Cloudreve 缓存
+ 解决Tx源一处Fallback死循环问题
#### \# 2024-04-30 v1.0.3.0430 (beta)
+ Tx源支持自定义CDN链接地址
+ Wy源支持扫码登录(beta), 启动参数 `-e wyqr`
+ 支持在解析时通过?key=传入验证Key
#### \# 2024-04-20 v1.0.3-rc3 (dev)
+ 新增状态页面,访问 `/status` 查看
+ Go1.20兼容版见 go120 分支
<!-- + 基于MKOnlinePlayer开始制作WebUI -->
#### \# 2024-04-06 v1.0.3-rc3 (dev)
+ Kg源新增刷新登录功能之前尝试写了一直没找到lite版的aeskey感谢Python版提供关键参数
#### \# 2024-03-22 v1.0.3-rc3 (dev)
+ Kg源Lite签到模块新增错误码"登录已过期"目前Token有效期为一个月暂未发现自动刷新方式
+ 重制构建脚本Action编译测试
+ 降级构建go1.20版本以支持更早的系统(beta)
<!-- + 脱离zTool依赖允许直接构建 -->
#### \# 2024-03-16 v1.0.3-rc2 (dev)
+ Mg源新增接口无需登录账号二次查询速度较慢
+ 清理部分结构体未使用字段
+ Tx源支持音质Fallback
+ Kg源Lite签到重试
<!-- + 移除Wy源内置接口上游停止服务 -->
#### \# 2024-03-08 v1.0.3-rc2 (dev)
+ 完善音质验证部分
+ 刷新登录更新时间统一为指定日期0时0秒
<!-- + Mg源支持网页版接口 -->
#### \# 2024-02-22 v1.0.3-rc2 (dev)
+ 清理无用代码删除旧版Router
+ 支持将内存缓存写入文件,重启程序不丢数据
+ 优化计划任务逻辑: 发生错误不更新下次执行时间,以便在下次检测时重试
+ **注:开学在即,项目暂缓更新**
<!-- + 引入GORM和SQLite驱动 (支持不开cgo编译,但可能会导致数据库性能下降) -->
#### \# 2024-02-22 v1.0.3-rc1 (dev)
+ 修复wy源sky音质获取增加更高音质常量
+ (dolby->高清环绕, sky->沉浸环绕, master->超清母带)
+ 支持Kg源概念版自动签到(beta)
+ 统一缓存目录KGHASH为大写形式
+ 源脚本获取地址添加 `?raw` 参数可直接下载 (要求开启自动补全 [Script].Auto>0)
+ 为部分需要二次查询的源添加缓存
+ 为不同来源自定义直链缓存时间
#### \# 2024-02-20 v1.0.3-fix (fix)
+ 修复新版Wraper在Code不为0时未能正常结束Handler的问题
+ zTool: 修复计划任务模块一处计时bug "时间倒流"
+ Tx源刷新登录间隔时间降至5天
+ Wy源刷新登录间隔时间固定为1天
+ Kg源暴露更多配置项
+ 使用新版MusicRouter处理器(beta)兼容Python版**调用**方式?
#### \# 2024-02-15 v1.0.3-pre (pre)
+ Wy源刷新登录模式确定每天执行一次合并Cookie
+ zTool: task: 增加传参 now(int64): 执行时间(Unix)
+ 优化Tx源刷新登录函数兼容计划任务错误处理模式
+ 对源脚本进行部分更改,建议重新下载导入(<http://127.0.0.1:1011/lx-custom-source.js>
+ 简单优化旧版LinkHandler
+ 支持Mg源自定义账号
#### \# 2024-02-12 v1.0.3-pre-d1 (pre)
<!-- + 预留小洛源(lx)更名为local -->
+ 确定tx刷新登录间隔6天
+ 更新源接口定义
#### \# 2024-02-09 v1.0.3-pre-d1 (pre)
+ 数据表结构规划中,暂时推迟
+ 修复wy源builtin接口√
+ 使用计划任务定期清理日志缓存√
#### \# 2024-02-07 v1.0.3-pre-d1 (pre)
<!-- + 支持为本地缓存接口设置速率限制 -->
+ 修复Tx源刷新登录到期时间问题(默认14天)
+ Kg源使用新版接口彻底弃用MusicId传参
+ 优化linkrouter
#### \# 2024-02-03 v1.0.3-pre-d1 (dev)
+ ~~更新一个大版本~~
+ 源脚本:**兼容**洛雪手机端v1.2.0版本
+ 计划引入SQLite支持(GORM)支持缓存请求重写Router/Caches逻辑
+ 修复linux环境非root用户启动无法创建配置目录提示权限不够问题
#### \# 2024-01-31 v1.0.2-b12-d3 (dev)
+ [重构请求处理逻辑]:
```
首先拆分为三部分Before(前置检测), Middle(获取数据), After(验证结果)
可解决部分重复代码,但传参难以统一
```
+ [缓存数据目录]:
```
128k.mp3 320k.mp3 flac.flac fl64.flac lyric.lrc cover.jpg info.json
| 各音质文件缓存 | 歌词 | 封面 | 详情 |
```
+ [数据库表结构]:
```
暂未确定
```
+ 计划引入SQLite支持缓存歌曲详情
+ 更新go.mod依赖
+ 默认文件权限改为0777
+ 屏蔽部分未实现功能配置项
#### \# 2024-01-29 v1.0.2-b12-d3 (dev)
+ 增加启动参数 `-p 0777` 设置默认文件权限 解决非root用户下的权限问题 (部分情况0666不管用)
+ **(注:为保证正确解析八进制数据,开头的"0"不能去掉!)**
+ 低调行事删除源脚本内Github仓库地址
+ 构建时修改版本号?
+ 本地缓存:下载出错后删除文件
+ 更新wy内置源Api地址
+ 优化配置文件保存逻辑
+ 支持使用自定义NeteaseCloudMusicApi项目(beta)
+ (注:当前测试阶段,强行与旧逻辑兼容,可能无法达到最佳性能)
+ 计划重构源请求逻辑合并builtin与custom目录?
+ 支持wy源检测音质是否可用(需启用qualityMapReverse支持)
+ 计划将参数传入Query忽略实际音质不匹配强制缓存
#### \# 2024-01-27 v1.0.2-b12-d3 (dev)
+ wy源Cookie长期有效(一个月左右),且刷新逻辑未知,暂时禁用刷新登录功能
+ 将router和middleware整合到server文件夹
+ 自适应本地缓存绑定地址(beta),多端口建议开启,但缺少灵活性
+ windows平台调用loger.Fatal后显示按任意键继续(优化防止闪退)
#### \# 2024-01-26 v1.0.2-b12-d3 (dev)
+ 计划增加命令行模式配置账号登录(测试版)
+ 启动命令 `./lx-source-linux-amd64v2 -e menu`
+ 清理部分无用注释
#### \# 2024-01-24 v1.0.2-b12-d3 (dev)
<!-- + wyApi项目被警告删库暂时停止相关更新 -->
+ 模块化wyApi部分√
+ \[Auth\] 将Key验证模块移至速率限制模块后
+ 优化main.go使用zcypt.RandomBytes函数
+ 添加对象池手动释放Loger优化创建速度
+ 添加多端口监听支持
+ 支持配置全局代理、伪装ip
+ 支持使用163api模式获取歌曲
#### \# 2024-01-21 v1.0.2-b12-d2 (dev)
+ 新版api结构设计(暂定)
```
# 基础接口
/
/link/{source}/{musicid}/{quality}
# 功能接口
/api/{source}/{method}/{query}
| 源 | 功能 | 参数 |
/api/wy/link/?id=xxx&quality=320k&key=xxx
# 软件接口
/app/{name}/{method}/{?query}
| 名称 | 功能 |
/app/lxmusic/link (参数通过post传入)
/app/musicfree/xxx (计划支持MusicFree)
```
+ 添加wy外链获取v1支持并改用此版本
+ 计划:统一错误输出,进行以下分类
+ 验证失败(Verify Failed)、实际音质不匹配、无返回数据(No Data)、...
<!-- + 其它这一段时间主要完善wy源接口kg和mg账号源推迟更新 -->
#### \# 2024-01-19 v1.0.2-b12-d2 (dev)
+ 添加wy批量SongUrl获取排序功能
+ 完善wy请求加密支持
+ 添加wy刷新登录模块(beta)
+ 待优化cookie需要频繁在map和string之间转换
#### \# 2024-01-18 v1.0.2-b12-d1 (dev)
+ 对部分功能实现方式进行优化去除qualityMapReverse依赖
+ 由于wy修改api验证方式python版逻辑已不可用现参考NeteaseCloudMusicApi项目进行修改