mirror of
https://github.com/ZxwyWebSite/lx-source.git
synced 2025-07-05 19:49:03 +08:00
Compare commits
No commits in common. "main" and "v1.0.2-b12-d1" have entirely different histories.
main
...
v1.0.2-b12
107
.github/workflows/action.yml
vendored
107
.github/workflows/action.yml
vendored
@ -1,107 +0,0 @@
|
||||
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
1
.gitignore
vendored
@ -6,3 +6,4 @@ data/
|
||||
test.go
|
||||
test_test.go
|
||||
# src/sources/builtin/
|
||||
rsrc_windows_amd64.syso
|
532
action.go
532
action.go
@ -1,532 +0,0 @@
|
||||
//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(¶m{
|
||||
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
|
||||
}
|
15
build.go
15
build.go
@ -39,8 +39,6 @@ const (
|
||||
args_zpak = true // 打包文件
|
||||
)
|
||||
|
||||
var workDir string
|
||||
|
||||
// 编译
|
||||
func doCompile(v_os, v_arch, v_archv, v_cc string) error {
|
||||
// 构建 | 目标系统 | 目标架构 | 优化等级 | 不包含调试信息 | 使用外部链接器 | 输出详细操作 | 静态编译 | JSON解释器
|
||||
@ -54,11 +52,7 @@ 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,
|
||||
` -gcflags=-trimpath="`, workDir, `" -asmflags=-trimpath="`, workDir, `" -trimpath -buildvcs=false`,
|
||||
` -ldflags "-s -w -linkmode external" -tags "go_json"`, // go_json | json(std) | jsoniter | sonic
|
||||
)
|
||||
cmd := ztool.Str_FastConcat(`go build -o `, pname, ` -ldflags "-s -w -linkmode external -extldflags ''" -tags "go_json"`) // go_json | json(std) | jsoniter | sonic
|
||||
// 输出要执行的命令
|
||||
ztool.Cmd_FastPrintln(ztool.Str_FastConcat(`执行命令:`, cmd))
|
||||
// 设置环境&执行编译
|
||||
@ -90,11 +84,7 @@ 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,
|
||||
filepath.Join(args_path, `archieve`, ztool.Str_LastBefore(fname, `.`))+`.zip`,
|
||||
ztool.Pak_ZipConfig{UnPath: true},
|
||||
); err != nil {
|
||||
if err := ztool.Pak_ZipFile(pname, ztool.Str_FastConcat(filepath.Join(args_path, `archieve`, 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, `"]完成`))
|
||||
@ -108,7 +98,6 @@ 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
48
go.mod
@ -1,51 +1,41 @@
|
||||
module lx-source
|
||||
|
||||
go 1.20
|
||||
go 1.21.4
|
||||
|
||||
require (
|
||||
github.com/ZxwyWebSite/cr-go-sdk v0.0.2
|
||||
github.com/ZxwyWebSite/ztool v0.0.1
|
||||
github.com/gin-contrib/gzip v1.0.0
|
||||
github.com/gin-contrib/gzip v0.0.6
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
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/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/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.19.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.14.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.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/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // 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.2.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // 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
|
||||
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
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/ZxwyWebSite/cr-go-sdk v0.0.2 => ../cr-go-sdk
|
||||
github.com/ZxwyWebSite/ztool v0.0.1 => ../ztool // ./pkg/ztool
|
||||
)
|
||||
replace github.com/ZxwyWebSite/ztool v0.0.1 => ./pkg/ztool // ../ztool
|
||||
|
130
go.sum
130
go.sum
@ -1,54 +1,67 @@
|
||||
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/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/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.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/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/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.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
|
||||
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
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/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.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
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/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.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
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/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=
|
||||
@ -57,50 +70,71 @@ 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.2.1 h1:9TA9+T8+8CUCO2+WYnDLCgrYi9+omqKXyjDtosvtEhg=
|
||||
github.com/pelletier/go-toml/v2 v2.2.1/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
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/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/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/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
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.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/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/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/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
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=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
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/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/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.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=
|
||||
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=
|
||||
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
217
init.go
@ -1,217 +0,0 @@
|
||||
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()
|
||||
}
|
228
main.go
228
main.go
@ -2,16 +2,19 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"flag"
|
||||
"io/fs"
|
||||
"lx-source/src/caches"
|
||||
"lx-source/src/caches/localcache"
|
||||
"lx-source/src/env"
|
||||
"lx-source/src/server"
|
||||
"lx-source/src/router"
|
||||
"lx-source/src/sources"
|
||||
"lx-source/src/sources/builtin"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@ -20,6 +23,50 @@ 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(`
|
||||
@ -35,26 +82,18 @@ 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()
|
||||
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)
|
||||
// fileLoger() // 注:记录日志必然会影响性能,自行选择是否开启
|
||||
// logs.DefLogger(`LX-SOURCE`, logs.LevelDebu)
|
||||
// logs.Main = `LX-SOURCE`
|
||||
env.Cfg.MustInit(confPath) //conf.InitConfig(confPath)
|
||||
// fmt.Printf("%+v\n", env.Config)
|
||||
env.Loger.NewGroup(`ServHello`).Info(`欢迎使用 LX-SOURCE 洛雪音乐自定义源`).Free()
|
||||
env.Loger.NewGroup(`ServHello`).Info(`欢迎使用 LX-SOURCE 洛雪音乐自定义源`)
|
||||
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 关闭)`).Free()
|
||||
env.Loger.NewGroup(`DebugMode`).Debug(`已开启调试模式, 将输出更详细日志 (配置文件中 [Main].Debug 改为 false 关闭)`)
|
||||
}
|
||||
genAuth()
|
||||
if env.Config.Main.SysLev {
|
||||
@ -64,93 +103,102 @@ 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()
|
||||
// 初始化基础功能
|
||||
initMain()
|
||||
|
||||
// 初始化缓存
|
||||
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,本次启动禁用内置源`)
|
||||
}
|
||||
|
||||
// 载入必要模块
|
||||
env.Inits.Do()
|
||||
env.Loger.NewGroup(`ServInit`).Info(`服务端启动, 监听地址 %s`, strings.Join(env.Config.Main.Listen, `|`)).Free()
|
||||
env.Loger.NewGroup(`ServStart`).Info(`服务端启动, 监听地址 %s`, env.Config.Main.Listen)
|
||||
loadFileLoger()
|
||||
env.Defer.Add(env.Tasker.Run(env.Loger)) // wait
|
||||
|
||||
// 启动Http服务
|
||||
listenAndServe(server.InitRouter(), env.Config.Main.Listen)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}()
|
||||
|
||||
// 监听多端口
|
||||
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(`等待结束活动连接...`)
|
||||
// 停止服务
|
||||
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)
|
||||
if err := server.Shutdown(ctx); err != nil {
|
||||
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(`等待结束活动连接...`) // Shutdown Server ...
|
||||
|
||||
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.Info(`已安全退出 :)`) // Server exited
|
||||
|
||||
// if err := InitRouter().Run(env.Config.Main.Listen); err != nil {
|
||||
// env.Loger.NewGroup(`InitRouter().Run`).Fatal(`%s`, err)
|
||||
// }
|
||||
}
|
||||
|
132
menu.go
132
menu.go
@ -1,132 +0,0 @@
|
||||
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)
|
||||
// }
|
21
readme.md
21
readme.md
@ -11,15 +11,11 @@
|
||||
## 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)
|
||||
<!-- + *手持两把锟斤拷,口中疾呼烫烫烫。 脚踏千朵屯屯屯,笑看万物锘锘锘。* -->
|
||||
|
||||
### 注意
|
||||
+ 不保证内置源可用性,如需长期使用请自备会员账号
|
||||
+ 音源最好留着自己用,你非要当大好人当我没说(由于传播导致的账号被封与本项目无关)
|
||||
<!-- + **锟斤拷** -->
|
||||
|
||||
### 使用
|
||||
#### 服务端
|
||||
@ -52,8 +48,8 @@
|
||||
<!-- + ... -->
|
||||
|
||||
### 音乐源
|
||||
+ 内置源 (抓取自网络公开接口) **注:文明上网,请勿滥用,否则停止后续更新**
|
||||
+ 账号源 (登录会员账号解析) **注:可能导致封号,如出问题本项目不负责**
|
||||
+ 内置源 (抓取自网络公开接口) **注:文明上网,请勿滥用**
|
||||
+ 账号源 (登录Vip账号解析) **注:可能导致封号,如出问题本项目不负责**
|
||||
|
||||
### 开发
|
||||
+ 环境要求:Golang 1.21 (建议 >=1.20)
|
||||
@ -65,21 +61,18 @@
|
||||
- pkg/ 依赖包,一般在外部调用,不轻易修改
|
||||
- src/ 源码包,用于实现各种功能
|
||||
* env 公用变量,需要全局调用的参数
|
||||
* database 数据库相关
|
||||
* caches 文件缓存封装
|
||||
* server Gin路由
|
||||
* router 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),[WyApi](https://github.com/ZxwyWebSite/NeteaseCloudMusicApi),...
|
||||
+ 部分功能参考 [Python版](https://github.com/lxmusics/lx-music-api-server-python) 实现
|
||||
|
||||
### 更新
|
||||
+ 见 `update.md`
|
||||
@ -99,5 +92,5 @@
|
||||
|
||||
音乐平台不易,请尊重版权,支持正版。
|
||||
本项目仅用于对技术可行性的探索及研究,不接受任何商业(包括但不限于广告等)合作及捐赠。
|
||||
若对此有疑问请 mail to: admin+zxwy.link (请将`+`替换为`@`)
|
||||
若对此有疑问请 mail to: admin+zxwy.tk (请将`+`替换为`@`)
|
||||
|
107
release.go
107
release.go
@ -1,107 +0,0 @@
|
||||
//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()
|
||||
}
|
Binary file not shown.
@ -1,13 +1,10 @@
|
||||
package caches
|
||||
|
||||
import (
|
||||
"lx-source/src/env"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/ZxwyWebSite/ztool"
|
||||
"github.com/ZxwyWebSite/ztool/logs"
|
||||
)
|
||||
|
||||
type (
|
||||
@ -18,7 +15,6 @@ type (
|
||||
Quality string // quality 音质 128k / 320k / flac / flac24bit
|
||||
Extname string // rext 扩展名 mp3 / flac (没有前缀点)
|
||||
query string // 查询字符串缓存
|
||||
Request *http.Request
|
||||
}
|
||||
// 缓存需实现以下接口
|
||||
Cache interface {
|
||||
@ -44,6 +40,7 @@ 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) }}
|
||||
@ -55,7 +52,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`
|
||||
@ -85,7 +82,7 @@ func (c *Query) Query() string {
|
||||
return c.query
|
||||
}
|
||||
|
||||
// 分割查询字符串 (已弃用)
|
||||
// 分割查询字符串
|
||||
/*
|
||||
kg: 分割 Hash-Album 如 "6DC276334F56E22BE2A0E8254D332B45-13097991"
|
||||
tx: 分割 songmid-strMediaMid 如 "002fktJg3cmSpC-000V6uuv35Cwnh"
|
||||
@ -114,12 +111,3 @@ func MustNew(c Cache) Cache {
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
var Loger *logs.Logger
|
||||
|
||||
// 初始化Loger
|
||||
func init() {
|
||||
env.Inits.Add(func() {
|
||||
Loger = env.Loger.NewGroup(`Caches`)
|
||||
})
|
||||
}
|
||||
|
@ -1,121 +1 @@
|
||||
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
|
||||
}
|
||||
|
@ -4,11 +4,9 @@ import (
|
||||
"errors"
|
||||
"lx-source/src/caches"
|
||||
"lx-source/src/env"
|
||||
"lx-source/src/middleware/util"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ZxwyWebSite/ztool"
|
||||
)
|
||||
@ -19,17 +17,10 @@ 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 *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) getLink(q string) string {
|
||||
return ztool.Str_FastConcat(c.Bind, `/file/`, q) // c.Addr + `file/` + q
|
||||
}
|
||||
|
||||
func (c *Cache) Get(q *caches.Query) string {
|
||||
@ -40,7 +31,7 @@ func (c *Cache) Get(q *caches.Query) string {
|
||||
}
|
||||
// env.Cache.Set(q.Query(), struct{}{}, 3600)
|
||||
// }
|
||||
return c.getLink(q)
|
||||
return c.getLink(q.Query())
|
||||
// fpath := filepath.Join(c.Path, q.Source, q.MusicID, q.Quality)
|
||||
// if _, e := os.Stat(fpath); e != nil {
|
||||
// return ``
|
||||
@ -63,23 +54,14 @@ 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 {
|
||||
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)
|
||||
err := ztool.Net_DownloadFile(l, fpath, nil)
|
||||
if err != nil {
|
||||
loger.Error(`DownloadFile: %v`, err)
|
||||
return ``
|
||||
}
|
||||
// }
|
||||
// env.Cache.Set(q.Query(), struct{}{}, 3600)
|
||||
return c.getLink(q)
|
||||
return c.getLink(q.Query())
|
||||
// fpath := filepath.Join(c.Path, q.String)
|
||||
// os.MkdirAll(filepath.Dir(fpath), fs.ModePerm)
|
||||
// g := c.Loger.NewGroup(`localcache`)
|
||||
|
@ -1,38 +0,0 @@
|
||||
//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{}
|
@ -1,9 +0,0 @@
|
||||
//go:build cgo && gorm
|
||||
|
||||
package modules
|
||||
|
||||
// import (
|
||||
// "gorm.io/driver/sqlite"
|
||||
// )
|
||||
|
||||
// var Sqlite = sqlite.Open
|
@ -1,9 +0,0 @@
|
||||
//go:build !cgo && gorm
|
||||
|
||||
package modules
|
||||
|
||||
// import (
|
||||
// "github.com/glebarez/sqlite"
|
||||
// )
|
||||
|
||||
// var Sqlite = sqlite.Open
|
@ -1,118 +0,0 @@
|
||||
//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
|
||||
// )
|
181
src/env/env.go
vendored
181
src/env/env.go
vendored
@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
Version = `1.0.3.0622`
|
||||
Version = `1.0.2-b12`
|
||||
)
|
||||
|
||||
var (
|
||||
@ -26,34 +26,20 @@ var (
|
||||
序号 名称 描述
|
||||
*/
|
||||
type (
|
||||
// 系统
|
||||
Conf_Main struct {
|
||||
Debug bool `comment:"调试模式"`
|
||||
Listen []string `comment:"监听地址 (多端口以','分隔)"`
|
||||
Gzip bool `comment:"开启GZip (对已压缩的内容使用会产生反效果)"`
|
||||
Cors bool `comment:"添加跨域响应头 (兼容前端请求)"`
|
||||
LogPath string `comment:"文件日志路径,不填禁用"`
|
||||
Print bool `comment:"控制台输出 (影响io性能,后台使用建议关闭)"`
|
||||
SysLev bool `comment:"(实验性) 设置进程高优先级"`
|
||||
Debug bool `comment:"调试模式"`
|
||||
Listen string `comment:"监听地址"`
|
||||
Gzip bool `comment:"开启GZip (对已压缩的内容使用会产生反效果)"`
|
||||
LogPath string `comment:"文件日志路径,不填禁用"`
|
||||
Print bool `comment:"控制台输出"`
|
||||
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,相关设置
|
||||
// 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相关接口"`
|
||||
// BindAddr string `comment:"外部访问地址,用于生成文件链接"`
|
||||
// LxM_Auth string `comment:"验证Key,自动生成,填写null禁用"`
|
||||
}
|
||||
// 验证
|
||||
Conf_Auth struct {
|
||||
// ApiKey
|
||||
ApiKey_Enable bool `comment:"是否开启Key验证"`
|
||||
@ -61,18 +47,17 @@ 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"`
|
||||
@ -81,42 +66,18 @@ type (
|
||||
Proxy_Address string `comment:"代理地址 (支持http, socks)"`
|
||||
// 验证
|
||||
MusicIdVerify bool `comment:"(beta) 验证音乐ID可用性"`
|
||||
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:"是否开启小洛源"`
|
||||
ForceFallback bool `comment:"忽略音质限制,强制获取试听音频"`
|
||||
} // `comment:""`
|
||||
// 账号
|
||||
Conf_Custom struct {
|
||||
// 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:"下次刷新时间 (由程序维护)"`
|
||||
// wy (暂未实现)
|
||||
Wy_Enable bool `comment:"是否开启小芸源"`
|
||||
Wy_Cookie string `comment:"账号cookie数据"`
|
||||
|
||||
// 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:"下次运行时间 (自动更新)"`
|
||||
// mg (暂未实现)
|
||||
// Mg_Enable bool `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"`
|
||||
@ -124,66 +85,31 @@ 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 client
|
||||
Kg_Client_AppId string `comment:"酷狗音乐的appid,官方安卓为1005,官方PC为1001(client.appid)"`
|
||||
Kg_Client_SignKey string `comment:"客户端signature采用的key值,需要与appid对应(client.signatureKey)"`
|
||||
Kg_Client_Version string `comment:"客户端versioncode,pidversionsecret可能随此值而变化(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:""`
|
||||
// kg (暂未实现)
|
||||
// Kg_Enable bool `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_Update struct {
|
||||
Conf_Script 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用户名"`
|
||||
@ -191,7 +117,6 @@ type (
|
||||
Cloud_Sess string `comment:"Cloudreve会话"`
|
||||
Cloud_Path string `comment:"Cloudreve存储路径"`
|
||||
}
|
||||
// 结构
|
||||
Conf struct {
|
||||
Main Conf_Main `comment:"程序主配置"`
|
||||
Apis Conf_Apis `comment:"接口设置"`
|
||||
@ -208,14 +133,11 @@ var (
|
||||
DefCfg = Conf{
|
||||
Main: Conf_Main{
|
||||
Debug: false,
|
||||
Listen: []string{`127.0.0.1:1011`},
|
||||
Listen: `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/`,
|
||||
@ -224,72 +146,39 @@ 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`,
|
||||
Wy_Enable: true,
|
||||
|
||||
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`,
|
||||
Log: `发布更新 (请删除旧源后重新导入):进行了部分优化,修复了部分Bug`, // 更新日志
|
||||
|
||||
Update: Conf_Script_Update{
|
||||
Log: `发布更新 (请删除旧源后重新导入):进行了部分优化,修复了部分Bug`, // 更新日志
|
||||
Ver: `1.0.3`, // 自定义脚本版本
|
||||
Force: true, // 强制推送更新
|
||||
|
||||
Ver: `1.0.3`, // 自定义脚本版本
|
||||
Force: true, // 强制推送更新
|
||||
|
||||
Url: `lx-custom-source.js`, // 脚本下载地址
|
||||
},
|
||||
Url: `public/lx-custom-source.js`, // 脚本下载地址
|
||||
},
|
||||
Cache: Conf_Cache{
|
||||
Mode: `local`, // 缓存模式
|
||||
|
@ -2,7 +2,6 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"lx-source/src/env"
|
||||
"lx-source/src/middleware/resp"
|
||||
"sync/atomic"
|
||||
@ -18,12 +17,21 @@ 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 速率限制
|
||||
/*
|
||||
逻辑:
|
||||
@ -43,16 +51,9 @@ 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 := getIp(c)
|
||||
rip := c.RemoteIP()
|
||||
if rip == `` {
|
||||
rip = `0.0.0.0`
|
||||
}
|
||||
@ -83,24 +84,21 @@ 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()
|
||||
// }
|
||||
|
@ -1 +0,0 @@
|
||||
package auth
|
75
src/middleware/loadpublic/loadpublic.go
Normal file
75
src/middleware/loadpublic/loadpublic.go
Normal file
@ -0,0 +1,75 @@
|
||||
// 静态资源
|
||||
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)
|
||||
}
|
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 162 KiB |
@ -3,19 +3,23 @@
|
||||
* @description Client
|
||||
* version 1.0.1
|
||||
* @author Zxwy
|
||||
* homepage null
|
||||
* @homepage https://github.com/ZxwyWebSite/lx-source
|
||||
*/
|
||||
|
||||
// 脚本配置
|
||||
const version = '1.0.3' // 脚本版本
|
||||
const apiaddr = 'http://127.0.0.1:1011/' // 服务端地址,末尾加斜杠
|
||||
const apipass = '' // 验证密钥,填在单引号内
|
||||
const apipass = '' // 验证密钥,由服务端自动生成 '${apipass}'
|
||||
const devmode = true // 调试模式
|
||||
// const timeout = 60 * 1000 // 请求超时(ms)
|
||||
|
||||
// 常量 & 默认值
|
||||
const { EVENT_NAMES, request, on, send } = globalThis.lx
|
||||
const { EVENT_NAMES, request, on, send } = window.lx ?? 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, */*',
|
||||
@ -35,7 +39,6 @@ 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)
|
||||
@ -45,8 +48,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}/${quality}` //${(ext != '' && ext != void 0) ? '-' + ext : ''}
|
||||
const ext = source == 'kg' ? info.albumId : '' //source == 'tx' ? info.strMediaMid
|
||||
const query = `${source}/${id}${(ext != '' && ext != void 0) ? '-' + ext : ''}/${quality}`
|
||||
console.log('创建任务: %s, 音乐信息: %O', query, info)
|
||||
const body = await httpRequest(`${apiaddr}link/${query}`, { method: 'get' });
|
||||
console.log('返回数据: %O', body, `, 耗时 ${new Date().getTime() - start} ms`)
|
||||
@ -77,7 +80,8 @@ const init = () => {
|
||||
'use strict';
|
||||
console.log('初始化脚本, 版本: %s, 服务端地址: %s', version, apiaddr)
|
||||
var stat = false; var msg = ''; var updUrl = ''; var sourcess = {}
|
||||
httpRequest(apiaddr, { method: 'get', timeout: 1000 * 10 })
|
||||
httpRequest(apiaddr, { method: 'get' })
|
||||
.catch((err) => { msg = '初始化失败: ' + err ?? '连接服务端超时'; console.log(msg) })
|
||||
.then((body) => {
|
||||
if (!body) { msg = '初始化失败:' + '无返回数据'; return }
|
||||
console.log('获取服务端数据成功: %o, 版本: %s', body, body.version)
|
||||
@ -100,7 +104,7 @@ const init = () => {
|
||||
if (source[v] != null /*== true*/) {
|
||||
sourcess[v] = {
|
||||
name: v,
|
||||
...defs,
|
||||
...defs, // ...defaults,
|
||||
qualitys: source[v], // 支持返回音质时启用 使用后端音质表
|
||||
}
|
||||
}
|
||||
@ -108,13 +112,18 @@ 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 : '' })
|
@ -2,7 +2,6 @@
|
||||
package resp
|
||||
|
||||
import (
|
||||
"lx-source/src/env"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@ -10,7 +9,7 @@ import (
|
||||
|
||||
// 统一输出
|
||||
/*
|
||||
返回码对应表 (参考Python版):
|
||||
返回码对应表:
|
||||
0: http.StatusOK, // [200] 成功
|
||||
1: http.StatusForbidden, // [403] IP被封禁
|
||||
2: http.StatusServiceUnavailable, // [503] 获取失败
|
||||
@ -22,45 +21,36 @@ import (
|
||||
type Resp struct {
|
||||
Code int `json:"code"` // 状态码 为兼容内置源设置 暂无实际作用 (1.0.2后已兼容Python版定义)
|
||||
Msg string `json:"msg"` // 提示or报错信息
|
||||
Data any `json:"data"` // 音乐URL
|
||||
Ext any `json:"ext,omitempty"` // 其它信息
|
||||
Data string `json:"data"` // 音乐URL
|
||||
Ext string `json:"ext,omitempty"` // 其它信息
|
||||
}
|
||||
|
||||
// 获取失败默认音频
|
||||
// var ErrMp3 = `https://r2eu.zxwy.link/gh/lx-source/static/error.mp3`
|
||||
// 返回码对应列表 (参考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`
|
||||
|
||||
// 返回请求
|
||||
/*
|
||||
注:Code不为0时调用c.Abort()终止Handler
|
||||
*/
|
||||
func (o *Resp) Execute(c *gin.Context) {
|
||||
// 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, ok := statusMap[o.Code]
|
||||
if !ok {
|
||||
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)
|
||||
@ -75,9 +65,3 @@ 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)
|
||||
// }
|
||||
// }
|
||||
|
@ -1,21 +1,8 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"lx-source/src/env"
|
||||
"net/http"
|
||||
|
||||
"github.com/ZxwyWebSite/ztool"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
import "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)
|
||||
@ -24,51 +11,3 @@ 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)
|
||||
}
|
||||
|
198
src/router/router.go
Normal file
198
src/router/router.go
Normal file
@ -0,0 +1,198 @@
|
||||
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}
|
||||
})
|
||||
}
|
@ -1,158 +0,0 @@
|
||||
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
|
||||
})
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
//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`)
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
//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})
|
||||
})
|
||||
}
|
@ -1,170 +0,0 @@
|
||||
// 静态资源
|
||||
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)
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
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
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 162 KiB |
File diff suppressed because one or more lines are too long
@ -1,126 +0,0 @@
|
||||
<!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>
|
@ -1,192 +0,0 @@
|
||||
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}
|
||||
// })
|
||||
// }
|
@ -1,288 +1,239 @@
|
||||
// 内置解析源
|
||||
package builtin
|
||||
|
||||
// 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"
|
||||
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"
|
||||
|
||||
// "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
|
||||
// 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{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
|
||||
)
|
||||
|
||||
// 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 init() {
|
||||
env.Inits.Add(func() {
|
||||
if env.Config.Source.MusicIdVerify {
|
||||
wv_pool = &sync.Pool{New: func() any { return new(WyApi_Vef) }}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// // 查询
|
||||
// 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,
|
||||
// `×tamp=`, 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 Data:Api接口忙,请稍后重试`
|
||||
// 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)
|
||||
// 查询
|
||||
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,
|
||||
`×tamp=`, 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 Data:Api接口忙,请稍后重试`
|
||||
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)
|
||||
|
||||
// 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)
|
||||
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)
|
||||
|
||||
// // // 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 := 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 := 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
|
||||
}
|
||||
|
@ -1,274 +1,293 @@
|
||||
package builtin
|
||||
|
||||
// import (
|
||||
// "lx-source/src/sources"
|
||||
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`
|
||||
// )
|
||||
|
||||
// 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"`
|
||||
// // }
|
||||
// )
|
||||
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/`}
|
||||
)
|
||||
|
||||
// // 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/`}
|
||||
// )
|
||||
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)
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
package custom
|
||||
|
||||
// type CSource struct{}
|
@ -1,134 +1,2 @@
|
||||
// 账号解析源
|
||||
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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1,70 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,104 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,265 +0,0 @@
|
||||
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)
|
||||
}*/
|
||||
})
|
||||
}
|
@ -1,503 +0,0 @@
|
||||
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"`
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
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
|
||||
// }
|
@ -9,7 +9,6 @@ import (
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/ZxwyWebSite/ztool/x/bytesconv"
|
||||
"github.com/ZxwyWebSite/ztool/zcypt"
|
||||
)
|
||||
|
||||
// 常量和数组
|
||||
@ -251,9 +250,8 @@ 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)
|
||||
return zcypt.Base64ToString(base64.StdEncoding, b1)
|
||||
// s := base64.StdEncoding.EncodeToString(b1)
|
||||
// return s //strings.ReplaceAll(s, "\n", ``)
|
||||
s := base64.StdEncoding.EncodeToString(b1)
|
||||
return s //strings.ReplaceAll(s, "\n", ``)
|
||||
}
|
||||
|
@ -1,19 +1,15 @@
|
||||
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 (
|
||||
@ -23,7 +19,6 @@ var (
|
||||
|
||||
parsemod bool
|
||||
convtype string
|
||||
desource string
|
||||
// desParse func([]byte, *playInfo) error
|
||||
// desParse func(any) ztool.Net_ResHandlerFunc
|
||||
)
|
||||
@ -33,7 +28,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,
|
||||
@ -41,47 +36,36 @@ 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) }}
|
||||
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)
|
||||
}
|
||||
Url = kwdes
|
||||
default:
|
||||
loger.Fatal(`未定义的接口模式,请检查配置 [Custom].Kw_Mode`)
|
||||
}
|
||||
loger.Free()
|
||||
loger = nil
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
@ -94,7 +78,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=`,
|
||||
`&uid=`, env.Config.Custom.Kw_Bd_Uid,
|
||||
`&uin=`, env.Config.Custom.Kw_Bd_Uid,
|
||||
`&token=`, env.Config.Custom.Kw_Bd_Token,
|
||||
)
|
||||
// jx.Debug(`Kw, Url: %s`, url)
|
||||
@ -107,7 +91,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(strconv.Itoa(resp.Code), `: `, resp.Msg)
|
||||
msg = ztool.Str_FastConcat(`failed: `, resp.Msg)
|
||||
return
|
||||
}
|
||||
ourl = utils.DelQuery(resp.Data.URL) //strings.Split(resp.Data.URL, `?`)[0]
|
||||
@ -116,7 +100,6 @@ 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
|
||||
@ -124,13 +107,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(
|
||||
`corp=kuwo&p2p=1&sig=0¬race=0&priority=bitrate&network=WIFI&mode=down`,
|
||||
`&source=`, desource,
|
||||
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¬race=0`,
|
||||
`&type=`, convtype,
|
||||
`&br=`, infoFile.H, infoFile.E,
|
||||
`&format=`, infoFile.E,
|
||||
`&rid=`, songMid,
|
||||
`&priority=bitrate&loginUid=0&network=WIFI&loginSid=0&mode=down`,
|
||||
)),
|
||||
)
|
||||
if parsemod {
|
||||
@ -146,7 +129,6 @@ 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)
|
||||
@ -154,13 +136,11 @@ func kwdes(songMid, quality string) (ourl, msg string) {
|
||||
return
|
||||
}
|
||||
realQuality := strconv.Itoa(resp.Data.Bitrate)
|
||||
if realQuality != infoFile.H[:len(infoFile.H)-1] /*&& resp.Data.Bitrate != 1*/ {
|
||||
if realQuality != infoFile.H[:len(infoFile.H)-1] {
|
||||
msg = sources.E_QNotMatch
|
||||
if !env.Config.Source.ForceFallback {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
ourl = resp.Data.URL //resp.Data.URL[:strings.Index(resp.Data.URL, `?`)]
|
||||
ourl = utils.DelQuery(resp.Data.URL) //resp.Data.URL[:strings.Index(resp.Data.URL, `?`)]
|
||||
return
|
||||
}
|
||||
ztool.Net_Request(http.MethodGet, target_url, nil,
|
||||
@ -184,9 +164,7 @@ 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
|
||||
}
|
||||
return
|
||||
}
|
||||
ourl = utils.DelQuery(infoData[`url`]) //infoData[`url`][:strings.Index(infoData[`url`], `?`)]
|
||||
return
|
||||
@ -195,133 +173,3 @@ 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
|
||||
}
|
||||
|
@ -13,8 +13,8 @@ type (
|
||||
Format string `json:"format"`
|
||||
P2PAudiosourceid string `json:"p2p_audiosourceid"`
|
||||
Rid int `json:"rid"`
|
||||
// Source string `json:"source"`
|
||||
URL string `json:"url"`
|
||||
Source string `json:"source"`
|
||||
URL string `json:"url"`
|
||||
} `json:"data"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
@ -13,11 +13,11 @@ var (
|
||||
H string // 专用音质
|
||||
}{
|
||||
sources.Q_128k: {
|
||||
E: sources.X_mp3,
|
||||
E: `mp3`,
|
||||
H: sources.Q_128k,
|
||||
},
|
||||
sources.Q_320k: {
|
||||
E: sources.X_mp3,
|
||||
E: `mp3`,
|
||||
H: sources.Q_320k,
|
||||
},
|
||||
sources.Q_flac: {
|
||||
@ -40,64 +40,25 @@ var (
|
||||
// `User-Agent`: `okhttp/3.10.0`,
|
||||
}
|
||||
bdheader = map[string]string{
|
||||
`channel`: `guanfang`,
|
||||
`channel`: `qq`,
|
||||
`plat`: `ar`,
|
||||
`net`: `wifi`,
|
||||
`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)`,
|
||||
`ver`: `3.1.2`,
|
||||
// `uid`: ``,
|
||||
// `devId`: `0`,
|
||||
}
|
||||
// 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++ {
|
||||
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 {
|
||||
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
|
||||
}*/
|
||||
|
@ -1,41 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,185 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
@ -1,533 +0,0 @@
|
||||
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"`
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
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`: ``,
|
||||
}
|
||||
)
|
@ -1,5 +1,11 @@
|
||||
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 != `` {
|
||||
@ -23,3 +29,14 @@ package tx
|
||||
// }
|
||||
// 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1,355 +0,0 @@
|
||||
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_()
|
@ -1,27 +1,200 @@
|
||||
package tx
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"lx-source/src/env"
|
||||
"lx-source/src/sources"
|
||||
"strings"
|
||||
|
||||
"github.com/ZxwyWebSite/ztool"
|
||||
"github.com/ZxwyWebSite/ztool/x/bytesconv"
|
||||
)
|
||||
|
||||
func init() {
|
||||
gob.Register(musicInfo{})
|
||||
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 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"`
|
||||
@ -43,6 +216,5 @@ 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, ``
|
||||
}
|
||||
|
@ -8,6 +8,62 @@ 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无账号信息:
|
||||
@ -31,54 +87,33 @@ import (
|
||||
返回结果
|
||||
更新:
|
||||
可通过 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 {
|
||||
if infoBody.TrackInfo.Vs[0] == `` {
|
||||
msg = sources.ErrNoLink
|
||||
return
|
||||
}
|
||||
strFileName = ztool.Str_FastConcat(`RS02`, infoBody.TrackInfo.Vs[0], `.`, sources.X_mp3)
|
||||
strFileName = ztool.Str_FastConcat(`RS02`, infoBody.TrackInfo.Vs[0], `.mp3`)
|
||||
} else {
|
||||
strFileName = ztool.Str_FastConcat(infoFile.H, infoBody.TrackInfo.File.MediaMid, `.`, infoFile.E)
|
||||
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,
|
||||
`"}}}`,
|
||||
)
|
||||
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"}}}`)
|
||||
var infoResp struct {
|
||||
Code int `json:"code"`
|
||||
// Ts int64 `json:"ts"`
|
||||
@ -88,28 +123,12 @@ Loop:
|
||||
}
|
||||
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
|
||||
}
|
||||
@ -120,10 +139,8 @@ Loop:
|
||||
// }
|
||||
if realQuality != infoFile.H && !tryLink {
|
||||
msg = sources.E_QNotMatch
|
||||
if !env.Config.Source.ForceFallback {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
ourl = env.Config.Custom.Tx_CDNUrl + infoData.Purl
|
||||
ourl = ztool.Str_FastConcat(`https://ws.stream.qqmusic.qq.com/`, infoData.Purl)
|
||||
return
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
package tx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"lx-source/src/env"
|
||||
"net/http"
|
||||
"strings"
|
||||
@ -80,15 +78,25 @@ type refreshData struct {
|
||||
第一次载入时会刷新一次测试可用性&同步过期时间 (默认7天)
|
||||
*/
|
||||
|
||||
func refresh(loger *logs.Logger, now int64) error {
|
||||
// 前置检测
|
||||
if now < env.Config.Custom.Tx_Refresh_Interval {
|
||||
loger.Debug(`Key未过期,跳过...`)
|
||||
return nil
|
||||
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 {
|
||||
loger.Debug(`Key未过期,跳过...`)
|
||||
return
|
||||
}
|
||||
// 刷新逻辑 (注:QQ登录最常用所以先判断QHL开头)
|
||||
var body, surl string
|
||||
if strings.HasPrefix(env.Config.Custom.Tx_Ukey, `Q_H_L`) {
|
||||
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`) {
|
||||
body = ztool.Str_FastConcat(
|
||||
`{"req1":{"method":"QQLogin","module":"QQConnectLogin.LoginServer","param":{"expired_in":7776000,"musicid":`,
|
||||
env.Config.Custom.Tx_Uuin,
|
||||
@ -97,69 +105,43 @@ func refresh(loger *logs.Logger, now int64) error {
|
||||
`"}}}`,
|
||||
)
|
||||
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 {
|
||||
// 致命错误(删除任务)
|
||||
panic(`未知的 qqmusic_key 格式, 请检查配置 [Custom].Tx_Ukey`)
|
||||
loger.Error(`未知的qqmusic_key格式`)
|
||||
env.Config.Custom.Tx_Refresh_Enable = false // 本次启动阻止继续执行
|
||||
return
|
||||
}
|
||||
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 errors.New(`请求Api失败: ` + err.Error())
|
||||
loger.Error(`请求Api失败: %s`, err)
|
||||
return
|
||||
}
|
||||
loger.Debug(`Resp: %+v`, resp)
|
||||
if resp.Req1.Code != 0 {
|
||||
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.Warn("刷新登录失败, code: %v\n响应体: %+v", resp.Req1.Code, resp)
|
||||
return
|
||||
}
|
||||
loger.Info(`刷新登录成功`)
|
||||
env.Config.Custom.Tx_Uuin = resp.Req1.Data.StrMusicId
|
||||
env.Config.Custom.Tx_Ukey = resp.Req1.Data.MusicKey
|
||||
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天提前一天
|
||||
env.Config.Custom.Tx_Refresh_Interval = resp.Req1.Data.ExpiredAt - 86000 // 提前一天
|
||||
loger.Debug(`Resp: %+v`, resp)
|
||||
loger.Debug(`Uuin: %v, Ukey: %v`, resp.Req1.Data.StrMusicId, resp.Req1.Data.MusicKey)
|
||||
loger.Debug(`ExpiresAt: %v, Real: %v`, resp.Req1.Data.ExpiredAt, env.Config.Custom.Tx_Refresh_Interval)
|
||||
loger.Debug(`ExpiresAt: %v`, resp.Req1.Data.ExpiredAt)
|
||||
err = env.Cfg.Save(``)
|
||||
// if err != nil {
|
||||
// loger.Error(`%s`, err)
|
||||
// return
|
||||
// }
|
||||
if err == nil {
|
||||
loger.Info(`数据更新成功`) // 已通过相应数据更新uin和qqmusic_key
|
||||
if err != nil {
|
||||
loger.Error(`%s`, err)
|
||||
return
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
loger.Info(`数据更新成功`) // 已通过相应数据更新uin和qqmusic_key
|
||||
}
|
@ -1,246 +0,0 @@
|
||||
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"`
|
||||
}
|
@ -14,28 +14,28 @@ var (
|
||||
H string // 专用音质
|
||||
}{
|
||||
sources.Q_128k: {
|
||||
E: sources.X_mp3,
|
||||
E: `.mp3`,
|
||||
H: `M500`,
|
||||
},
|
||||
sources.Q_320k: {
|
||||
E: sources.X_mp3,
|
||||
E: `.mp3`,
|
||||
H: `M800`,
|
||||
},
|
||||
sources.Q_flac: {
|
||||
E: sources.Q_flac,
|
||||
E: `.flac`,
|
||||
H: `F000`,
|
||||
},
|
||||
sources.Q_fl24: {
|
||||
E: sources.Q_flac,
|
||||
E: `.flac`,
|
||||
H: `RS01`,
|
||||
},
|
||||
sources.Q_dolby: {
|
||||
E: sources.Q_flac,
|
||||
`dolby`: {
|
||||
E: `.flac`,
|
||||
H: `Q000`,
|
||||
},
|
||||
sources.Q_master: {
|
||||
E: sources.Q_flac,
|
||||
H: `AI00`, // (~~母带音质大部分都是AI提上去的~~)
|
||||
`master`: {
|
||||
E: `.flac`,
|
||||
H: `AI00`,
|
||||
},
|
||||
}
|
||||
// qualityMapReverse = map[string]string{
|
||||
|
98
src/sources/custom/wy/encrypt.go
Normal file
98
src/sources/custom/wy/encrypt.go
Normal file
@ -0,0 +1,98 @@
|
||||
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
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
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"`
|
||||
}
|
||||
)
|
@ -1,19 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
package wy
|
||||
|
||||
// 二维码生成接口
|
||||
func LoginQrCreate(key string) string {
|
||||
return `https://music.163.com/login?codekey=` + key
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
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`,
|
||||
},
|
||||
)
|
||||
}
|
@ -1,126 +1,34 @@
|
||||
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"
|
||||
"sync"
|
||||
"time"
|
||||
"strings"
|
||||
|
||||
"github.com/ZxwyWebSite/ztool"
|
||||
"github.com/ZxwyWebSite/ztool/x/cookie"
|
||||
)
|
||||
|
||||
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) {
|
||||
func Url(songMid, quality string) (ourl, msg string) {
|
||||
loger := env.Loger.NewGroup(`Wy`)
|
||||
defer loger.Free()
|
||||
rquality, ok := qualityMap[quality]
|
||||
rquality, ok := brMap[quality]
|
||||
if !ok {
|
||||
msg = sources.E_QNotSupport
|
||||
return
|
||||
}
|
||||
resp := wy_pool.Get().(*wm.PlayInfo)
|
||||
defer wy_pool.Put(resp)
|
||||
url := ztool.Str_FastConcat(
|
||||
`https://`, example.Api_wy, `&id=`, songMid, `&level=`, rquality,
|
||||
`×tamp=`, 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 Data:Api接口忙,请稍后重试`
|
||||
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{
|
||||
cookies := cookie.Parse(env.Config.Custom.Wy_Cookie)
|
||||
answer, err := SongUrl(song_url_query{
|
||||
Cookie: cookie.ToMap(cookies),
|
||||
Ids: songMid,
|
||||
// Br: rquality,
|
||||
Level: rquality,
|
||||
Br: rquality,
|
||||
})
|
||||
body := wy_pool.Get().(*wm.PlayInfo)
|
||||
defer wy_pool.Put(body)
|
||||
var body builtin.WyApi_Song
|
||||
if err == nil {
|
||||
err = ztool.Val_MapToStruct(answer.Body, &body)
|
||||
}
|
||||
@ -135,48 +43,52 @@ func nmModule(songMid, quality string) (ourl, msg string) {
|
||||
return
|
||||
}
|
||||
data := body.Data[0]
|
||||
if data.Code != 200 {
|
||||
msg = `触发风控或专辑单独收费: ` + strconv.Itoa(data.Code)
|
||||
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
|
||||
}
|
||||
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 nmCustom(songMid, quality string) (ourl, msg string) {
|
||||
func PyUrl(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
|
||||
}
|
||||
body := wy_pool.Get().(*wm.PlayInfo)
|
||||
defer wy_pool.Put(body)
|
||||
err := ztool.Net_Request(
|
||||
http.MethodGet,
|
||||
ztool.Str_FastConcat(
|
||||
env.Config.Custom.Wy_Api_Address, `song/url/v1`, `?id=`, songMid, `&level=`, rquality,
|
||||
// `×tamp=`, 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{ztool.Net_ResToStruct(&body)},
|
||||
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 {
|
||||
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,
|
||||
})},
|
||||
[]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),
|
||||
},
|
||||
)
|
||||
// }
|
||||
if err != nil {
|
||||
loger.Error(`SongUrl: %s`, err)
|
||||
loger.Error(`Request: %s`, err)
|
||||
msg = sources.ErrHttpReq
|
||||
return
|
||||
}
|
||||
@ -186,15 +98,9 @@ func nmCustom(songMid, quality string) (ourl, msg string) {
|
||||
return
|
||||
}
|
||||
data := body.Data[0]
|
||||
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
|
||||
}
|
||||
msg = sources.E_QNotMatch
|
||||
return
|
||||
}
|
||||
ourl = utils.DelQuery(data.URL)
|
||||
return
|
||||
|
@ -1,83 +0,0 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
42
src/sources/custom/wy/song_url.go
Normal file
42
src/sources/custom/wy/song_url.go
Normal file
@ -0,0 +1,42 @@
|
||||
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`,
|
||||
},
|
||||
)
|
||||
}
|
@ -3,21 +3,20 @@ 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`,
|
||||
|
||||
sources.Q_dolby: `jyeffect`,
|
||||
sources.Q_sky: sources.Q_sky,
|
||||
sources.Q_master: `jymaster`,
|
||||
`dolby`: `jyeffect`,
|
||||
`sky`: `jysky`,
|
||||
`master`: `jymaster`,
|
||||
}
|
||||
// 优化:返回音质与查询音质相同,完全可以直接比较,不用多一步Reverse
|
||||
// qualityMapReverse = map[string]string{
|
||||
|
@ -20,11 +20,7 @@ import (
|
||||
|
||||
const anonymous_token = `1f5fa7b6a6a9f81a11886e5186fde7fb98e25cf0036d7afd055b980b2261f5464b7f5273fc3921d1262bfec66a19a30c41d8da00c3685f5ace96f0d5a48b6db334d974731083682e3324751bcc9aaf44c3061cd1`
|
||||
|
||||
var (
|
||||
wapiReg = regexp.MustCompile(`\w*api`)
|
||||
csrfReg = regexp.MustCompile(`_csrf=([^(;|$)]+)`)
|
||||
domaReg = regexp.MustCompile(`\s*Domain=[^(;|$)]+;*`)
|
||||
)
|
||||
var wapiReg = regexp.MustCompile(`\w*api`)
|
||||
|
||||
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`,
|
||||
@ -32,30 +28,24 @@ var userAgentMap = map[string]string{
|
||||
}
|
||||
|
||||
type (
|
||||
ReqQuery struct {
|
||||
Cookie map[string]string
|
||||
RealIP string
|
||||
Ids string
|
||||
Br string
|
||||
Level string
|
||||
}
|
||||
// reqCookie struct{}
|
||||
reqOptions struct {
|
||||
Headers map[string]string
|
||||
UA string
|
||||
Headers map[string]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)
|
||||
}
|
||||
@ -106,24 +96,26 @@ func createRequest(method, url string, data map[string]any, options reqOptions)
|
||||
var form stdurl.Values
|
||||
switch options.Crypto {
|
||||
case `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`)
|
||||
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`)
|
||||
case `linuxapi`:
|
||||
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`
|
||||
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`
|
||||
case `eapi`:
|
||||
cookie, ok := options.Cookie.(map[string]string)
|
||||
if !ok {
|
||||
@ -176,7 +168,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{
|
||||
@ -186,20 +178,17 @@ 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 {
|
||||
// fmt.Println(`body:`, string(body), "\nstr:", body)
|
||||
if len(body) == 0 {
|
||||
err = errors.New(`nil Body`)
|
||||
answer.Cookie = res.Header[`Set-Cookie`]
|
||||
reg := regexp.MustCompile(`\s*Domain=[^(;|$)]+;*`)
|
||||
for i, v := range answer.Cookie {
|
||||
answer.Cookie[i] = reg.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 {
|
||||
answer.Cookie = res.Header[`Set-Cookie`] //res.Header.Values(`set-cookie`)
|
||||
for i, v := range answer.Cookie {
|
||||
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 code, ok := answer.Body[`code`].(string); ok {
|
||||
answer.Body[`code`], err = strconv.Atoi(code)
|
||||
} else {
|
||||
@ -223,10 +212,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.Error()}
|
||||
// return err
|
||||
answer.Body = map[string]any{`code`: 502, `msg`: err}
|
||||
return err
|
||||
}
|
||||
return err // nil
|
||||
return nil
|
||||
},
|
||||
},
|
||||
)
|
@ -1,38 +0,0 @@
|
||||
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)
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
package sources
|
||||
|
||||
// import (
|
||||
// "lx-source/src/caches"
|
||||
// )
|
||||
import (
|
||||
"lx-source/src/caches"
|
||||
)
|
||||
|
||||
// var Loger = env.Loger.NewGroup(`Sources`) // JieXiApis
|
||||
|
||||
@ -13,14 +13,6 @@ 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` // 小蜜
|
||||
@ -37,26 +29,6 @@ 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} // 全部平台
|
||||
)
|
||||
|
||||
// 源查询接口
|
||||
@ -71,58 +43,24 @@ var (
|
||||
验证部分放到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 (
|
||||
// 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)
|
||||
// type Error struct {
|
||||
// msg string
|
||||
// }
|
||||
|
||||
// 验证失败(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: ``,
|
||||
// }
|
||||
// func (e *Error) Error() string {
|
||||
// return ztool.Str_FastConcat(e.msg)
|
||||
// }
|
||||
|
@ -1,48 +0,0 @@
|
||||
//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
202
update.md
@ -1,207 +1,5 @@
|
||||
## 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项目进行修改
|
||||
|
Loading…
x
Reference in New Issue
Block a user