mirror of
https://github.com/ZxwyWebSite/lx-source.git
synced 2025-07-05 19:49:03 +08:00
Compare commits
15 Commits
v1.0.2-b12
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
9155f34105 | ||
|
3437281f73 | ||
|
55e7c6227e | ||
|
76e7f5a1b6 | ||
|
70238162c1 | ||
|
d7a2563a98 | ||
|
05b8f3766c | ||
|
30b3260617 | ||
|
e402c10381 | ||
|
1ddc08d3b4 | ||
|
0b44b071c0 | ||
|
07c32ab6d4 | ||
|
2dea36c3ce | ||
|
32c2617f3f | ||
|
ad7614c564 |
107
.github/workflows/action.yml
vendored
Normal file
107
.github/workflows/action.yml
vendored
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
name: Action
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout git repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
path: ./repo
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Golang
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.21'
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: |
|
||||||
|
go version && go env && export PATH=$PATH:$(go env GOPATH)/bin
|
||||||
|
go install golang.org/dl/go1.20.14@latest && go1.20.14 download && go1.20.14 version
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get -y install gcc-mingw-w64-x86-64
|
||||||
|
sudo apt-get -y install gcc-arm-linux-gnueabihf libc6-dev-armhf-cross
|
||||||
|
sudo apt-get -y install gcc-aarch64-linux-gnu libc6-dev-arm64-cross
|
||||||
|
wget -q https://dl.google.com/android/repository/android-ndk-r26b-linux.zip && unzip -d ~ android-ndk-r26b-linux.zip && rm android-ndk-r26b-linux.zip
|
||||||
|
|
||||||
|
- name: Fetch Modules
|
||||||
|
run: |
|
||||||
|
wget -q "https://r2eu.zxwy.link/gh/lx-source/static/ztool_20240525.zip" -O ztool.zip && unzip ztool.zip && rm ztool.zip
|
||||||
|
wget -q "https://r2eu.zxwy.link/gh/lx-source/static/cr-go-sdk_20240525.zip" -O cr-go-sdk.zip && unzip cr-go-sdk.zip && rm cr-go-sdk.zip
|
||||||
|
|
||||||
|
- name: Run Action
|
||||||
|
run: cd repo && go run action.go && mv dist ../
|
||||||
|
|
||||||
|
- name: Short SHA
|
||||||
|
uses: benjlevesque/short-sha@v3.0
|
||||||
|
id: short-sha
|
||||||
|
|
||||||
|
- name: Upload Artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: lx-source-bin_${{ env.SHA }}
|
||||||
|
path: ./dist
|
||||||
|
|
||||||
|
- name: Generate Changelog
|
||||||
|
run: cd repo && echo PACKAGE_VERSION=`go run release.go` >> $GITHUB_ENV && mv changelog.md ../
|
||||||
|
|
||||||
|
- name: Create git tag
|
||||||
|
uses: pkgdeps/git-tag-action@v3
|
||||||
|
with:
|
||||||
|
github_token: ${{ github.token }}
|
||||||
|
github_repo: ${{ github.repository }}
|
||||||
|
version: ${{ env.PACKAGE_VERSION }}
|
||||||
|
git_commit_sha: ${{ github.sha }}
|
||||||
|
git_tag_prefix: "v"
|
||||||
|
|
||||||
|
- name: Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
body_path: ./changelog.md
|
||||||
|
prerelease: false
|
||||||
|
draft: false
|
||||||
|
tag_name: v${{ env.PACKAGE_VERSION }}
|
||||||
|
files: |
|
||||||
|
./dist/lx-source-android-386.zip
|
||||||
|
./dist/lx-source-android-amd64.zip
|
||||||
|
./dist/lx-source-android-arm.zip
|
||||||
|
./dist/lx-source-android-arm64.zip
|
||||||
|
./dist/lx-source-darwin-amd64v2-go1.20.14.zip
|
||||||
|
./dist/lx-source-darwin-amd64v3-go1.20.14.zip
|
||||||
|
./dist/lx-source-darwin-arm64-go1.20.14.zip
|
||||||
|
./dist/lx-source-linux-amd64v1.zip
|
||||||
|
./dist/lx-source-linux-amd64v2.zip
|
||||||
|
./dist/lx-source-linux-amd64v3.zip
|
||||||
|
./dist/lx-source-linux-amd64v4.zip
|
||||||
|
./dist/lx-source-linux-arm5.zip
|
||||||
|
./dist/lx-source-linux-arm5-go1.20.14.zip
|
||||||
|
./dist/lx-source-linux-arm6.zip
|
||||||
|
./dist/lx-source-linux-arm6-go1.20.14.zip
|
||||||
|
./dist/lx-source-linux-arm64.zip
|
||||||
|
./dist/lx-source-linux-arm64-go1.20.14.zip
|
||||||
|
./dist/lx-source-linux-arm7.zip
|
||||||
|
./dist/lx-source-linux-arm7-go1.20.14.zip
|
||||||
|
./dist/lx-source-linux-mips64hardfloat-go1.20.14.zip
|
||||||
|
./dist/lx-source-linux-mips64lehardfloat-go1.20.14.zip
|
||||||
|
./dist/lx-source-linux-mips64lesoftfloat-go1.20.14.zip
|
||||||
|
./dist/lx-source-linux-mips64softfloat-go1.20.14.zip
|
||||||
|
./dist/lx-source-linux-mipshardfloat-go1.20.14.zip
|
||||||
|
./dist/lx-source-linux-mipslehardfloat-go1.20.14.zip
|
||||||
|
./dist/lx-source-linux-mipslesoftfloat-go1.20.14.zip
|
||||||
|
./dist/lx-source-linux-mipssoftfloat-go1.20.14.zip
|
||||||
|
./dist/lx-source-windows-amd64v1-go1.20.14.zip
|
||||||
|
./dist/lx-source-windows-amd64v2-go1.20.14.zip
|
||||||
|
./dist/lx-source-windows-amd64v2.zip
|
||||||
|
./dist/lx-source-windows-amd64v3-go1.20.14.zip
|
||||||
|
./dist/lx-source-windows-amd64v3.zip
|
||||||
|
./dist/lx-source-windows-amd64v4.zip
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ github.token }}
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,4 +6,3 @@ data/
|
|||||||
test.go
|
test.go
|
||||||
test_test.go
|
test_test.go
|
||||||
# src/sources/builtin/
|
# src/sources/builtin/
|
||||||
rsrc_windows_amd64.syso
|
|
532
action.go
Normal file
532
action.go
Normal file
@ -0,0 +1,532 @@
|
|||||||
|
//go:build ignore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// 运行参数
|
||||||
|
args_name = `lx-source` // 程序名称
|
||||||
|
args_path = `dist/` // 输出目录
|
||||||
|
args_zpak = true // 打包文件
|
||||||
|
// args_repo = `repo/` // 源码目录
|
||||||
|
args_home = `/home/runner` // 用户目录
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
workDir string
|
||||||
|
homeDir string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if runtime.GOOS != `linux` {
|
||||||
|
fmt.Println(`不兼容的运行环境:`, runtime.GOOS)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
workDir, _ = os.Getwd()
|
||||||
|
fmt.Println(`运行目录:`, workDir)
|
||||||
|
homeDir = os.Getenv(`HOME`)
|
||||||
|
if homeDir == `` {
|
||||||
|
homeDir = args_home
|
||||||
|
}
|
||||||
|
fmt.Println(`用户目录:`, homeDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// 架构参数 [v2]
|
||||||
|
list_vers map[string]struct {
|
||||||
|
Tags string
|
||||||
|
}
|
||||||
|
// CGO参数 [nil]
|
||||||
|
list_cgos struct {
|
||||||
|
AR string
|
||||||
|
CC string
|
||||||
|
CXX string
|
||||||
|
}
|
||||||
|
// 架构列表 [amd64]
|
||||||
|
list_arch map[string]struct {
|
||||||
|
Cgos *list_cgos
|
||||||
|
Vers list_vers
|
||||||
|
Venv string // 覆盖架构参数名 'mipsle'->'GOMIPS'
|
||||||
|
}
|
||||||
|
// 目标系统 [linux]
|
||||||
|
list_goos map[string]struct {
|
||||||
|
Arch list_arch
|
||||||
|
}
|
||||||
|
// 编译环境 [go1.20.14]
|
||||||
|
list_conf map[string]struct {
|
||||||
|
Args []string
|
||||||
|
GoOS list_goos
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 构建参数
|
||||||
|
var def_args = []string{
|
||||||
|
`-trimpath`, `-buildvcs=false`,
|
||||||
|
`-ldflags`, `-s -w -linkmode external`,
|
||||||
|
}
|
||||||
|
|
||||||
|
type param struct {
|
||||||
|
GoVer string // 环境 go1.20.14
|
||||||
|
GoOS string // 系统 linux
|
||||||
|
GoArch string // 架构 amd64
|
||||||
|
GoIns string // 指令 GOAMD64=v2
|
||||||
|
Args []string // 参数 ldflags
|
||||||
|
Tag string // 标志 go_json
|
||||||
|
Cgos *list_cgos
|
||||||
|
Venv string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取相对用户目录
|
||||||
|
func home(str string) string {
|
||||||
|
return homeDir + `/` + str
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测环境是否存在
|
||||||
|
func chkenv(s ...string) (err error) {
|
||||||
|
for _, f := range s {
|
||||||
|
if _, e := exec.LookPath(f); e != nil && !errors.Is(e, exec.ErrDot) {
|
||||||
|
err = fmt.Errorf(`未找到指定环境: %s`, e)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var def_list = list_conf{
|
||||||
|
`go`: {
|
||||||
|
Args: def_args,
|
||||||
|
GoOS: list_goos{
|
||||||
|
`linux`: {
|
||||||
|
Arch: list_arch{
|
||||||
|
`amd64`: {
|
||||||
|
Cgos: &list_cgos{
|
||||||
|
AR: `x86_64-linux-gnu-ar`,
|
||||||
|
CC: `x86_64-linux-gnu-gcc`,
|
||||||
|
CXX: `x86_64-linux-gnu-g++`,
|
||||||
|
},
|
||||||
|
Vers: list_vers{
|
||||||
|
`v1`: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
`v2`: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
`v3`: {
|
||||||
|
Tags: `sonic avx`,
|
||||||
|
},
|
||||||
|
`v4`: {
|
||||||
|
Tags: `sonic avx`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
`arm`: {
|
||||||
|
Cgos: &list_cgos{
|
||||||
|
AR: `arm-linux-gnueabihf-gcc-ar`,
|
||||||
|
CC: `arm-linux-gnueabihf-gcc`,
|
||||||
|
CXX: `arm-linux-gnueabihf-cpp`,
|
||||||
|
},
|
||||||
|
Vers: list_vers{
|
||||||
|
`5`: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
`6`: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
`7`: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
`arm64`: {
|
||||||
|
Cgos: &list_cgos{
|
||||||
|
AR: `aarch64-linux-gnu-gcc-ar`,
|
||||||
|
CC: `aarch64-linux-gnu-gcc`,
|
||||||
|
CXX: `aarch64-linux-gnu-cpp`,
|
||||||
|
},
|
||||||
|
Vers: list_vers{
|
||||||
|
``: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
`windows`: {
|
||||||
|
Arch: list_arch{
|
||||||
|
`amd64`: {
|
||||||
|
Cgos: &list_cgos{
|
||||||
|
AR: `x86_64-w64-mingw32-ar`,
|
||||||
|
CC: `x86_64-w64-mingw32-gcc`,
|
||||||
|
CXX: `x86_64-w64-mingw32-cpp`,
|
||||||
|
},
|
||||||
|
Vers: list_vers{
|
||||||
|
`v2`: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
`v3`: {
|
||||||
|
Tags: `sonic avx`,
|
||||||
|
},
|
||||||
|
`v4`: {
|
||||||
|
Tags: `sonic avx`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
`android`: {
|
||||||
|
Arch: list_arch{
|
||||||
|
`amd64`: {
|
||||||
|
Cgos: &list_cgos{
|
||||||
|
AR: home(`android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar`),
|
||||||
|
CC: home(`android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android24-clang`),
|
||||||
|
CXX: home(`android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android24-clang++`),
|
||||||
|
},
|
||||||
|
Vers: list_vers{
|
||||||
|
``: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
`arm64`: {
|
||||||
|
Cgos: &list_cgos{
|
||||||
|
AR: home(`android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar`),
|
||||||
|
CC: home(`android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang`),
|
||||||
|
CXX: home(`android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang++`),
|
||||||
|
},
|
||||||
|
Vers: list_vers{
|
||||||
|
``: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
`386`: {
|
||||||
|
Cgos: &list_cgos{
|
||||||
|
AR: home(`android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar`),
|
||||||
|
CC: home(`android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android24-clang`),
|
||||||
|
CXX: home(`android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android24-clang++`),
|
||||||
|
},
|
||||||
|
Vers: list_vers{
|
||||||
|
``: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
`arm`: {
|
||||||
|
Cgos: &list_cgos{
|
||||||
|
AR: home(`android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar`),
|
||||||
|
CC: home(`android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi24-clang`),
|
||||||
|
CXX: home(`android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi24-clang++`),
|
||||||
|
},
|
||||||
|
Vers: list_vers{
|
||||||
|
``: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
home(`go/bin/go1.20.14`): {
|
||||||
|
Args: []string{
|
||||||
|
`-trimpath`, `-buildvcs=false`,
|
||||||
|
`-ldflags`, `-s -w -extldflags '-v -static'`,
|
||||||
|
},
|
||||||
|
GoOS: list_goos{
|
||||||
|
`windows`: {
|
||||||
|
Arch: list_arch{
|
||||||
|
`amd64`: {
|
||||||
|
Cgos: &list_cgos{
|
||||||
|
AR: `x86_64-w64-mingw32-ar`,
|
||||||
|
CC: `x86_64-w64-mingw32-gcc`,
|
||||||
|
CXX: `x86_64-w64-mingw32-cpp`,
|
||||||
|
},
|
||||||
|
Vers: list_vers{
|
||||||
|
`v1`: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
`v2`: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
`v3`: {
|
||||||
|
Tags: `sonic avx`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
`linux`: {
|
||||||
|
Arch: list_arch{
|
||||||
|
`arm`: {
|
||||||
|
Cgos: &list_cgos{
|
||||||
|
AR: `arm-linux-gnueabihf-gcc-ar`,
|
||||||
|
CC: `arm-linux-gnueabihf-gcc`,
|
||||||
|
CXX: `arm-linux-gnueabihf-cpp`,
|
||||||
|
},
|
||||||
|
Vers: list_vers{
|
||||||
|
`5`: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
`6`: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
`7`: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
`arm64`: {
|
||||||
|
Cgos: &list_cgos{
|
||||||
|
AR: `aarch64-linux-gnu-gcc-ar`,
|
||||||
|
CC: `aarch64-linux-gnu-gcc`,
|
||||||
|
CXX: `aarch64-linux-gnu-cpp`,
|
||||||
|
},
|
||||||
|
Vers: list_vers{
|
||||||
|
``: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 针对部分OpenWrt路由器系统 暂不支持开启CGO
|
||||||
|
`mips`: {
|
||||||
|
Vers: list_vers{
|
||||||
|
`hardfloat`: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
`softfloat`: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
`mipsle`: {
|
||||||
|
Vers: list_vers{
|
||||||
|
`hardfloat`: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
`softfloat`: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Venv: `MIPS`,
|
||||||
|
},
|
||||||
|
`mips64`: {
|
||||||
|
Vers: list_vers{
|
||||||
|
`hardfloat`: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
`softfloat`: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
`mips64le`: {
|
||||||
|
Vers: list_vers{
|
||||||
|
`hardfloat`: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
`softfloat`: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Venv: `MIPS64`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Mac OS
|
||||||
|
`darwin`: {
|
||||||
|
Arch: list_arch{
|
||||||
|
`amd64`: {
|
||||||
|
Vers: list_vers{
|
||||||
|
`v2`: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
`v3`: {
|
||||||
|
Tags: `sonic avx`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
`arm64`: {
|
||||||
|
Vers: list_vers{
|
||||||
|
``: {
|
||||||
|
Tags: `go_json`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
fmt.Printf(`
|
||||||
|
================================
|
||||||
|
| Action 一键编译脚本
|
||||||
|
| 程序名称:%v
|
||||||
|
| 输出目录:%v
|
||||||
|
| 打包文件:%v
|
||||||
|
================================
|
||||||
|
`, args_name, args_path, args_zpak)
|
||||||
|
// 解析配置文件
|
||||||
|
for goVer, conf_list := range def_list {
|
||||||
|
// 环境检测
|
||||||
|
if err := chkenv(goVer); err != nil {
|
||||||
|
fmt.Println(err, `跳过该环境`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for goOS, goos_list := range conf_list.GoOS {
|
||||||
|
for goArch, arch_list := range goos_list.Arch {
|
||||||
|
// 工具链检测
|
||||||
|
if arch_list.Cgos != nil {
|
||||||
|
if err := chkenv(
|
||||||
|
arch_list.Cgos.AR,
|
||||||
|
arch_list.Cgos.CC,
|
||||||
|
arch_list.Cgos.CXX,
|
||||||
|
); err != nil {
|
||||||
|
fmt.Println(err, `跳过该架构`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for goIns, vers_list := range arch_list.Vers {
|
||||||
|
// 构建程序二进制
|
||||||
|
if err := build(¶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,6 +39,8 @@ const (
|
|||||||
args_zpak = true // 打包文件
|
args_zpak = true // 打包文件
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var workDir string
|
||||||
|
|
||||||
// 编译
|
// 编译
|
||||||
func doCompile(v_os, v_arch, v_archv, v_cc string) error {
|
func doCompile(v_os, v_arch, v_archv, v_cc string) error {
|
||||||
// 构建 | 目标系统 | 目标架构 | 优化等级 | 不包含调试信息 | 使用外部链接器 | 输出详细操作 | 静态编译 | JSON解释器
|
// 构建 | 目标系统 | 目标架构 | 优化等级 | 不包含调试信息 | 使用外部链接器 | 输出详细操作 | 静态编译 | JSON解释器
|
||||||
@ -52,7 +54,11 @@ func doCompile(v_os, v_arch, v_archv, v_cc string) error {
|
|||||||
return ztool.Str_FastConcat(name, v_archv, wexe)
|
return ztool.Str_FastConcat(name, v_archv, wexe)
|
||||||
}()
|
}()
|
||||||
pname := filepath.Clean(ztool.Str_FastConcat(args_path, fname))
|
pname := filepath.Clean(ztool.Str_FastConcat(args_path, fname))
|
||||||
cmd := ztool.Str_FastConcat(`go build -o `, pname, ` -ldflags "-s -w -linkmode external -extldflags ''" -tags "go_json"`) // go_json | json(std) | jsoniter | sonic
|
cmd := ztool.Str_FastConcat(
|
||||||
|
`go build -o `, pname,
|
||||||
|
` -gcflags=-trimpath="`, workDir, `" -asmflags=-trimpath="`, workDir, `" -trimpath -buildvcs=false`,
|
||||||
|
` -ldflags "-s -w -linkmode external" -tags "go_json"`, // go_json | json(std) | jsoniter | sonic
|
||||||
|
)
|
||||||
// 输出要执行的命令
|
// 输出要执行的命令
|
||||||
ztool.Cmd_FastPrintln(ztool.Str_FastConcat(`执行命令:`, cmd))
|
ztool.Cmd_FastPrintln(ztool.Str_FastConcat(`执行命令:`, cmd))
|
||||||
// 设置环境&执行编译
|
// 设置环境&执行编译
|
||||||
@ -84,7 +90,11 @@ func doCompile(v_os, v_arch, v_archv, v_cc string) error {
|
|||||||
if !ztool.Fbj_IsExists(`archieve`) {
|
if !ztool.Fbj_IsExists(`archieve`) {
|
||||||
os.MkdirAll(filepath.Join(args_path, `archieve`), 0755)
|
os.MkdirAll(filepath.Join(args_path, `archieve`), 0755)
|
||||||
}
|
}
|
||||||
if err := ztool.Pak_ZipFile(pname, ztool.Str_FastConcat(filepath.Join(args_path, `archieve`, fname), `.zip`), ztool.Pak_ZipConfig{UnPath: true}); err != nil {
|
if err := ztool.Pak_ZipFile(
|
||||||
|
pname,
|
||||||
|
filepath.Join(args_path, `archieve`, ztool.Str_LastBefore(fname, `.`))+`.zip`,
|
||||||
|
ztool.Pak_ZipConfig{UnPath: true},
|
||||||
|
); err != nil {
|
||||||
ztool.Cmd_FastPrintln(ztool.Str_FastConcat(`打包["`, pname, `"]出错:`, err.Error()))
|
ztool.Cmd_FastPrintln(ztool.Str_FastConcat(`打包["`, pname, `"]出错:`, err.Error()))
|
||||||
} else {
|
} else {
|
||||||
ztool.Cmd_FastPrintln(ztool.Str_FastConcat(`打包["`, pname, `"]完成`))
|
ztool.Cmd_FastPrintln(ztool.Str_FastConcat(`打包["`, pname, `"]完成`))
|
||||||
@ -98,6 +108,7 @@ func init() {
|
|||||||
ztool.Cmd_FastPrintln("简易脚本,未对Linux以外系统做适配,请复制执行以下命令编译:\ngo build -ldflags \"-s -w\" -tags \"go_json\"\n如无报错则会在本目录生成名为lx-source的可执行文件。")
|
ztool.Cmd_FastPrintln("简易脚本,未对Linux以外系统做适配,请复制执行以下命令编译:\ngo build -ldflags \"-s -w\" -tags \"go_json\"\n如无报错则会在本目录生成名为lx-source的可执行文件。")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
workDir, _ = os.Getwd()
|
||||||
ztool.Cmd_FastPrintln(ztool.Str_FastConcat(`
|
ztool.Cmd_FastPrintln(ztool.Str_FastConcat(`
|
||||||
================================
|
================================
|
||||||
| Golang 一键编译脚本
|
| Golang 一键编译脚本
|
||||||
|
48
go.mod
48
go.mod
@ -1,41 +1,51 @@
|
|||||||
module lx-source
|
module lx-source
|
||||||
|
|
||||||
go 1.21.4
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/ZxwyWebSite/cr-go-sdk v0.0.2
|
||||||
github.com/ZxwyWebSite/ztool v0.0.1
|
github.com/ZxwyWebSite/ztool v0.0.1
|
||||||
github.com/gin-contrib/gzip v0.0.6
|
github.com/gin-contrib/gzip v1.0.0
|
||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bytedance/sonic v1.10.2 // indirect
|
github.com/bytedance/sonic v1.11.5 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||||
github.com/chenzhuoyu/iasm v0.9.0 // indirect
|
github.com/cloudwego/base64x v0.1.3 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
|
github.com/gin-contrib/cors v1.7.1
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-ini/ini v1.67.0 // indirect
|
github.com/go-ini/ini v1.67.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
github.com/go-playground/validator/v10 v10.19.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||||
github.com/leodido/go-urn v1.2.4 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.1 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
golang.org/x/arch v0.3.0 // indirect
|
golang.org/x/arch v0.7.0 // indirect
|
||||||
golang.org/x/crypto v0.9.0 // indirect
|
golang.org/x/crypto v0.22.0 // indirect
|
||||||
golang.org/x/net v0.10.0 // indirect
|
golang.org/x/net v0.24.0 // indirect
|
||||||
golang.org/x/sys v0.14.0 // indirect
|
golang.org/x/sys v0.19.0 // indirect
|
||||||
golang.org/x/text v0.9.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
google.golang.org/protobuf v1.30.0 // indirect
|
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||||
|
google.golang.org/protobuf v1.33.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/ZxwyWebSite/ztool v0.0.1 => ./pkg/ztool // ../ztool
|
replace (
|
||||||
|
github.com/ZxwyWebSite/cr-go-sdk v0.0.2 => ../cr-go-sdk
|
||||||
|
github.com/ZxwyWebSite/ztool v0.0.1 => ../ztool // ./pkg/ztool
|
||||||
|
)
|
||||||
|
130
go.sum
130
go.sum
@ -1,67 +1,54 @@
|
|||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
github.com/bytedance/sonic v1.11.5 h1:G00FYjjqll5iQ1PYXynbg/hyzqBqavH8Mo9/oTopd9k=
|
||||||
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
github.com/bytedance/sonic v1.11.5/go.mod h1:X2PC2giUdj/Cv2lliWFLk6c/DUQok5rViJSemeB0wDw=
|
||||||
github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE=
|
github.com/bytedance/sonic/loader v0.1.0/go.mod h1:UmRT+IRTGKz/DAkzcEGzyVqQFJ7H9BqwBO3pm9H/+HY=
|
||||||
github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
github.com/cloudwego/base64x v0.1.3 h1:b5J/l8xolB7dyDTTmhJP2oTs5LdrjyrUFuNxdfq5hAg=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
github.com/cloudwego/base64x v0.1.3/go.mod h1:1+1K5BUHIQzyapgpF7LwvOGAEDicKtt1umPV+aN8pi8=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||||
github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
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/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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||||
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
github.com/gin-contrib/cors v1.7.1 h1:s9SIppU/rk8enVvkzwiC2VK3UZ/0NNGsWfUKvV55rqs=
|
||||||
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
github.com/gin-contrib/cors v1.7.1/go.mod h1:n/Zj7B4xyrgk/cX1WCX2dkzFfaNm/xJb6oIUk7WTtps=
|
||||||
|
github.com/gin-contrib/gzip v1.0.0 h1:UKN586Po/92IDX6ie5CWLgMI81obiIp5nSP85T3wlTk=
|
||||||
|
github.com/gin-contrib/gzip v1.0.0/go.mod h1:CtG7tQrPB3vIBo6Gat9FVUsis+1emjvQqd66ME5TdnE=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
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 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
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 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
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 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 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
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 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
|
||||||
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
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 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
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 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/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 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
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.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
github.com/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 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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
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 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
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.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 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
@ -70,71 +57,50 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
|||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/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 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
github.com/pelletier/go-toml/v2 v2.2.1 h1:9TA9+T8+8CUCO2+WYnDLCgrYi9+omqKXyjDtosvtEhg=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
github.com/pelletier/go-toml/v2 v2.2.1/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||||
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
|
||||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||||
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.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.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.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.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.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.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.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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
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.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
|
||||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||||
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.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.19.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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
|
||||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
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 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 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-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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
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=
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
|
217
init.go
Normal file
217
init.go
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"lx-source/src/caches"
|
||||||
|
"lx-source/src/caches/cloudcache"
|
||||||
|
"lx-source/src/caches/localcache"
|
||||||
|
"lx-source/src/env"
|
||||||
|
|
||||||
|
// "lx-source/src/sources"
|
||||||
|
// "lx-source/src/sources/builtin"
|
||||||
|
"net/http"
|
||||||
|
stdurl "net/url"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/ZxwyWebSite/cr-go-sdk"
|
||||||
|
"github.com/ZxwyWebSite/ztool"
|
||||||
|
"github.com/ZxwyWebSite/ztool/logs"
|
||||||
|
"github.com/ZxwyWebSite/ztool/zcypt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 生成连接码
|
||||||
|
func genAuth() {
|
||||||
|
ga := env.Loger.NewGroup(`LxM-Auth`)
|
||||||
|
// 检测Key是否存在, 否则生成并保存
|
||||||
|
if env.Config.Auth.ApiKey_Value == `` {
|
||||||
|
pass := zcypt.Base64ToString(base64.StdEncoding, zcypt.RandomBytes(4*4))
|
||||||
|
env.Config.Auth.ApiKey_Value = pass // env.Config.Apis.LxM_Auth
|
||||||
|
ga.Info(`已生成默认连接码: %q`, pass)
|
||||||
|
ga.Info(`可在配置文件 [Auth].ApiKey_Value 中修改`) //可在配置文件 [Apis].LxM_Auth 中修改, 填写 "null" 关闭验证
|
||||||
|
if err := env.Cfg.Save(``); err != nil {
|
||||||
|
ga.Error(`写入配置文件失败: %s, 将导致下次启动时连接码发生变化`, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !env.Config.Auth.ApiKey_Enable {
|
||||||
|
ga.Warn(`已关闭Key验证, 公开部署可能导致安全隐患`)
|
||||||
|
} else {
|
||||||
|
ga.Warn(`已开启Key验证, 记得在脚本中填写 apipass=%q`, env.Config.Auth.ApiKey_Value)
|
||||||
|
}
|
||||||
|
ga.Free()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载文件日志 (请在初始化配置文件后调用)
|
||||||
|
func loadFileLoger() {
|
||||||
|
// 最后加载FileLoger保证必要日志已输出 (Debug模式强制在控制台输出日志)
|
||||||
|
if env.Config.Main.LogPath != `` {
|
||||||
|
lg := env.Loger.NewGroup(`FileLoger`)
|
||||||
|
printout := env.Config.Main.Print // || env.Config.Main.Debug
|
||||||
|
f, do, err := env.Loger.SetOutFile(ztool.Str_FastConcat(env.RunPath, env.Config.Main.LogPath), printout)
|
||||||
|
if err == nil {
|
||||||
|
// env.Defer.Add(do)
|
||||||
|
env.Defer.Add(func() { do(); f.Close() })
|
||||||
|
env.Tasker.Add(`flog_flush`, func(loger *logs.Logger, now int64) error {
|
||||||
|
loger.Debug(`已写入文件并清理日志缓存`)
|
||||||
|
return do()
|
||||||
|
}, 3600, false)
|
||||||
|
gin.DefaultWriter = env.Loger.GetOutput()
|
||||||
|
gin.ForceConsoleColor()
|
||||||
|
// lg.Info(`文件日志初始化成功`)
|
||||||
|
} else {
|
||||||
|
lg.Error(`文件日志初始化失败:%v`, err)
|
||||||
|
}
|
||||||
|
lg.Free()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化基础功能
|
||||||
|
func initMain() {
|
||||||
|
// 载入内存缓存
|
||||||
|
storepath := env.RunPath + env.Config.Main.Store
|
||||||
|
env.Cache.MustRestore(storepath)
|
||||||
|
env.Defer.Add(func() { env.Cache.MustPersist(storepath) })
|
||||||
|
env.Tasker.Add(`memo_flush`, func(*logs.Logger, int64) error {
|
||||||
|
return env.Cache.Persist(storepath)
|
||||||
|
}, 3600, false)
|
||||||
|
|
||||||
|
// 初始化数据库
|
||||||
|
// idb := env.Loger.NewGroup(`InitDB`)
|
||||||
|
// switch `sqlite` {
|
||||||
|
// case `memo`:
|
||||||
|
// break
|
||||||
|
// case `sqlite`:
|
||||||
|
// err := database.InitDB(`data/data.db`)
|
||||||
|
// if err != nil {
|
||||||
|
// idb.Error(`数据库载入失败: %s`, err)
|
||||||
|
// }
|
||||||
|
// default:
|
||||||
|
// idb.Error(`未定义的数据库模式,请检查配置 [DataBase].Mode`)
|
||||||
|
// }
|
||||||
|
// idb.Free()
|
||||||
|
|
||||||
|
// 初始化代理
|
||||||
|
ipr := env.Loger.NewGroup(`InitProxy`)
|
||||||
|
switch env.Config.Source.FakeIP_Mode {
|
||||||
|
case `0`, `off`:
|
||||||
|
break
|
||||||
|
case `1`, `req`:
|
||||||
|
ipr.Fatal(`暂未实现此功能`)
|
||||||
|
case `2`, `val`:
|
||||||
|
if env.Config.Source.FakeIP_Value != `` {
|
||||||
|
ipr.Info(`已开启伪装IP,当前值: %v`, env.Config.Source.FakeIP_Value)
|
||||||
|
ztool.Net_header[`X-Real-IP`] = env.Config.Source.FakeIP_Value
|
||||||
|
ztool.Net_header[`X-Forwarded-For`] = env.Config.Source.FakeIP_Value
|
||||||
|
} else {
|
||||||
|
ipr.Error(`伪装IP为空,请检查配置 [Source].FakeIP_Value`)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
ipr.Error(`未定义的代理模式,请检查配置 [Source].FakeIP_Mode,本次启动禁用IP伪装`)
|
||||||
|
}
|
||||||
|
if env.Config.Source.Proxy_Enable {
|
||||||
|
ipr.Debug(`ProxyAddr: %v`, env.Config.Source.Proxy_Address)
|
||||||
|
addr, err := stdurl.Parse(env.Config.Source.Proxy_Address)
|
||||||
|
if err != nil {
|
||||||
|
ipr.Error(`代理Url解析失败: %s, 将禁用代理功能`, err)
|
||||||
|
} else {
|
||||||
|
type chkRegion struct {
|
||||||
|
AmapFlag int `json:"amap_flag"`
|
||||||
|
IPFlag int `json:"ip_flag"`
|
||||||
|
AmapAddress string `json:"amap_address"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
Flag int `json:"flag"`
|
||||||
|
Errcode int `json:"errcode"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
var out chkRegion
|
||||||
|
oldval := ztool.Net_client.Transport
|
||||||
|
ztool.Net_client.Transport = &http.Transport{Proxy: http.ProxyURL(addr)}
|
||||||
|
err := ztool.Net_Request(http.MethodGet,
|
||||||
|
`https://mips.kugou.com/check/iscn?&format=json`, nil,
|
||||||
|
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeader(ztool.Net_header)},
|
||||||
|
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&out)},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
ztool.Net_client.Transport = oldval
|
||||||
|
ipr.Error(`地区验证失败: %s, 已恢复默认配置`, err)
|
||||||
|
} else {
|
||||||
|
ipr.Debug(`Resp: %+v`, out)
|
||||||
|
if out.Flag != 1 {
|
||||||
|
ipr.Warn(`您正在使用非中国大陆(%v)代理,可能导致部分音乐不可用`, out.Country)
|
||||||
|
} else {
|
||||||
|
ipr.Warn(`代理开启成功,请注意潜在的Cookie泄露问题`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ipr.Free()
|
||||||
|
|
||||||
|
// 初始化缓存
|
||||||
|
icl := env.Loger.NewGroup(`InitCache`)
|
||||||
|
switch env.Config.Cache.Mode {
|
||||||
|
case `0`, `off`:
|
||||||
|
// NothingToDo... (已默认禁用缓存)
|
||||||
|
break
|
||||||
|
case `1`, `local`:
|
||||||
|
// 注:由于需要修改LocalCachePath参数,必须在InitRouter之前执行
|
||||||
|
cache, err := caches.New(&localcache.Cache{
|
||||||
|
Path: filepath.Join(env.RunPath, env.Config.Cache.Local_Path),
|
||||||
|
Bind: env.Config.Cache.Local_Bind,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
icl.Error(`驱动["local"]初始化失败: %v, 将禁用缓存功能`, err)
|
||||||
|
}
|
||||||
|
caches.UseCache = cache
|
||||||
|
icl.Warn(`本地缓存绑定地址:%q,请确认其与实际访问地址相符`, env.Config.Cache.Local_Bind)
|
||||||
|
// LocalCachePath = filepath.Join(runPath, env.Config.Cache.Local_Path)
|
||||||
|
// UseCache = &localcache.Cache{
|
||||||
|
// Path: LocalCachePath,
|
||||||
|
// Addr: env.Config.Apis.BindAddr,
|
||||||
|
// }
|
||||||
|
// icl.Info(`使用本地缓存,文件路径 %q,绑定地址 %v`, LocalCachePath, env.Config.Apis.BindAddr)
|
||||||
|
case `2`, `cloudreve`:
|
||||||
|
icl.Warn(`欢迎使用新版 Cloudreve 驱动, 由 cr-go-sdk 提供强力支持`)
|
||||||
|
site := &cr.SiteObj{
|
||||||
|
Addr: env.Config.Cache.Cloud_Site,
|
||||||
|
ApiVer: cr.ApiV383,
|
||||||
|
Users: &cr.UserObj{
|
||||||
|
Mail: env.Config.Cache.Cloud_User,
|
||||||
|
Pass: env.Config.Cache.Cloud_Pass,
|
||||||
|
Cookie: cr.ParseCookie(env.Config.Cache.Cloud_Sess),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cache, err := caches.New(&cloudcache.Cache{
|
||||||
|
Site: site,
|
||||||
|
Path: env.Config.Cache.Cloud_Path,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
icl.Error(`驱动["cloudreve"]初始化失败: %v, 将禁用缓存功能`, err)
|
||||||
|
} else {
|
||||||
|
env.Tasker.Add(`cloud_sess`, func(l *logs.Logger, i int64) error {
|
||||||
|
if sess := site.Users.Cookie.String(); sess != env.Config.Cache.Cloud_Sess {
|
||||||
|
env.Config.Cache.Cloud_Sess = sess
|
||||||
|
}
|
||||||
|
return env.Cfg.Save(``)
|
||||||
|
}, 3600, true)
|
||||||
|
}
|
||||||
|
caches.UseCache = cache
|
||||||
|
default:
|
||||||
|
icl.Error(`未定义的缓存模式,请检查配置 [Cache].Mode,本次启动禁用缓存`)
|
||||||
|
}
|
||||||
|
icl.Free()
|
||||||
|
|
||||||
|
// 初始化音乐源
|
||||||
|
// ise := env.Loger.NewGroup(`InitSource`)
|
||||||
|
// switch env.Config.Source.Mode {
|
||||||
|
// case `0`, `off`:
|
||||||
|
// break
|
||||||
|
// case `1`, `builtin`:
|
||||||
|
// sources.UseSource = &builtin.Source{}
|
||||||
|
// case `2`, `custom`:
|
||||||
|
// ise.Fatal(`暂未实现账号解析源`)
|
||||||
|
// default:
|
||||||
|
// ise.Error(`未定义的音乐源,请检查配置 [Source].Mode,本次启动禁用内置源`)
|
||||||
|
// }
|
||||||
|
// ise.Free()
|
||||||
|
}
|
224
main.go
224
main.go
@ -2,19 +2,16 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
|
||||||
"flag"
|
"flag"
|
||||||
"lx-source/src/caches"
|
"io/fs"
|
||||||
"lx-source/src/caches/localcache"
|
|
||||||
"lx-source/src/env"
|
"lx-source/src/env"
|
||||||
"lx-source/src/router"
|
"lx-source/src/server"
|
||||||
"lx-source/src/sources"
|
|
||||||
"lx-source/src/sources/builtin"
|
|
||||||
"math/rand"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -23,50 +20,6 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"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() {
|
func init() {
|
||||||
ztool.Cmd_FastPrint(ztool.Str_FastConcat(`
|
ztool.Cmd_FastPrint(ztool.Str_FastConcat(`
|
||||||
@ -82,18 +35,26 @@ func init() {
|
|||||||
env.RunPath, _ = os.Getwd()
|
env.RunPath, _ = os.Getwd()
|
||||||
var confPath string
|
var confPath string
|
||||||
flag.StringVar(&confPath, `c`, ztool.Str_FastConcat(env.RunPath, `/data/conf.ini`), `指定配置文件路径`)
|
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()
|
flag.Parse()
|
||||||
// fileLoger() // 注:记录日志必然会影响性能,自行选择是否开启
|
if perm != nil && *perm != 0 {
|
||||||
// logs.DefLogger(`LX-SOURCE`, logs.LevelDebu)
|
ztool.Fbj_DefPerm = fs.FileMode(*perm)
|
||||||
// logs.Main = `LX-SOURCE`
|
fp := env.Loger.NewGroup(`FilePerm`)
|
||||||
env.Cfg.MustInit(confPath) //conf.InitConfig(confPath)
|
// if ztool.Fbj_DefPerm > 777 {
|
||||||
|
// fp.Fatal(`请在实际权限前面加0`)
|
||||||
|
// }
|
||||||
|
fp.Info(`设置默认文件权限为 %o (%v)`, *perm, ztool.Fbj_DefPerm).Free()
|
||||||
|
}
|
||||||
|
env.Cfg.MustInit(confPath)
|
||||||
|
parseEtag(etag)
|
||||||
// fmt.Printf("%+v\n", env.Config)
|
// fmt.Printf("%+v\n", env.Config)
|
||||||
env.Loger.NewGroup(`ServHello`).Info(`欢迎使用 LX-SOURCE 洛雪音乐自定义源`)
|
env.Loger.NewGroup(`ServHello`).Info(`欢迎使用 LX-SOURCE 洛雪音乐自定义源`).Free()
|
||||||
if !env.Config.Main.Debug {
|
if !env.Config.Main.Debug {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
} else {
|
} else {
|
||||||
logs.Levell = logs.LevelDebu // logs.Level = 3
|
logs.Levell = logs.LevelDebu // logs.Level = 3
|
||||||
env.Loger.NewGroup(`DebugMode`).Debug(`已开启调试模式, 将输出更详细日志 (配置文件中 [Main].Debug 改为 false 关闭)`)
|
env.Loger.NewGroup(`DebugMode`).Debug(`已开启调试模式, 将输出更详细日志 (配置文件中 [Main].Debug 改为 false 关闭)`).Free()
|
||||||
}
|
}
|
||||||
genAuth()
|
genAuth()
|
||||||
if env.Config.Main.SysLev {
|
if env.Config.Main.SysLev {
|
||||||
@ -103,102 +64,93 @@ func init() {
|
|||||||
} else {
|
} else {
|
||||||
sl.Warn(`成功设置较高优先级,此功能可能导致系统不稳定`)
|
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() {
|
func main() {
|
||||||
defer env.Defer.Do()
|
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.Inits.Do()
|
||||||
env.Loger.NewGroup(`ServStart`).Info(`服务端启动, 监听地址 %s`, env.Config.Main.Listen)
|
env.Loger.NewGroup(`ServInit`).Info(`服务端启动, 监听地址 %s`, strings.Join(env.Config.Main.Listen, `|`)).Free()
|
||||||
loadFileLoger()
|
loadFileLoger()
|
||||||
env.Defer.Add(env.Tasker.Run(env.Loger)) // wait
|
env.Defer.Add(env.Tasker.Run(env.Loger)) // wait
|
||||||
|
|
||||||
// 启动Http服务
|
// 启动Http服务
|
||||||
r := router.InitRouter() //InitRouter()
|
listenAndServe(server.InitRouter(), env.Config.Main.Listen)
|
||||||
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)
|
quit := make(chan os.Signal, 1)
|
||||||
signal.Notify(quit, os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
|
signal.Notify(quit, os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
|
||||||
<-quit
|
<-quit
|
||||||
sc := env.Loger.NewGroup(`ServClose`)
|
sc := env.Loger.NewGroup(`ServClose`)
|
||||||
sc.Info(`等待结束活动连接...`) // Shutdown Server ...
|
sc.Info(`等待结束活动连接...`)
|
||||||
|
// 停止服务
|
||||||
|
var unsafenum int32
|
||||||
|
wg := new(sync.WaitGroup)
|
||||||
|
for i := range srvlist {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(n int) {
|
||||||
|
server := srvlist[n]
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := server.Shutdown(ctx); err != nil {
|
if err := server.Shutdown(ctx); err != nil {
|
||||||
sc.Fatal(`未安全退出: %s`, err) // Server Shutdown
|
sc.Warn(`连接%q未安全退出: %s`, server.Addr, err) // 连接":1011"未安全退出: timeout
|
||||||
|
atomic.AddInt32(&unsafenum, 1)
|
||||||
|
}
|
||||||
|
cancel()
|
||||||
|
wg.Done()
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
if unsafenum != 0 {
|
||||||
|
sc.Warn(`未安全退出 :(`)
|
||||||
|
} else {
|
||||||
|
sc.Info(`已安全退出 :)`)
|
||||||
}
|
}
|
||||||
sc.Info(`已安全退出 :)`) // Server exited
|
|
||||||
|
|
||||||
// if err := InitRouter().Run(env.Config.Main.Listen); err != nil {
|
|
||||||
// env.Loger.NewGroup(`InitRouter().Run`).Fatal(`%s`, err)
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
132
menu.go
Normal file
132
menu.go
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"lx-source/src/env"
|
||||||
|
"lx-source/src/sources/custom/tx"
|
||||||
|
wm "lx-source/src/sources/custom/wy/modules"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
qrcode "github.com/skip2/go-qrcode"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseEtag(etag *string) {
|
||||||
|
if etag == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loger := env.Loger.NewGroup(`ParseEtag`)
|
||||||
|
switch *etag {
|
||||||
|
case ``:
|
||||||
|
break
|
||||||
|
case `menu`:
|
||||||
|
loger.Fatal(`暂不支持交互菜单,敬请期待...`)
|
||||||
|
// menuMian()
|
||||||
|
case `wyqr`:
|
||||||
|
wyQrLogin()
|
||||||
|
case `txqq`:
|
||||||
|
txQqLogin()
|
||||||
|
default:
|
||||||
|
loger.Fatal(`未知参数:%q`, *etag)
|
||||||
|
}
|
||||||
|
loger.Free()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 网易云扫码登录
|
||||||
|
func wyQrLogin() {
|
||||||
|
loger := env.Loger.NewGroup(`WyQrLogin`)
|
||||||
|
defer loger.Free()
|
||||||
|
loger.Info(`执行模块: 网易云扫码登录`)
|
||||||
|
|
||||||
|
if env.Config.Custom.Wy_Api_Cookie != `` {
|
||||||
|
loger.Warn("已存在账号数据, 继续操作可能导致数据覆盖丢失!")
|
||||||
|
fmt.Print(`输入'y'继续: `)
|
||||||
|
var input string
|
||||||
|
fmt.Scanln(&input)
|
||||||
|
if input != `y` {
|
||||||
|
loger.Fatal(`用户取消操作`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := wm.LoginQrKey()
|
||||||
|
if err != nil {
|
||||||
|
loger.Fatal(`无法创建请求: %s`, err)
|
||||||
|
}
|
||||||
|
key := res.Body[`unikey`].(string)
|
||||||
|
loger.Info(`创建请求成功: %v`, key)
|
||||||
|
|
||||||
|
link := wm.LoginQrCreate(key)
|
||||||
|
qr, err := qrcode.New(link, qrcode.Low)
|
||||||
|
if err != nil {
|
||||||
|
loger.Fatal(`无法生成二维码: %s`, err)
|
||||||
|
}
|
||||||
|
loger.Info("\n请使用网易云音乐手机APP扫描以下二维码授权登录:\n%v", qr.ToSmallString(false))
|
||||||
|
|
||||||
|
for {
|
||||||
|
time.Sleep(time.Second * 5)
|
||||||
|
res, err = wm.LoginQrCheck(key)
|
||||||
|
if err != nil {
|
||||||
|
loger.Error(`检测状态失败: %s`, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
msg := res.Body[`message`].(string)
|
||||||
|
switch msg {
|
||||||
|
case `等待扫码`:
|
||||||
|
loger.Info(msg)
|
||||||
|
case `授权中`:
|
||||||
|
loger.Info(`扫码成功: %q, 请在手机上确认登录`, res.Body[`nickname`])
|
||||||
|
case `授权登陆成功`:
|
||||||
|
loger.Info(`授权成功`)
|
||||||
|
env.Config.Custom.Wy_Enable = true
|
||||||
|
env.Config.Custom.Wy_Mode = `163api`
|
||||||
|
env.Config.Custom.Wy_Api_Cookie = strings.Join(res.Cookie, `; `)
|
||||||
|
env.Config.Custom.Wy_Refresh_Enable = true
|
||||||
|
if err := env.Cfg.Save(``); err != nil {
|
||||||
|
loger.Error(`配置保存失败: %s`, err)
|
||||||
|
} else {
|
||||||
|
loger.Info(`配置保存成功`)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case `二维码不存在或已过期`:
|
||||||
|
loger.Fatal(`授权请求超时,请重试!`)
|
||||||
|
default:
|
||||||
|
loger.Fatal(`未知状态: %v`, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// QQ快速登录
|
||||||
|
func txQqLogin() {
|
||||||
|
loger := env.Loger.NewGroup(`TxQqLogin`)
|
||||||
|
defer loger.Free()
|
||||||
|
loger.Info(`执行模块: QQ快速登录`)
|
||||||
|
|
||||||
|
if runtime.GOOS != `windows` {
|
||||||
|
loger.Fatal(`该模块仅支持在windows环境下使用`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if env.Config.Custom.Tx_Ukey != `` {
|
||||||
|
loger.Warn("已存在账号数据, 继续操作可能导致数据覆盖丢失!")
|
||||||
|
fmt.Print(`输入'y'继续: `)
|
||||||
|
var input string
|
||||||
|
fmt.Scanln(&input)
|
||||||
|
if input != `y` {
|
||||||
|
loger.Fatal(`用户取消操作`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Qlogin_graph(loger); err != nil {
|
||||||
|
loger.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// func menuMian() {
|
||||||
|
// app := menu.NewApp(`Lx-Source`)
|
||||||
|
// app.Data = menu.Data{
|
||||||
|
// `Main`: func(this *menu.App) string { return ` ` },
|
||||||
|
// }
|
||||||
|
// app.Run()
|
||||||
|
// os.Exit(0)
|
||||||
|
// }
|
21
readme.md
21
readme.md
@ -11,11 +11,15 @@
|
|||||||
## ZxwyWebSite/LX-Source
|
## ZxwyWebSite/LX-Source
|
||||||
### 简介
|
### 简介
|
||||||
+ LX-Music 解析源 (洛雪音乐自定义源)
|
+ 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)
|
+ 视频教程:[使用教程.mp4](https://r2eu.zxwy.link/gh/lx-source/v1.0.2-b0.1/%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B.mp4)
|
||||||
<!-- + **锟斤拷** -->
|
<!-- + *手持两把锟斤拷,口中疾呼烫烫烫。 脚踏千朵屯屯屯,笑看万物锘锘锘。* -->
|
||||||
|
|
||||||
|
### 注意
|
||||||
|
+ 不保证内置源可用性,如需长期使用请自备会员账号
|
||||||
|
+ 音源最好留着自己用,你非要当大好人当我没说(由于传播导致的账号被封与本项目无关)
|
||||||
|
|
||||||
### 使用
|
### 使用
|
||||||
#### 服务端
|
#### 服务端
|
||||||
@ -48,8 +52,8 @@
|
|||||||
<!-- + ... -->
|
<!-- + ... -->
|
||||||
|
|
||||||
### 音乐源
|
### 音乐源
|
||||||
+ 内置源 (抓取自网络公开接口) **注:文明上网,请勿滥用**
|
+ 内置源 (抓取自网络公开接口) **注:文明上网,请勿滥用,否则停止后续更新**
|
||||||
+ 账号源 (登录Vip账号解析) **注:可能导致封号,如出问题本项目不负责**
|
+ 账号源 (登录会员账号解析) **注:可能导致封号,如出问题本项目不负责**
|
||||||
|
|
||||||
### 开发
|
### 开发
|
||||||
+ 环境要求:Golang 1.21 (建议 >=1.20)
|
+ 环境要求:Golang 1.21 (建议 >=1.20)
|
||||||
@ -61,18 +65,21 @@
|
|||||||
- pkg/ 依赖包,一般在外部调用,不轻易修改
|
- pkg/ 依赖包,一般在外部调用,不轻易修改
|
||||||
- src/ 源码包,用于实现各种功能
|
- src/ 源码包,用于实现各种功能
|
||||||
* env 公用变量,需要全局调用的参数
|
* env 公用变量,需要全局调用的参数
|
||||||
|
* database 数据库相关
|
||||||
* caches 文件缓存封装
|
* caches 文件缓存封装
|
||||||
* router Gin路由
|
* server Gin路由
|
||||||
* middleware 请求中间件
|
* middleware 请求中间件
|
||||||
* sources 音乐源
|
* sources 音乐源
|
||||||
<!-- - public/ 静态资源,打包进程序,新环境运行自动释放 -->
|
<!-- - public/ 静态资源,打包进程序,新环境运行自动释放 -->
|
||||||
<!-- - scripts/ 一些快捷脚本 -->
|
<!-- - scripts/ 一些快捷脚本 -->
|
||||||
- build.go 快速构建脚本 (请先根据本地环境编辑配置)
|
- build.go 快速构建脚本 (请先根据本地环境编辑配置)
|
||||||
|
- init.go 初始化检测
|
||||||
|
- menu.go 交互菜单 (暂未实现)
|
||||||
- main.go 主程序
|
- main.go 主程序
|
||||||
|
|
||||||
### 其它
|
### 其它
|
||||||
+ 基于 Golang + Gin框架 编写
|
+ 基于 Golang + Gin框架 编写
|
||||||
+ 部分功能参考 [Python版](https://github.com/lxmusics/lx-music-api-server-python) 实现
|
+ 感谢以下项目提供参考:[Python版](https://github.com/lxmusics/lx-music-api-server-python),[WyApi](https://github.com/ZxwyWebSite/NeteaseCloudMusicApi),...
|
||||||
|
|
||||||
### 更新
|
### 更新
|
||||||
+ 见 `update.md`
|
+ 见 `update.md`
|
||||||
@ -92,5 +99,5 @@
|
|||||||
|
|
||||||
音乐平台不易,请尊重版权,支持正版。
|
音乐平台不易,请尊重版权,支持正版。
|
||||||
本项目仅用于对技术可行性的探索及研究,不接受任何商业(包括但不限于广告等)合作及捐赠。
|
本项目仅用于对技术可行性的探索及研究,不接受任何商业(包括但不限于广告等)合作及捐赠。
|
||||||
若对此有疑问请 mail to: admin+zxwy.tk (请将`+`替换为`@`)
|
若对此有疑问请 mail to: admin+zxwy.link (请将`+`替换为`@`)
|
||||||
|
|
107
release.go
Normal file
107
release.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
//go:build ignore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 获取版本号
|
||||||
|
func version() string {
|
||||||
|
fenv, _ := os.Open(`src/env/env.go`)
|
||||||
|
benv := bufio.NewReader(fenv)
|
||||||
|
var ever string
|
||||||
|
for {
|
||||||
|
line, _, _ := benv.ReadLine()
|
||||||
|
length := len(line)
|
||||||
|
if length == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sline := string(line)
|
||||||
|
if strings.HasPrefix(sline, ` Version`) {
|
||||||
|
ever = `v` + sline[12:length-1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fenv.Close()
|
||||||
|
if ever == `` {
|
||||||
|
panic(`No Version`)
|
||||||
|
} else {
|
||||||
|
return ever
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成更新日志
|
||||||
|
func changelog(ever string) string {
|
||||||
|
fupd, _ := os.Open(`update.md`)
|
||||||
|
bupd := bufio.NewReader(fupd)
|
||||||
|
var eupd strings.Builder
|
||||||
|
eupd.WriteString(`### 更新内容:`)
|
||||||
|
eupd.WriteByte('\n')
|
||||||
|
for {
|
||||||
|
line, _, _ := bupd.ReadLine()
|
||||||
|
length := len(line)
|
||||||
|
if length == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.Contains(string(line), ever) {
|
||||||
|
for {
|
||||||
|
lline, _, _ := bupd.ReadLine()
|
||||||
|
length := len(lline)
|
||||||
|
if length == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
eupd.WriteString(string(lline))
|
||||||
|
eupd.WriteByte('\n')
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fupd.Close()
|
||||||
|
eupd.WriteByte('\n')
|
||||||
|
eupd.WriteString(`### CDN加速下载:`)
|
||||||
|
eupd.WriteByte('\n')
|
||||||
|
for _, v := range []string{
|
||||||
|
`lx-source-android-arm.zip`,
|
||||||
|
`lx-source-android-arm64.zip`,
|
||||||
|
`lx-source-linux-amd64v2.zip`,
|
||||||
|
`lx-source-linux-amd64v3.zip`,
|
||||||
|
`lx-source-linux-arm7.zip`,
|
||||||
|
`lx-source-linux-arm64.zip`,
|
||||||
|
`lx-source-windows-amd64v2.zip`,
|
||||||
|
`lx-source-windows-amd64v2-go1.20.14.zip`,
|
||||||
|
`lx-source-windows-amd64v3.zip`,
|
||||||
|
} {
|
||||||
|
eupd.WriteByte('+')
|
||||||
|
eupd.WriteByte(' ')
|
||||||
|
|
||||||
|
eupd.WriteByte('[')
|
||||||
|
eupd.WriteString(v)
|
||||||
|
eupd.WriteByte(']')
|
||||||
|
eupd.WriteByte('(')
|
||||||
|
eupd.WriteString(`https://r2eu.zxwy.link/gh/lx-source/`)
|
||||||
|
eupd.WriteString(ever)
|
||||||
|
eupd.WriteByte('/')
|
||||||
|
eupd.WriteString(v)
|
||||||
|
eupd.WriteByte(')')
|
||||||
|
|
||||||
|
eupd.WriteByte('\n')
|
||||||
|
}
|
||||||
|
return eupd.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ever := version()
|
||||||
|
fmt.Println(ever)
|
||||||
|
|
||||||
|
eupd := changelog(ever)
|
||||||
|
file, err := os.Create(`changelog.md`)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
file.WriteString(eupd)
|
||||||
|
file.Close()
|
||||||
|
}
|
BIN
rsrc_windows_amd64.syso
Normal file
BIN
rsrc_windows_amd64.syso
Normal file
Binary file not shown.
@ -1,10 +1,13 @@
|
|||||||
package caches
|
package caches
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"lx-source/src/env"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ZxwyWebSite/ztool"
|
"github.com/ZxwyWebSite/ztool"
|
||||||
|
"github.com/ZxwyWebSite/ztool/logs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@ -15,6 +18,7 @@ type (
|
|||||||
Quality string // quality 音质 128k / 320k / flac / flac24bit
|
Quality string // quality 音质 128k / 320k / flac / flac24bit
|
||||||
Extname string // rext 扩展名 mp3 / flac (没有前缀点)
|
Extname string // rext 扩展名 mp3 / flac (没有前缀点)
|
||||||
query string // 查询字符串缓存
|
query string // 查询字符串缓存
|
||||||
|
Request *http.Request
|
||||||
}
|
}
|
||||||
// 缓存需实现以下接口
|
// 缓存需实现以下接口
|
||||||
Cache interface {
|
Cache interface {
|
||||||
@ -40,7 +44,6 @@ func (*Nullcache) Stat() bool { return false }
|
|||||||
func (*Nullcache) Init() error { return nil }
|
func (*Nullcache) Init() error { return nil }
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Loger = env.Loger.NewGroup(`Caches`)
|
|
||||||
UseCache Cache = &Nullcache{}
|
UseCache Cache = &Nullcache{}
|
||||||
// ErrNotInited = errors.New(`缓存策略未初始化`)
|
// ErrNotInited = errors.New(`缓存策略未初始化`)
|
||||||
query_pool = sync.Pool{New: func() any { return new(Query) }}
|
query_pool = sync.Pool{New: func() any { return new(Query) }}
|
||||||
@ -52,7 +55,7 @@ func (c *Query) Free() { c.query = ``; query_pool.Put(c) }
|
|||||||
|
|
||||||
// 根据音质判断文件后缀
|
// 根据音质判断文件后缀
|
||||||
func rext(q string) string {
|
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 `mp3`
|
||||||
}
|
}
|
||||||
return `flac`
|
return `flac`
|
||||||
@ -82,7 +85,7 @@ func (c *Query) Query() string {
|
|||||||
return c.query
|
return c.query
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分割查询字符串
|
// 分割查询字符串 (已弃用)
|
||||||
/*
|
/*
|
||||||
kg: 分割 Hash-Album 如 "6DC276334F56E22BE2A0E8254D332B45-13097991"
|
kg: 分割 Hash-Album 如 "6DC276334F56E22BE2A0E8254D332B45-13097991"
|
||||||
tx: 分割 songmid-strMediaMid 如 "002fktJg3cmSpC-000V6uuv35Cwnh"
|
tx: 分割 songmid-strMediaMid 如 "002fktJg3cmSpC-000V6uuv35Cwnh"
|
||||||
@ -111,3 +114,12 @@ func MustNew(c Cache) Cache {
|
|||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var Loger *logs.Logger
|
||||||
|
|
||||||
|
// 初始化Loger
|
||||||
|
func init() {
|
||||||
|
env.Inits.Add(func() {
|
||||||
|
Loger = env.Loger.NewGroup(`Caches`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -1 +1,121 @@
|
|||||||
package cloudcache
|
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,9 +4,11 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"lx-source/src/caches"
|
"lx-source/src/caches"
|
||||||
"lx-source/src/env"
|
"lx-source/src/env"
|
||||||
|
"lx-source/src/middleware/util"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ZxwyWebSite/ztool"
|
"github.com/ZxwyWebSite/ztool"
|
||||||
)
|
)
|
||||||
@ -17,10 +19,17 @@ type Cache struct {
|
|||||||
state bool // 激活状态
|
state bool // 激活状态
|
||||||
}
|
}
|
||||||
|
|
||||||
var loger = env.Loger.NewGroup(`Caches`) //caches.Loger.AppGroup(`local`)
|
// var loger = env.Loger.NewGroup(`Caches`) //caches.Loger.AppGroup(`local`)
|
||||||
|
|
||||||
func (c *Cache) getLink(q string) string {
|
func (c *Cache) getLink(q *caches.Query) (furl string) {
|
||||||
return ztool.Str_FastConcat(c.Bind, `/file/`, q) // c.Addr + `file/` + q
|
// fmt.Printf("%#v\n", q.Request)
|
||||||
|
if env.Config.Cache.Local_Auto {
|
||||||
|
// 注:此方式无法确定是否支持HTTPS,暂时默认HTTP,让Nginx重定向
|
||||||
|
furl = ztool.Str_FastConcat(util.GetPath(q.Request, `link/`), `file/`, q.Query())
|
||||||
|
} else {
|
||||||
|
furl = ztool.Str_FastConcat(c.Bind, `/file/`, q.Query())
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) Get(q *caches.Query) string {
|
func (c *Cache) Get(q *caches.Query) string {
|
||||||
@ -31,7 +40,7 @@ func (c *Cache) Get(q *caches.Query) string {
|
|||||||
}
|
}
|
||||||
// env.Cache.Set(q.Query(), struct{}{}, 3600)
|
// env.Cache.Set(q.Query(), struct{}{}, 3600)
|
||||||
// }
|
// }
|
||||||
return c.getLink(q.Query())
|
return c.getLink(q)
|
||||||
// fpath := filepath.Join(c.Path, q.Source, q.MusicID, q.Quality)
|
// fpath := filepath.Join(c.Path, q.Source, q.MusicID, q.Quality)
|
||||||
// if _, e := os.Stat(fpath); e != nil {
|
// if _, e := os.Stat(fpath); e != nil {
|
||||||
// return ``
|
// return ``
|
||||||
@ -54,14 +63,23 @@ func (c *Cache) Set(q *caches.Query, l string) string {
|
|||||||
// }
|
// }
|
||||||
// loger.Debug(`FFMpeg_Out: %v`, out)
|
// loger.Debug(`FFMpeg_Out: %v`, out)
|
||||||
// } else {
|
// } else {
|
||||||
|
for i := 0; true; i++ {
|
||||||
err := ztool.Net_DownloadFile(l, fpath, nil)
|
err := ztool.Net_DownloadFile(l, fpath, nil)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
loger.Error(`DownloadFile: %v`, err)
|
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 ``
|
return ``
|
||||||
}
|
}
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
// }
|
// }
|
||||||
// env.Cache.Set(q.Query(), struct{}{}, 3600)
|
// env.Cache.Set(q.Query(), struct{}{}, 3600)
|
||||||
return c.getLink(q.Query())
|
return c.getLink(q)
|
||||||
// fpath := filepath.Join(c.Path, q.String)
|
// fpath := filepath.Join(c.Path, q.String)
|
||||||
// os.MkdirAll(filepath.Dir(fpath), fs.ModePerm)
|
// os.MkdirAll(filepath.Dir(fpath), fs.ModePerm)
|
||||||
// g := c.Loger.NewGroup(`localcache`)
|
// g := c.Loger.NewGroup(`localcache`)
|
||||||
|
38
src/database/driver.go
Normal file
38
src/database/driver.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
//go:build gorm
|
||||||
|
|
||||||
|
package database
|
||||||
|
|
||||||
|
// import (
|
||||||
|
// "lx-source/src/database/modules"
|
||||||
|
// "lx-source/src/env"
|
||||||
|
// "lx-source/src/sources"
|
||||||
|
|
||||||
|
// "gorm.io/gorm"
|
||||||
|
// )
|
||||||
|
|
||||||
|
// var DB *gorm.DB
|
||||||
|
|
||||||
|
// func InitDB(dsn string) (err error) {
|
||||||
|
// loger := env.Loger.NewGroup(`InitDB`)
|
||||||
|
// defer loger.Free()
|
||||||
|
// DB, err = gorm.Open(modules.Sqlite(dsn), &gorm.Config{})
|
||||||
|
// if err == nil {
|
||||||
|
// for _, typ := range []struct {
|
||||||
|
// Name string
|
||||||
|
// Type interface{}
|
||||||
|
// }{
|
||||||
|
// {Name: T_music, Type: &XMusicItem{}},
|
||||||
|
// {Name: T_lyric, Type: &XLyricItem{}},
|
||||||
|
// } {
|
||||||
|
// for _, src := range sources.S_al {
|
||||||
|
// err = DB.Table(src + `_` + typ.Name).AutoMigrate(typ.Type)
|
||||||
|
// if err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// type Driver struct{}
|
9
src/database/modules/sqlite_cgo.go
Normal file
9
src/database/modules/sqlite_cgo.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
//go:build cgo && gorm
|
||||||
|
|
||||||
|
package modules
|
||||||
|
|
||||||
|
// import (
|
||||||
|
// "gorm.io/driver/sqlite"
|
||||||
|
// )
|
||||||
|
|
||||||
|
// var Sqlite = sqlite.Open
|
9
src/database/modules/sqlite_etc.go
Normal file
9
src/database/modules/sqlite_etc.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
//go:build !cgo && gorm
|
||||||
|
|
||||||
|
package modules
|
||||||
|
|
||||||
|
// import (
|
||||||
|
// "github.com/glebarez/sqlite"
|
||||||
|
// )
|
||||||
|
|
||||||
|
// var Sqlite = sqlite.Open
|
118
src/database/types.go
Normal file
118
src/database/types.go
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
//go:build gorm
|
||||||
|
|
||||||
|
package database
|
||||||
|
|
||||||
|
// MusicFree 数据结构
|
||||||
|
// type (
|
||||||
|
// // 其他
|
||||||
|
// IExtra map[string]interface{}
|
||||||
|
// // 音乐
|
||||||
|
// IMusicItem struct {
|
||||||
|
// Artist string `json:"artist"` // 作者
|
||||||
|
// Title string `json:"title"` // 歌曲标题
|
||||||
|
// Duration int `json:"duration,omitempty"` // 时长(s)
|
||||||
|
// Album string `json:"album,omitempty"` // 专辑名
|
||||||
|
// Artwork string `json:"artwork,omitempty"` // 专辑封面图
|
||||||
|
// Url string `json:"url,omitempty"` // 默认音源
|
||||||
|
// Lrc string `json:"lrc,omitempty"` // 歌词URL
|
||||||
|
// RawLrc string `json:"rawLrc,omitempty"` // 歌词文本(lrc格式 带时间戳)
|
||||||
|
// Other IExtra `json:"extra,omitempty"` // 其他
|
||||||
|
// }
|
||||||
|
// // 歌单
|
||||||
|
// IMusicSheetItem struct {
|
||||||
|
// Artwork string `json:"artwork,omitempty"` // 封面图
|
||||||
|
// Title string `json:"title"` // 标题
|
||||||
|
// Description string `json:"description,omitempty"` // 描述
|
||||||
|
// WorksNum int `json:"worksNum,omitempty"` // 作品总数
|
||||||
|
// PlayCount int `json:"playCount,omitempty"` // 播放次数
|
||||||
|
// MusicList []IMusicItem `json:"musicList,omitempty"` // 播放列表
|
||||||
|
// CreateAt int64 `json:"createAt,omitempty"` // 歌单创建日期
|
||||||
|
// Artist string `json:"artist,omitempty"` // 歌单作者
|
||||||
|
// Other IExtra `json:"extra,omitempty"` // 其他
|
||||||
|
// }
|
||||||
|
// // 专辑
|
||||||
|
// IAlbumItem IMusicSheetItem
|
||||||
|
// // 作者
|
||||||
|
// IArtistItem struct {
|
||||||
|
// Platform string `json:"platform,omitempty"` // 插件名
|
||||||
|
// ID interface{} `json:"id"` // 唯一id
|
||||||
|
// Name string `json:"name"` // 姓名
|
||||||
|
// Fans int `json:"fans,omitempty"` // 粉丝数
|
||||||
|
// Description string `json:"description,omitempty"` // 简介
|
||||||
|
// Avatar string `json:"avatar,omitempty"` // 头像
|
||||||
|
// WorksNum int `json:"worksNum,omitempty"` // 作品数目
|
||||||
|
// MusicList []IMusicItem `json:"musicList,omitempty"` // 作者的音乐列表
|
||||||
|
// AlbumList []IAlbumItem `json:"albumList,omitempty"` // 作者的专辑列表
|
||||||
|
// Other IExtra `json:"extra,omitempty"` // 其他
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
|
||||||
|
// 结构表
|
||||||
|
// type (
|
||||||
|
// // 重复
|
||||||
|
// XPublicKeys struct {
|
||||||
|
// ID string `json:"id" gorm:"primaryKey"` // 唯一ID
|
||||||
|
// Exp int64 `json:"exp" gorm:"column:exp"` // 过期时间
|
||||||
|
// }
|
||||||
|
// // 音乐
|
||||||
|
// XMusicItem struct {
|
||||||
|
// ID string `json:"id" gorm:"primaryKey"` // 唯一ID
|
||||||
|
// Name string `json:"name" gorm:"column:name"` // 歌曲名称
|
||||||
|
// }
|
||||||
|
// // 作者
|
||||||
|
// // XArtistItem struct {
|
||||||
|
// // ID string `json:"id" gorm:"primaryKey"`
|
||||||
|
// // Name string `json:"name" gorm:"column:name"`
|
||||||
|
// // }
|
||||||
|
// // 歌词
|
||||||
|
// XLyricItem struct {
|
||||||
|
// ID string `json:"id" gorm:"primaryKey"`
|
||||||
|
// Lyric string `json:"lyric" gorm:"column:lyric"` // 歌曲歌词
|
||||||
|
// TLyric string `json:"tlyric" gorm:"column:tlyric"` // 翻译歌词,没有可为 null
|
||||||
|
// RLyric string `json:"rlyric" gorm:"column:rlyric"` // 罗马音歌词,没有可为 null
|
||||||
|
// LxLyric string `json:"lxlyric" gorm:"column:lxlyric"` // lx 逐字歌词,没有可为 null
|
||||||
|
// // 歌词格式为 [分钟:秒.毫秒]<开始时间(基于该句),持续时间>歌词文字
|
||||||
|
// // 例如: [00:00.000]<0,36>测<36,36>试<50,60>歌<80,75>词
|
||||||
|
// }
|
||||||
|
// // 视频
|
||||||
|
// // XMovieItem struct {
|
||||||
|
// // ID string `json:"id" gorm:"primaryKey"`
|
||||||
|
// // Name string `json:"name" gorm:"column:name"`
|
||||||
|
// // }
|
||||||
|
// // 链接
|
||||||
|
// XLinkItem struct {
|
||||||
|
// ID string `json:"id" gorm:"primaryKey"`
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
|
||||||
|
// const (
|
||||||
|
// T_artist = `artist`
|
||||||
|
// T_detail = `detail`
|
||||||
|
// T_lyric = `lyric`
|
||||||
|
// T_music = `music`
|
||||||
|
// )
|
||||||
|
|
||||||
|
// 分源表
|
||||||
|
// type (
|
||||||
|
// // Music
|
||||||
|
// WyMusic MusicItem
|
||||||
|
// MgMusic MusicItem
|
||||||
|
// KwMusic MusicItem
|
||||||
|
// KgMusic MusicItem
|
||||||
|
// TxMusic MusicItem
|
||||||
|
// LxMusic MusicItem
|
||||||
|
// // Artist
|
||||||
|
// WyArtist ArtistItem
|
||||||
|
// MgArtist ArtistItem
|
||||||
|
// KwArtist ArtistItem
|
||||||
|
// KgArtist ArtistItem
|
||||||
|
// TxArtist ArtistItem
|
||||||
|
// LxArtist ArtistItem
|
||||||
|
// // Lyric
|
||||||
|
// WyLyric LyricItem
|
||||||
|
// MgLyric LyricItem
|
||||||
|
// KwLyric LyricItem
|
||||||
|
// KgLyric LyricItem
|
||||||
|
// TxLyric LyricItem
|
||||||
|
// LxLyric LyricItem
|
||||||
|
// )
|
165
src/env/env.go
vendored
165
src/env/env.go
vendored
@ -12,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Version = `1.0.2-b12`
|
Version = `1.0.3.0622`
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -26,20 +26,34 @@ var (
|
|||||||
序号 名称 描述
|
序号 名称 描述
|
||||||
*/
|
*/
|
||||||
type (
|
type (
|
||||||
|
// 系统
|
||||||
Conf_Main struct {
|
Conf_Main struct {
|
||||||
Debug bool `comment:"调试模式"`
|
Debug bool `comment:"调试模式"`
|
||||||
Listen string `comment:"监听地址"`
|
Listen []string `comment:"监听地址 (多端口以','分隔)"`
|
||||||
Gzip bool `comment:"开启GZip (对已压缩的内容使用会产生反效果)"`
|
Gzip bool `comment:"开启GZip (对已压缩的内容使用会产生反效果)"`
|
||||||
|
Cors bool `comment:"添加跨域响应头 (兼容前端请求)"`
|
||||||
LogPath string `comment:"文件日志路径,不填禁用"`
|
LogPath string `comment:"文件日志路径,不填禁用"`
|
||||||
Print bool `comment:"控制台输出"`
|
Print bool `comment:"控制台输出 (影响io性能,后台使用建议关闭)"`
|
||||||
SysLev bool `comment:"(实验性) 设置进程高优先级"`
|
SysLev bool `comment:"(实验性) 设置进程高优先级"`
|
||||||
// FFConv bool `comment:"(实验性) 使用FFMpeg修复音频(本地缓存)"`
|
// FFConv bool `comment:"(实验性) 使用FFMpeg修复音频(本地缓存)"`
|
||||||
|
NgProxy bool `comment:"兼容反向代理(beta)"`
|
||||||
|
Timeout int64 `comment:"网络请求超时(单位:秒,海外服务器可适当调大)"`
|
||||||
|
Store string `comment:"内存缓存持久化文件地址"`
|
||||||
|
ErrMp3 string `comment:"获取失败默认音频"`
|
||||||
}
|
}
|
||||||
|
// 接口
|
||||||
Conf_Apis struct {
|
Conf_Apis struct {
|
||||||
// 预留:后期可能会出一个WebUI,/webui,相关设置
|
// 预留:后期可能会出一个WebUI,/webui,相关设置
|
||||||
// BindAddr string `comment:"外部访问地址,用于生成文件链接"`
|
// WebUI
|
||||||
// LxM_Auth string `comment:"验证Key,自动生成,填写null禁用"`
|
// WebUI_Enable bool `comment:"是否开启WebUI相关接口"`
|
||||||
|
// App
|
||||||
|
// App_Enable bool `comment:"是否开启软件接口"`
|
||||||
|
// App Lx-Music
|
||||||
|
// App_LX_Enable bool `comment:"是否开启Lx-Music相关接口"`
|
||||||
|
// App MusicFree
|
||||||
|
// App_MF_Enable bool `comment:"是否开启MusicFree相关接口"`
|
||||||
}
|
}
|
||||||
|
// 验证
|
||||||
Conf_Auth struct {
|
Conf_Auth struct {
|
||||||
// ApiKey
|
// ApiKey
|
||||||
ApiKey_Enable bool `comment:"是否开启Key验证"`
|
ApiKey_Enable bool `comment:"是否开启Key验证"`
|
||||||
@ -47,17 +61,18 @@ type (
|
|||||||
// 速率限制
|
// 速率限制
|
||||||
RateLimit_Enable bool `comment:"是否开启速率限制"`
|
RateLimit_Enable bool `comment:"是否开启速率限制"`
|
||||||
RateLimit_Block uint32 `comment:"检测范围,每分区为x秒"` // 每x秒一个分区
|
RateLimit_Block uint32 `comment:"检测范围,每分区为x秒"` // 每x秒一个分区
|
||||||
RateLimit_Global uint32 `comment:"全局速率限制,单位次每x秒(暂未开放)"`
|
// RateLimit_Global uint32 `comment:"全局速率限制,单位次每x秒(暂未开放)"`
|
||||||
RateLimit_Single uint32 `comment:"单IP速率限制,单位次每x秒"`
|
RateLimit_Single uint32 `comment:"单IP速率限制,单位次每x秒"`
|
||||||
RateLimit_BanNum uint32 `comment:"容忍限度,超出限制N次后封禁"`
|
RateLimit_BanNum uint32 `comment:"容忍限度,超出限制N次后封禁"`
|
||||||
RateLimit_BanTim uint32 `comment:"封禁后每次延长时间"`
|
RateLimit_BanTim uint32 `comment:"封禁后每次延长时间"`
|
||||||
// 黑白名单
|
// 黑白名单
|
||||||
BanList_Mode string `comment:"名单模式 0: off(关闭), 1: white(白名单), 2: black(黑名单)"`
|
// BanList_Mode string `comment:"名单模式 0: off(关闭), 1: white(白名单), 2: black(黑名单)"`
|
||||||
BanList_White []string `comment:"host白名单"`
|
// BanList_White []string `comment:"host白名单"`
|
||||||
BanList_Black []string `comment:"host黑名单"`
|
// BanList_Black []string `comment:"host黑名单"`
|
||||||
}
|
}
|
||||||
|
// 来源
|
||||||
Conf_Source struct {
|
Conf_Source struct {
|
||||||
Mode string `comment:"音乐来源 0: off(关闭 仅本地), 1: builtin(内置), 2: custom(登录账号 暂不支持)"`
|
// Mode string `comment:"音乐来源 0: off(关闭 仅本地), 1: builtin(内置), 2: custom(登录账号 暂不支持)"`
|
||||||
// 伪装IP
|
// 伪装IP
|
||||||
FakeIP_Mode string `comment:"伪装IP模式 0: off(关闭), 1: req(传入值), 2: val(静态)"`
|
FakeIP_Mode string `comment:"伪装IP模式 0: off(关闭), 1: req(传入值), 2: val(静态)"`
|
||||||
FakeIP_Value string `comment:"静态伪装IP"`
|
FakeIP_Value string `comment:"静态伪装IP"`
|
||||||
@ -66,18 +81,42 @@ type (
|
|||||||
Proxy_Address string `comment:"代理地址 (支持http, socks)"`
|
Proxy_Address string `comment:"代理地址 (支持http, socks)"`
|
||||||
// 验证
|
// 验证
|
||||||
MusicIdVerify bool `comment:"(beta) 验证音乐ID可用性"`
|
MusicIdVerify bool `comment:"(beta) 验证音乐ID可用性"`
|
||||||
ForceFallback bool `comment:"忽略音质限制,强制获取试听音频"`
|
ForceFallback bool `comment:"忽略音质限制,强制返回链接(部分支持)"`
|
||||||
|
// 总开关(解决部分源无法彻底禁用问题)?
|
||||||
|
Enable_Wy bool `comment:"是否开启小芸源"`
|
||||||
|
Enable_Mg bool `comment:"是否开启小蜜源"`
|
||||||
|
Enable_Kw bool `comment:"是否开启小蜗源"`
|
||||||
|
Enable_Kg bool `comment:"是否开启小枸源"`
|
||||||
|
Enable_Tx bool `comment:"是否开启小秋源"`
|
||||||
|
Enable_Lx bool `comment:"是否开启小洛源"`
|
||||||
} // `comment:""`
|
} // `comment:""`
|
||||||
|
// 账号
|
||||||
Conf_Custom struct {
|
Conf_Custom struct {
|
||||||
// wy (暂未实现)
|
// wy
|
||||||
Wy_Enable bool `comment:"是否开启小芸源"`
|
Wy_Enable bool `comment:"是否启用小芸源"`
|
||||||
Wy_Cookie string `comment:"账号cookie数据"`
|
Wy_Mode string `comment:"获取方式 0: builtin, 1: 163api"`
|
||||||
|
// wy 163api
|
||||||
|
Wy_Api_Type string `comment:"调用方式 0: native(内置模块), 1: remote(指定地址)"`
|
||||||
|
Wy_Api_Address string `comment:"NeteaseCloudMusicApi项目地址"`
|
||||||
|
Wy_Api_Cookie string `comment:"账号cookie数据"`
|
||||||
|
// wy refresh
|
||||||
|
Wy_Refresh_Enable bool `comment:"是否启用刷新登录"`
|
||||||
|
Wy_Refresh_Interval int64 `comment:"下次刷新时间 (由程序维护)"`
|
||||||
|
|
||||||
// mg (暂未实现)
|
// mg
|
||||||
// Mg_Enable bool `comment:"是否开启小蜜源"`
|
Mg_Enable bool `comment:"是否启用小蜜源"`
|
||||||
|
Mg_Mode string `comment:"获取方式 0: builtin, 1: custom"`
|
||||||
|
// mg custom
|
||||||
|
Mg_Usr_VerId string `comment:"field user.aversionid"`
|
||||||
|
Mg_Usr_Token string `comment:"field user.token"`
|
||||||
|
Mg_Usr_OSVer string `comment:"field user.osversion"`
|
||||||
|
Mg_Usr_ReqUA string `comment:"field user.useragent"`
|
||||||
|
// mg refresh
|
||||||
|
Mg_Refresh_Enable bool `comment:"是否启用Cookie保活"`
|
||||||
|
Mg_Refresh_Interval int64 `comment:"下次运行时间 (自动更新)"`
|
||||||
|
|
||||||
// kw
|
// kw
|
||||||
Kw_Enable bool `comment:"是否开启小蜗源"`
|
Kw_Enable bool `comment:"是否启用小蜗源"`
|
||||||
Kw_Mode string `comment:"接口模式 0: bdapi(需验证), 1: kwdes"`
|
Kw_Mode string `comment:"接口模式 0: bdapi(需验证), 1: kwdes"`
|
||||||
// kw bdapi
|
// kw bdapi
|
||||||
Kw_Bd_Uid string `comment:"field user.uid"`
|
Kw_Bd_Uid string `comment:"field user.uid"`
|
||||||
@ -85,31 +124,66 @@ type (
|
|||||||
Kw_Bd_DevId string `comment:"field user.device_id"`
|
Kw_Bd_DevId string `comment:"field user.device_id"`
|
||||||
// kw kwdes
|
// kw kwdes
|
||||||
Kw_Des_Type string `comment:"返回格式 0: text, 1: json"`
|
Kw_Des_Type string `comment:"返回格式 0: text, 1: json"`
|
||||||
|
Kw_Des_Source string `comment:"query source"`
|
||||||
Kw_Des_Header string `comment:"请求头 User-Agent"`
|
Kw_Des_Header string `comment:"请求头 User-Agent"`
|
||||||
|
|
||||||
// kg (暂未实现)
|
// kg
|
||||||
// Kg_Enable bool `comment:"是否开启小枸源"`
|
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:""`
|
||||||
|
|
||||||
// tx
|
// tx
|
||||||
Tx_Enable bool `comment:"是否开启小秋源"`
|
Tx_Enable bool `comment:"是否启用小秋源"`
|
||||||
Tx_Ukey string `comment:"Cookie中/客户端的请求体中的(comm.authst)"`
|
Tx_Ukey string `comment:"Cookie中/客户端的请求体中的(comm.authst)"`
|
||||||
Tx_Uuin string `comment:"key对应的QQ号"`
|
Tx_Uuin string `comment:"key对应的QQ号"`
|
||||||
|
Tx_CDNUrl string `comment:"指定音频CDN地址"`
|
||||||
// tx refresh_login
|
// tx refresh_login
|
||||||
Tx_Refresh_Enable bool `comment:"是否启动刷新登录"`
|
Tx_Refresh_Enable bool `comment:"是否启动刷新登录"`
|
||||||
Tx_Refresh_Interval int64 `comment:"刷新间隔 (由程序维护,非必要无需修改)"`
|
Tx_Refresh_Interval int64 `comment:"刷新间隔 (由程序维护,非必要无需修改)"`
|
||||||
|
|
||||||
|
// lx (local)
|
||||||
|
// Lx_Enable bool `comment:"是否启用小洛源"`
|
||||||
}
|
}
|
||||||
Conf_Script struct {
|
// 脚本
|
||||||
|
Conf_Script_Update struct {
|
||||||
Ver string `comment:"自定义脚本版本" json:"ver"`
|
Ver string `comment:"自定义脚本版本" json:"ver"`
|
||||||
Log string `comment:"更新日志" json:"log"`
|
Log string `comment:"更新日志" json:"log"`
|
||||||
Url string `comment:"脚本下载地址 (public目录内文件名)" json:"url"`
|
Url string `comment:"脚本下载地址 (public目录内文件名)" json:"url"`
|
||||||
Force bool `comment:"强制推送更新" json:"force"`
|
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 {
|
Conf_Cache struct {
|
||||||
Mode string `comment:"缓存模式 0: off(关闭), 1: local(本地), 2: cloudreve(云盘 未完善)"`
|
Mode string `comment:"缓存模式 0: off(关闭), 1: local(本地), 2: cloudreve(云盘 未完善)"`
|
||||||
LinkMode string `comment:"外链样式 1: static(永久链), 2: dynamic(临时链)"`
|
LinkMode string `comment:"外链样式 1: static(永久链), 2: dynamic(临时链)"`
|
||||||
// 本地
|
// 本地
|
||||||
Local_Path string `comment:"本地缓存保存路径"`
|
Local_Path string `comment:"本地缓存保存路径"`
|
||||||
Local_Bind string `comment:"本地缓存外部访问地址"`
|
Local_Bind string `comment:"本地缓存外部访问地址"`
|
||||||
|
Local_Auto bool `comment:"自适应缓存访问地址(beta)"`
|
||||||
// 云盘
|
// 云盘
|
||||||
Cloud_Site string `comment:"Cloudreve站点地址"`
|
Cloud_Site string `comment:"Cloudreve站点地址"`
|
||||||
Cloud_User string `comment:"Cloudreve用户名"`
|
Cloud_User string `comment:"Cloudreve用户名"`
|
||||||
@ -117,6 +191,7 @@ type (
|
|||||||
Cloud_Sess string `comment:"Cloudreve会话"`
|
Cloud_Sess string `comment:"Cloudreve会话"`
|
||||||
Cloud_Path string `comment:"Cloudreve存储路径"`
|
Cloud_Path string `comment:"Cloudreve存储路径"`
|
||||||
}
|
}
|
||||||
|
// 结构
|
||||||
Conf struct {
|
Conf struct {
|
||||||
Main Conf_Main `comment:"程序主配置"`
|
Main Conf_Main `comment:"程序主配置"`
|
||||||
Apis Conf_Apis `comment:"接口设置"`
|
Apis Conf_Apis `comment:"接口设置"`
|
||||||
@ -133,11 +208,14 @@ var (
|
|||||||
DefCfg = Conf{
|
DefCfg = Conf{
|
||||||
Main: Conf_Main{
|
Main: Conf_Main{
|
||||||
Debug: false,
|
Debug: false,
|
||||||
Listen: `127.0.0.1:1011`,
|
Listen: []string{`127.0.0.1:1011`},
|
||||||
Gzip: false,
|
Gzip: false,
|
||||||
LogPath: `/data/logfile.log`,
|
LogPath: `/data/logfile.log`,
|
||||||
Print: true,
|
Print: true,
|
||||||
SysLev: false,
|
SysLev: false,
|
||||||
|
Timeout: 30,
|
||||||
|
Store: `/data/memo.bin`,
|
||||||
|
ErrMp3: `https://r2eu.zxwy.link/gh/lx-source/static/error.mp3`,
|
||||||
},
|
},
|
||||||
Apis: Conf_Apis{
|
Apis: Conf_Apis{
|
||||||
// BindAddr: `http://192.168.10.22:1011/`,
|
// BindAddr: `http://192.168.10.22:1011/`,
|
||||||
@ -146,39 +224,72 @@ var (
|
|||||||
ApiKey_Enable: true,
|
ApiKey_Enable: true,
|
||||||
RateLimit_Enable: false,
|
RateLimit_Enable: false,
|
||||||
RateLimit_Block: 30,
|
RateLimit_Block: 30,
|
||||||
RateLimit_Global: 1,
|
// RateLimit_Global: 1,
|
||||||
RateLimit_Single: 15,
|
RateLimit_Single: 15,
|
||||||
RateLimit_BanNum: 5,
|
RateLimit_BanNum: 5,
|
||||||
RateLimit_BanTim: 10,
|
RateLimit_BanTim: 10,
|
||||||
BanList_Mode: `off`,
|
// BanList_Mode: `off`,
|
||||||
BanList_White: []string{`127.0.0.1`},
|
// BanList_White: []string{`127.0.0.1`},
|
||||||
},
|
},
|
||||||
Source: Conf_Source{
|
Source: Conf_Source{
|
||||||
Mode: `builtin`,
|
// Mode: `builtin`,
|
||||||
FakeIP_Mode: `0`,
|
FakeIP_Mode: `0`,
|
||||||
FakeIP_Value: `192.168.10.2`,
|
FakeIP_Value: `192.168.10.2`,
|
||||||
Proxy_Enable: false,
|
Proxy_Enable: false,
|
||||||
Proxy_Address: `{protocol}://({user}:{password})@{address}:{port}`,
|
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{
|
Custom: Conf_Custom{
|
||||||
Wy_Enable: true,
|
Wy_Enable: true,
|
||||||
|
Wy_Mode: `builtin`,
|
||||||
|
Wy_Api_Type: `native`,
|
||||||
|
Wy_Refresh_Interval: 1633622400,
|
||||||
|
|
||||||
|
Mg_Enable: true,
|
||||||
|
Mg_Mode: `builtin`,
|
||||||
|
Mg_Usr_OSVer: `10`,
|
||||||
|
Mg_Usr_ReqUA: `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36`,
|
||||||
|
|
||||||
Kw_Enable: true,
|
Kw_Enable: true,
|
||||||
Kw_Mode: `kwdes`,
|
Kw_Mode: `kwdes`,
|
||||||
Kw_Des_Type: `json`,
|
Kw_Des_Type: `json`,
|
||||||
Kw_Des_Header: `okhttp/3.10.0`,
|
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_Enable: false,
|
||||||
|
Tx_CDNUrl: `https://isure6.stream.qqmusic.qq.com/`,
|
||||||
Tx_Refresh_Enable: false,
|
Tx_Refresh_Enable: false,
|
||||||
Tx_Refresh_Interval: 86000,
|
Tx_Refresh_Interval: 86000,
|
||||||
},
|
},
|
||||||
Script: Conf_Script{
|
Script: Conf_Script{
|
||||||
|
Name: `Lx-Source-Script`,
|
||||||
|
Descript: `洛雪音乐自定义源脚本`,
|
||||||
|
Version: `1.1.0`,
|
||||||
|
Author: `Zxwy`,
|
||||||
|
Homepage: `https://github.com/ZxwyWebSite/lx-script`,
|
||||||
|
|
||||||
|
Update: Conf_Script_Update{
|
||||||
Log: `发布更新 (请删除旧源后重新导入):进行了部分优化,修复了部分Bug`, // 更新日志
|
Log: `发布更新 (请删除旧源后重新导入):进行了部分优化,修复了部分Bug`, // 更新日志
|
||||||
|
|
||||||
Ver: `1.0.3`, // 自定义脚本版本
|
Ver: `1.0.3`, // 自定义脚本版本
|
||||||
Force: true, // 强制推送更新
|
Force: true, // 强制推送更新
|
||||||
|
|
||||||
Url: `public/lx-custom-source.js`, // 脚本下载地址
|
Url: `lx-custom-source.js`, // 脚本下载地址
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Cache: Conf_Cache{
|
Cache: Conf_Cache{
|
||||||
Mode: `local`, // 缓存模式
|
Mode: `local`, // 缓存模式
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/gob"
|
||||||
"lx-source/src/env"
|
"lx-source/src/env"
|
||||||
"lx-source/src/middleware/resp"
|
"lx-source/src/middleware/resp"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@ -17,21 +18,12 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
gob.Register(RateLimit{})
|
||||||
|
}
|
||||||
|
|
||||||
func InitHandler(h gin.HandlerFunc) (out []gin.HandlerFunc) {
|
func InitHandler(h gin.HandlerFunc) (out []gin.HandlerFunc) {
|
||||||
loger := env.Loger.NewGroup(`AuthHandler`)
|
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 速率限制
|
// RateLimit 速率限制
|
||||||
/*
|
/*
|
||||||
逻辑:
|
逻辑:
|
||||||
@ -51,9 +43,16 @@ func InitHandler(h gin.HandlerFunc) (out []gin.HandlerFunc) {
|
|||||||
block_mem := int(env.Config.Auth.RateLimit_Block * env.Config.Auth.RateLimit_BanNum)
|
block_mem := int(env.Config.Auth.RateLimit_Block * env.Config.Auth.RateLimit_BanNum)
|
||||||
bannum := env.Config.Auth.RateLimit_Single + env.Config.Auth.RateLimit_BanNum
|
bannum := env.Config.Auth.RateLimit_Single + env.Config.Auth.RateLimit_BanNum
|
||||||
bantim := int64(env.Config.Auth.RateLimit_BanTim)
|
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) {
|
out = append(out, func(c *gin.Context) {
|
||||||
resp.Wrap(c, func() *resp.Resp {
|
resp.Wrap(c, func() *resp.Resp {
|
||||||
rip := c.RemoteIP()
|
rip := getIp(c)
|
||||||
if rip == `` {
|
if rip == `` {
|
||||||
rip = `0.0.0.0`
|
rip = `0.0.0.0`
|
||||||
}
|
}
|
||||||
@ -84,21 +83,24 @@ func InitHandler(h gin.HandlerFunc) (out []gin.HandlerFunc) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// ApiKey 请求头验证
|
||||||
|
if env.Config.Auth.ApiKey_Enable {
|
||||||
|
loger.Debug(`ApiKeyAuth Enabled`)
|
||||||
|
out = append(out, func(c *gin.Context) {
|
||||||
|
resp.Wrap(c, func() *resp.Resp {
|
||||||
|
var auth string
|
||||||
|
if key, ok := c.GetQuery(`key`); ok {
|
||||||
|
auth = key
|
||||||
|
} else {
|
||||||
|
auth = c.Request.Header.Get(`X-LxM-Auth`)
|
||||||
|
}
|
||||||
|
if auth != env.Config.Auth.ApiKey_Value {
|
||||||
|
loger.Debug(`验证失败: %q`, auth)
|
||||||
|
return &resp.Resp{Code: 3, Msg: `验证Key失败, 请联系网站管理员`}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
return append(out, h)
|
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
src/middleware/auth/ratelimit.go
Normal file
1
src/middleware/auth/ratelimit.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package auth
|
@ -1,75 +0,0 @@
|
|||||||
// 静态资源
|
|
||||||
package loadpublic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/fs"
|
|
||||||
"lx-source/src/env"
|
|
||||||
"net/http"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/ZxwyWebSite/ztool"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed public
|
|
||||||
var publicEM embed.FS // 打包默认Public目录 src/router/router.go
|
|
||||||
|
|
||||||
// 载入Public目录并设置路由
|
|
||||||
func LoadPublic(r *gin.Engine) {
|
|
||||||
pf := env.Loger.NewGroup(`PublicFS`)
|
|
||||||
dir := ztool.Str_FastConcat(env.RunPath, `/data/public`)
|
|
||||||
publicFS, err := fs.Sub(publicEM, `public`)
|
|
||||||
var httpFS http.FileSystem = http.FS(publicFS)
|
|
||||||
if err != nil {
|
|
||||||
pf.Fatal(`内置Public目录载入错误: %s, 请尝试重新编译`, err)
|
|
||||||
}
|
|
||||||
if !ztool.Fbj_IsExists(dir) {
|
|
||||||
pf.Info(`不存在Public目录, 释放默认静态文件`)
|
|
||||||
walk := func(relPath string, d fs.DirEntry, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf(`无法获取[%q]的信息: %s`, relPath, err)
|
|
||||||
}
|
|
||||||
if !d.IsDir() {
|
|
||||||
out, err := ztool.Fbj_CreatFile(filepath.Join(dir, relPath))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf(`无法创建文件[%q]: %s`, relPath, err)
|
|
||||||
}
|
|
||||||
defer out.Close()
|
|
||||||
pf.Debug(`导出 [%q]...`, relPath)
|
|
||||||
obj, err := publicFS.Open(relPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf(`无法打开文件[%q]: %s`, relPath, err)
|
|
||||||
}
|
|
||||||
if _, err := io.Copy(out, obj); err != nil {
|
|
||||||
return fmt.Errorf(`无法写入文件[%q]: %s`, relPath, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := fs.WalkDir(publicFS, `.`, walk); err != nil {
|
|
||||||
pf.Error(`无法释放静态文件: %s`, err)
|
|
||||||
// pf.Warn(`正在使用内置Public目录, 将无法自定义静态文件`)
|
|
||||||
// httpFS = http.FS(publicFS)
|
|
||||||
} else {
|
|
||||||
pf.Info(`全部静态资源导出完成, 祝你使用愉快 ^_^`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// httpFS = gin.Dir(dir, false)
|
|
||||||
// r.GET(`/:file`, func(c *gin.Context) {
|
|
||||||
// file := c.Param(`file`)
|
|
||||||
// switch file {
|
|
||||||
// case `favicon.ico`:
|
|
||||||
// c.FileFromFS(`icon.ico`, httpFS)
|
|
||||||
// // case `lx-custom-source.js`:
|
|
||||||
// // c.FileFromFS(`lx-custom-source.js`, http.FS(publicFS))
|
|
||||||
// default:
|
|
||||||
// c.FileFromFS(file, httpFS)
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
r.StaticFileFS(`/favicon.ico`, `icon.ico`, httpFS)
|
|
||||||
r.StaticFileFS(`/lx-custom-source.js`, `lx-custom-source.js`, httpFS)
|
|
||||||
r.StaticFS(`/public`, httpFS)
|
|
||||||
}
|
|
@ -2,6 +2,7 @@
|
|||||||
package resp
|
package resp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"lx-source/src/env"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@ -9,7 +10,7 @@ import (
|
|||||||
|
|
||||||
// 统一输出
|
// 统一输出
|
||||||
/*
|
/*
|
||||||
返回码对应表:
|
返回码对应表 (参考Python版):
|
||||||
0: http.StatusOK, // [200] 成功
|
0: http.StatusOK, // [200] 成功
|
||||||
1: http.StatusForbidden, // [403] IP被封禁
|
1: http.StatusForbidden, // [403] IP被封禁
|
||||||
2: http.StatusServiceUnavailable, // [503] 获取失败
|
2: http.StatusServiceUnavailable, // [503] 获取失败
|
||||||
@ -21,36 +22,45 @@ import (
|
|||||||
type Resp struct {
|
type Resp struct {
|
||||||
Code int `json:"code"` // 状态码 为兼容内置源设置 暂无实际作用 (1.0.2后已兼容Python版定义)
|
Code int `json:"code"` // 状态码 为兼容内置源设置 暂无实际作用 (1.0.2后已兼容Python版定义)
|
||||||
Msg string `json:"msg"` // 提示or报错信息
|
Msg string `json:"msg"` // 提示or报错信息
|
||||||
Data string `json:"data"` // 音乐URL
|
Data any `json:"data"` // 音乐URL
|
||||||
Ext string `json:"ext,omitempty"` // 其它信息
|
Ext any `json:"ext,omitempty"` // 其它信息
|
||||||
}
|
}
|
||||||
|
|
||||||
// 返回码对应列表 (参考Python版)
|
// 获取失败默认音频
|
||||||
var statusMap = map[int]int{
|
// var ErrMp3 = `https://r2eu.zxwy.link/gh/lx-source/static/error.mp3`
|
||||||
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
|
注:Code不为0时调用c.Abort()终止Handler
|
||||||
*/
|
*/
|
||||||
func (o *Resp) Execute(c *gin.Context) {
|
func (o *Resp) Execute(c *gin.Context) {
|
||||||
status, ok := statusMap[o.Code]
|
// StatusCode转换 (小分支switch快, 大分支map快)
|
||||||
if !ok {
|
var status int
|
||||||
|
switch o.Code {
|
||||||
|
case 0:
|
||||||
|
status = http.StatusOK
|
||||||
|
case 1:
|
||||||
|
status = http.StatusForbidden
|
||||||
|
case 2:
|
||||||
|
status = http.StatusServiceUnavailable
|
||||||
|
if o.Data == nil || o.Data == `` {
|
||||||
|
o.Data = env.Config.Main.ErrMp3 //ErrMp3
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
status = http.StatusUnauthorized
|
||||||
|
case 4:
|
||||||
|
status = http.StatusInternalServerError
|
||||||
|
case 5:
|
||||||
|
status = http.StatusTooManyRequests
|
||||||
|
case 6:
|
||||||
|
status = http.StatusBadRequest
|
||||||
|
default:
|
||||||
status = http.StatusOK
|
status = http.StatusOK
|
||||||
}
|
}
|
||||||
if o.Code != 0 {
|
if o.Code != 0 {
|
||||||
if o.Code == 2 /*&& o.Data == ``*/ {
|
// if o.Code == 2 /*&& o.Data == ``*/ {
|
||||||
o.Data = ErrMp3
|
// o.Data = ErrMp3
|
||||||
}
|
// }
|
||||||
c.Abort()
|
c.Abort()
|
||||||
}
|
}
|
||||||
c.JSON(status, o)
|
c.JSON(status, o)
|
||||||
@ -65,3 +75,9 @@ func Wrap(c *gin.Context, f func() *Resp) {
|
|||||||
r.Execute(c)
|
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,8 +1,21 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import "github.com/gin-gonic/gin"
|
import (
|
||||||
|
"lx-source/src/env"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/ZxwyWebSite/ztool"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
// 将路由参数转为Map
|
// 将路由参数转为Map
|
||||||
|
/*
|
||||||
|
`:s/:id/:q` -> {
|
||||||
|
`s`: `source`,
|
||||||
|
`id`: `musicId`,
|
||||||
|
`q`: `quality`,
|
||||||
|
}
|
||||||
|
*/
|
||||||
func ParaMap(c *gin.Context) map[string]string {
|
func ParaMap(c *gin.Context) map[string]string {
|
||||||
parmlen := len(c.Params)
|
parmlen := len(c.Params)
|
||||||
parms := make(map[string]string, parmlen)
|
parms := make(map[string]string, parmlen)
|
||||||
@ -11,3 +24,51 @@ func ParaMap(c *gin.Context) map[string]string {
|
|||||||
}
|
}
|
||||||
return parms
|
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)
|
||||||
|
}
|
||||||
|
@ -1,198 +0,0 @@
|
|||||||
package router
|
|
||||||
|
|
||||||
import (
|
|
||||||
"lx-source/src/caches"
|
|
||||||
"lx-source/src/env"
|
|
||||||
"lx-source/src/middleware/auth"
|
|
||||||
"lx-source/src/middleware/dynlink"
|
|
||||||
"lx-source/src/middleware/loadpublic"
|
|
||||||
"lx-source/src/middleware/resp"
|
|
||||||
"lx-source/src/middleware/util"
|
|
||||||
"lx-source/src/sources"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/ZxwyWebSite/ztool"
|
|
||||||
"github.com/gin-contrib/gzip"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// 默认音质
|
|
||||||
defQuality = []string{`128k`, `320k`, `flac`, `flac24bit`}
|
|
||||||
// 试听音质
|
|
||||||
tstQuality = []string{`128k`}
|
|
||||||
// 标准音质
|
|
||||||
stdQuality = []string{`128k`, `320k`, `flac`}
|
|
||||||
)
|
|
||||||
|
|
||||||
// 自动生成支持的音质表
|
|
||||||
func loadQMap() [][]string {
|
|
||||||
m := make([][]string, 6)
|
|
||||||
// 0.wy
|
|
||||||
if env.Config.Custom.Wy_Enable {
|
|
||||||
m[0] = defQuality
|
|
||||||
} else {
|
|
||||||
m[0] = tstQuality
|
|
||||||
}
|
|
||||||
// 1.mg
|
|
||||||
m[1] = defQuality
|
|
||||||
// 2.kw
|
|
||||||
m[2] = defQuality
|
|
||||||
// 3.kg
|
|
||||||
m[3] = tstQuality
|
|
||||||
// 4.tx
|
|
||||||
if env.Config.Custom.Tx_Enable {
|
|
||||||
m[4] = stdQuality
|
|
||||||
} else {
|
|
||||||
m[4] = tstQuality
|
|
||||||
}
|
|
||||||
// 5.lx
|
|
||||||
// m[sources.S_lx] = defQuality
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// 载入路由
|
|
||||||
func InitRouter() *gin.Engine {
|
|
||||||
r := gin.Default()
|
|
||||||
qmap := loadQMap()
|
|
||||||
// Gzip压缩
|
|
||||||
if env.Config.Main.Gzip {
|
|
||||||
r.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{"/file/"})))
|
|
||||||
}
|
|
||||||
// 源信息
|
|
||||||
r.GET(`/`, func(c *gin.Context) {
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
`version`: env.Version, // 服务端程序版本
|
|
||||||
`name`: `lx-music-source`, // 名称
|
|
||||||
`msg`: `Hello~::^-^::~v1~`, // Api大版本
|
|
||||||
// `developer`: []string{`Zxwy`}, // 开发者列表, 可在保留原作者的基础上添加你自己的名字?
|
|
||||||
// 仓库地址
|
|
||||||
// `github`: `https://github.com/ZxwyWebSite/lx-source`,
|
|
||||||
// 可用平台
|
|
||||||
`source`: gin.H{
|
|
||||||
sources.S_wy: qmap[0], //true,
|
|
||||||
sources.S_mg: qmap[1], //true,
|
|
||||||
sources.S_kw: qmap[2], //true,
|
|
||||||
sources.S_kg: qmap[3], //[]string{`128k`, `320k`}, // 测试结构2, 启用时返回音质列表, 禁用为false
|
|
||||||
sources.S_tx: qmap[4], //gin.H{ // "测试结构 不代表最终方式"
|
|
||||||
// `enable`: false,
|
|
||||||
// `qualitys`: []string{`128k`, `320k`, `flac`, `flac24bit`},
|
|
||||||
// },
|
|
||||||
sources.S_lx: qmap[5],
|
|
||||||
},
|
|
||||||
// 自定义源脚本更新
|
|
||||||
`script`: env.DefCfg.Script, //env.Config.Script,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
// 静态文件
|
|
||||||
loadpublic.LoadPublic(r)
|
|
||||||
// r.StaticFile(`/favicon.ico`, `public/icon.ico`)
|
|
||||||
// r.StaticFile(`/lx-custom-source.js`, `public/lx-custom-source.js`)
|
|
||||||
// 解析接口
|
|
||||||
r.GET(`/link/:s/:id/:q`, auth.InitHandler(linkHandler)...)
|
|
||||||
dynlink.LoadHandler(r)
|
|
||||||
// r.GET(`/file/:t/:x/:f`, dynlink.FileHandler())
|
|
||||||
// if cache, ok := caches.UseCache.(*localcache.Cache); ok {
|
|
||||||
// r.Static(`/file`, cache.Path)
|
|
||||||
// }
|
|
||||||
// if env.Config.Cache.Mode == `local` {
|
|
||||||
// r.Static(`/file`, env.Config.Cache.Local_Path)
|
|
||||||
// }
|
|
||||||
// 软件接口
|
|
||||||
// api := r.Group(`/api`)
|
|
||||||
// {
|
|
||||||
// api.GET(`/lx`, lxHandler) // 洛雪音乐
|
|
||||||
// }
|
|
||||||
// 数据接口
|
|
||||||
// r.GET(`/file/:t/:hq/:n`, func(c *gin.Context) {
|
|
||||||
// c.String(http.StatusOK, time.Now().Format(`20060102150405`))
|
|
||||||
// })
|
|
||||||
// 暂不对文件接口进行验证 脚本返回链接无法附加请求头 只可在Get添加Query
|
|
||||||
// g := r.Group(``)
|
|
||||||
// {
|
|
||||||
// g.Use(authHandler)
|
|
||||||
// g.GET(`/link/:s/:id/:q`, linkHandler)
|
|
||||||
// g.Static(`/file`, LocalCachePath)
|
|
||||||
// }
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// 数据返回格式
|
|
||||||
const (
|
|
||||||
cacheHIT = `Cache HIT` // 缓存已命中
|
|
||||||
cacheMISS = `Cache MISS` // 缓存未命中
|
|
||||||
cacheSet = `Cache Seted` // 缓存已设置
|
|
||||||
|
|
||||||
memHIT = `Memory HIT` // 内存已命中
|
|
||||||
memRej = `Memory Reject` // 内存已拒绝
|
|
||||||
)
|
|
||||||
|
|
||||||
// 外链解析
|
|
||||||
func linkHandler(c *gin.Context) {
|
|
||||||
resp.Wrap(c, func() *resp.Resp {
|
|
||||||
// 获取传入参数 检查合法性
|
|
||||||
parms := util.ParaMap(c)
|
|
||||||
// getParam := func(p string) string { return strings.TrimSuffix(strings.TrimPrefix(c.Param(p), `/`), `/`) } //strings.Trim(c.Param(p), `/`)
|
|
||||||
s := parms[`s`] //c.Param(`s`) //getParam(`s`) // source 平台 wy, mg, kw
|
|
||||||
id := parms[`id`] //c.Param(`id`) //getParam(`id`) // sid 音乐ID wy: songmid, mg: copyrightId
|
|
||||||
q := parms[`q`] //c.Param(`q`) //getParam(`q`) // quality 音质 128k / 320k / flac / flac24bit
|
|
||||||
env.Loger.NewGroup(`LinkQuery`).Debug(`s: %v, id: %v, q: %v`, s, id, q)
|
|
||||||
if ztool.Chk_IsNilStr(s, q, id) {
|
|
||||||
return &resp.Resp{Code: 6, Msg: `参数不全`} // http.StatusBadRequest
|
|
||||||
}
|
|
||||||
cquery := caches.NewQuery(s, id, q)
|
|
||||||
// fmt.Printf("%+v\n", cquery)
|
|
||||||
defer cquery.Free()
|
|
||||||
// _, ok := sources.UseSource.Verify(cquery) // 获取请求音质 同时检测是否支持(如kw源没有flac24bit) qualitys[q][s]rquery
|
|
||||||
// if !ok {
|
|
||||||
// return &resp.Resp{Code: 6, Msg: `不支持的平台或音质`}
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 查询内存
|
|
||||||
clink, ok := env.Cache.Get(cquery.Query())
|
|
||||||
if ok {
|
|
||||||
if str, ok := clink.(string); ok {
|
|
||||||
env.Loger.NewGroup(`MemCache`).Debug(`MemHIT [%q]=>[%q]`, cquery.Query(), str)
|
|
||||||
if str == `` {
|
|
||||||
return &resp.Resp{Code: 2, Msg: memRej} // 拒绝请求,当前一段时间内解析出错 `MemCache Reject`
|
|
||||||
}
|
|
||||||
return &resp.Resp{Msg: memHIT, Data: str} // `MemCache HIT`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 查询缓存
|
|
||||||
var cstat bool
|
|
||||||
if caches.UseCache != nil {
|
|
||||||
cstat = caches.UseCache.Stat()
|
|
||||||
}
|
|
||||||
sc := env.Loger.NewGroup(`StatCache`)
|
|
||||||
if cstat {
|
|
||||||
sc.Debug(`Method: Get, Query: %v`, cquery.Query())
|
|
||||||
if link := caches.UseCache.Get(cquery); link != `` {
|
|
||||||
env.Cache.Set(cquery.Query(), link, 3600)
|
|
||||||
return &resp.Resp{Msg: cacheHIT, Data: link}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sc.Debug(`Disabled`)
|
|
||||||
}
|
|
||||||
// 解析歌曲外链
|
|
||||||
outlink, emsg := sources.UseSource.GetLink(cquery)
|
|
||||||
if emsg != `` {
|
|
||||||
if emsg == sources.Err_Verify { // Verify Failed: 不支持的平台或音质
|
|
||||||
return &resp.Resp{Code: 6, Msg: ztool.Str_FastConcat(emsg, `: 不支持的平台或音质`)}
|
|
||||||
}
|
|
||||||
env.Cache.Set(cquery.Query(), ``, 600) // 发生错误的10分钟内禁止再次查询
|
|
||||||
return &resp.Resp{Code: 2, Msg: emsg}
|
|
||||||
}
|
|
||||||
// 缓存并获取直链 !(s == `kg` || (s == `tx` && !tx_en)) => (s != `kg` && (s != `tx` || tx_en))
|
|
||||||
if outlink != `` && cstat && cquery.Source != sources.S_kg && (cquery.Source != sources.S_tx || env.Config.Custom.Tx_Enable) {
|
|
||||||
sc.Debug(`Method: Set, Link: %v`, outlink)
|
|
||||||
if link := caches.UseCache.Set(cquery, outlink); link != `` {
|
|
||||||
env.Cache.Set(cquery.Query(), link, 3600)
|
|
||||||
return &resp.Resp{Msg: cacheSet, Data: link}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 无法获取直链 直接返回原链接
|
|
||||||
env.Cache.Set(cquery.Query(), outlink, 1200)
|
|
||||||
return &resp.Resp{Msg: cacheMISS, Data: outlink}
|
|
||||||
})
|
|
||||||
}
|
|
158
src/server/api_music.go
Normal file
158
src/server/api_music.go
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"lx-source/src/caches"
|
||||||
|
"lx-source/src/env"
|
||||||
|
"lx-source/src/middleware/auth"
|
||||||
|
"lx-source/src/middleware/resp"
|
||||||
|
"lx-source/src/middleware/util"
|
||||||
|
"lx-source/src/sources"
|
||||||
|
"lx-source/src/sources/custom"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/ZxwyWebSite/ztool"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// type (
|
||||||
|
// Context struct {
|
||||||
|
// Parms map[string]string
|
||||||
|
// }
|
||||||
|
// Source interface {
|
||||||
|
// Link(c *Context) (string, error)
|
||||||
|
// Lyric(c *Context)
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
|
||||||
|
func loadMusic(api gin.IRouter) {
|
||||||
|
// /{method}/{source}/{musicId}/{?quality}
|
||||||
|
api.GET(`/:m/:s/:id/*q`, auth.InitHandler(musicHandler)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func musicHandler(c *gin.Context) {
|
||||||
|
resp.Wrap(c, func() *resp.Resp {
|
||||||
|
// 获取请求参数 (测试用Array会不会提升性能)
|
||||||
|
arr := util.ParaArr(c, `s`, `m`, `id`, `q`)
|
||||||
|
ps, pm, pid, pq := arr[0], arr[1], arr[2], strings.TrimPrefix(arr[3], `/`)
|
||||||
|
out := &resp.Resp{Code: 0} // 默认Code:6 (参数错误)
|
||||||
|
loger := env.Loger.NewGroup(`MusicHandler`)
|
||||||
|
defer loger.Free()
|
||||||
|
loger.Debug(`s:'%v', m:'%v', id:'%v', q:'%v'`, ps, pm, pid, pq)
|
||||||
|
// 定位音乐源
|
||||||
|
var source custom.Source
|
||||||
|
var active bool // 是否激活(自定义账号)
|
||||||
|
switch ps {
|
||||||
|
case sources.S_wy:
|
||||||
|
active = env.Config.Custom.Wy_Enable
|
||||||
|
source = custom.WySource
|
||||||
|
case sources.S_mg:
|
||||||
|
active = env.Config.Custom.Mg_Enable
|
||||||
|
source = custom.MgSource
|
||||||
|
case sources.S_kw:
|
||||||
|
active = env.Config.Custom.Kw_Enable
|
||||||
|
source = custom.KwSource
|
||||||
|
case sources.S_kg:
|
||||||
|
active = env.Config.Custom.Kg_Enable
|
||||||
|
source = custom.KgSource
|
||||||
|
case sources.S_tx:
|
||||||
|
active = env.Config.Custom.Tx_Enable
|
||||||
|
source = custom.TxSource
|
||||||
|
case sources.S_lx:
|
||||||
|
source = custom.LxSource
|
||||||
|
default:
|
||||||
|
out.Code = 6
|
||||||
|
out.Msg = ztool.Str_FastConcat(`无效源参数:'`, ps, `'`)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
if source == nil {
|
||||||
|
out.Code = 6
|
||||||
|
out.Msg = sources.ErrDisable
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
if !source.Vef(&pid) {
|
||||||
|
out.Code = 6
|
||||||
|
out.Msg = sources.E_VefMusicId
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
// 查询内存缓存
|
||||||
|
atomic.AddInt64(&accnum, 1)
|
||||||
|
cquery := strings.Join([]string{pm, ps, pid, pq}, `/`)
|
||||||
|
loger.Debug(`MemoGet: %v`, cquery)
|
||||||
|
if cdata, ok := env.Cache.Get(cquery); ok {
|
||||||
|
loger.Debug(`MemoHIT: %q`, cdata)
|
||||||
|
if cdata == `` {
|
||||||
|
out.Code = 2
|
||||||
|
out.Msg = memRej
|
||||||
|
} else {
|
||||||
|
out.Msg = memHIT
|
||||||
|
out.Data = cdata
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
// 定位源方法
|
||||||
|
switch pm {
|
||||||
|
case `url`, `link`:
|
||||||
|
// if !active && pq != sources.Q_128k {
|
||||||
|
// out.Msg = `未激活源仅可试听128k音质`
|
||||||
|
// return out
|
||||||
|
// }
|
||||||
|
// 查询文件缓存
|
||||||
|
var cstat bool
|
||||||
|
if caches.UseCache != nil {
|
||||||
|
cstat = caches.UseCache.Stat()
|
||||||
|
}
|
||||||
|
uquery := caches.NewQuery(ps, pid, pq)
|
||||||
|
uquery.Request = c.Request
|
||||||
|
defer uquery.Free()
|
||||||
|
if cstat {
|
||||||
|
loger.Debug(`FileGet: %v`, uquery.Query())
|
||||||
|
if olink := caches.UseCache.Get(uquery); olink != `` {
|
||||||
|
env.Cache.Set(cquery, olink, sources.C_lx)
|
||||||
|
out.Msg = cacheHIT
|
||||||
|
out.Data = olink
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 解析歌曲外链
|
||||||
|
atomic.AddInt64(&reqnum, 1)
|
||||||
|
out.Data, out.Msg = source.Url(pid, pq)
|
||||||
|
if out.Data != `` {
|
||||||
|
// 缓存并获取直链
|
||||||
|
atomic.AddInt64(&secnum, 1)
|
||||||
|
if out.Msg == `` {
|
||||||
|
if cstat && active {
|
||||||
|
loger.Debug(`FileSet: %v`, out.Data)
|
||||||
|
if link := caches.UseCache.Set(uquery, out.Data.(string)); link != `` {
|
||||||
|
env.Cache.Set(cquery, link, sources.C_lx)
|
||||||
|
out.Msg = cacheSet
|
||||||
|
out.Data = link
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
out.Msg = cacheFAIL
|
||||||
|
} else {
|
||||||
|
out.Msg = cacheMISS
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loger.Warn(`发生错误: %s`, out.Msg)
|
||||||
|
}
|
||||||
|
// 无法获取直链 直接返回原链接
|
||||||
|
env.Cache.Set(cquery, out.Data, source.Exp()-300)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
case `lrc`, `lyric`:
|
||||||
|
out.Data, out.Msg = source.Lrc(pid)
|
||||||
|
case `pic`, `cover`:
|
||||||
|
out.Data, out.Msg = source.Pic(pid)
|
||||||
|
default:
|
||||||
|
out.Code = 6
|
||||||
|
out.Msg = ztool.Str_FastConcat(`无效源方法:'`, pm, `'`)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
if out.Msg != `` {
|
||||||
|
out.Code = 2
|
||||||
|
env.Cache.Set(cquery, out.Data, 600)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
})
|
||||||
|
}
|
61
src/server/app_lxmusic.go
Normal file
61
src/server/app_lxmusic.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
//go:build extapp
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"lx-source/src/middleware/resp"
|
||||||
|
|
||||||
|
"github.com/ZxwyWebSite/ztool"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type queryObj struct {
|
||||||
|
Name string `json:"name"` // 歌名
|
||||||
|
Singer string `json:"singer"` // 歌手
|
||||||
|
Source string `json:"source"` // 平台
|
||||||
|
Songmid string `json:"songmid"` // 音乐ID
|
||||||
|
Interval string `json:"interval"` // 时长
|
||||||
|
AlbumName string `json:"albumName"` // 专辑
|
||||||
|
Img string `json:"img"` // 封面
|
||||||
|
// TypeURL struct {
|
||||||
|
// } `json:"typeUrl"` // 未知
|
||||||
|
AlbumID string `json:"albumId"` // 专辑ID
|
||||||
|
// 支持音质
|
||||||
|
Types []struct {
|
||||||
|
Type string `json:"type"` // 音质
|
||||||
|
Size string `json:"size"` // 大小
|
||||||
|
Hash string `json:"hash"` // 哈希(kg only)
|
||||||
|
} `json:"types"`
|
||||||
|
// tx
|
||||||
|
StrMediaMid string `json:"strMediaMid"` // 当前文件ID
|
||||||
|
AlbumMid string `json:"albumMid"` // 专辑ID
|
||||||
|
SongID int `json:"songId"` // 音乐ID
|
||||||
|
// mg
|
||||||
|
CopyrightID string `json:"copyrightId"` // 音乐ID
|
||||||
|
LrcURL string `json:"lrcUrl"` // lrc歌词
|
||||||
|
MrcURL string `json:"mrcUrl"` // mrc歌词
|
||||||
|
TrcURL string `json:"trcUrl"` // trc歌词
|
||||||
|
// kg
|
||||||
|
Hash string `json:"hash"` // 文件哈希(kg only)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadLxMusic(lx *gin.RouterGroup) {
|
||||||
|
// 获取链接
|
||||||
|
lx.POST(`/link/:q`, func(c *gin.Context) {
|
||||||
|
resp.Wrap(c, func() *resp.Resp {
|
||||||
|
var obj queryObj
|
||||||
|
if err := c.ShouldBindJSON(&obj); err != nil {
|
||||||
|
return &resp.Resp{Code: 6, Msg: `解析错误: ` + err.Error()}
|
||||||
|
}
|
||||||
|
pams := map[string]string{
|
||||||
|
`s`: obj.Source,
|
||||||
|
`id`: ztool.Str_Select(obj.Hash, obj.CopyrightID, obj.Songmid),
|
||||||
|
}
|
||||||
|
for k, v := range pams {
|
||||||
|
c.Params = append(c.Params, gin.Param{Key: k, Value: v})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}, linkHandler)
|
||||||
|
// lx.GET(`/info`)
|
||||||
|
}
|
36
src/server/app_musicfree.go
Normal file
36
src/server/app_musicfree.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
//go:build extapp
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"lx-source/src/middleware/util"
|
||||||
|
"lx-source/src/sources"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/ZxwyWebSite/ztool"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func loadMusicFree(mf *gin.RouterGroup) {
|
||||||
|
// 插件订阅
|
||||||
|
mf.GET(`/subscribe`, func(c *gin.Context) {
|
||||||
|
slist := sources.S_al
|
||||||
|
type plugins struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
}
|
||||||
|
length := len(slist)
|
||||||
|
plgs := make([]plugins, length)
|
||||||
|
url := ztool.Str_FastConcat(util.GetPath(c.Request, `app/`), `public/musicfree/`)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
name := `lxs-` + slist[i]
|
||||||
|
plgs[i] = plugins{
|
||||||
|
Name: name,
|
||||||
|
Url: ztool.Str_FastConcat(url, name, `.js`),
|
||||||
|
Version: `0.0.0`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, gin.H{`plugins`: plgs})
|
||||||
|
})
|
||||||
|
}
|
170
src/server/loadpublic.go
Normal file
170
src/server/loadpublic.go
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
// 静态资源
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"lx-source/src/env"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ZxwyWebSite/ztool"
|
||||||
|
"github.com/ZxwyWebSite/ztool/x/bytesconv"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gin-gonic/gin/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed public
|
||||||
|
var publicEM embed.FS // 打包默认Public目录 src/server/public
|
||||||
|
|
||||||
|
// 载入Public目录并设置路由
|
||||||
|
func loadPublic(r *gin.Engine) {
|
||||||
|
pf := env.Loger.NewGroup(`PublicFS`)
|
||||||
|
dir := ztool.Str_FastConcat(env.RunPath, `/data/public`)
|
||||||
|
publicFS, err := fs.Sub(publicEM, `public`)
|
||||||
|
var httpFS http.FileSystem = http.FS(publicFS)
|
||||||
|
if err != nil {
|
||||||
|
pf.Fatal(`内置Public目录载入错误: %s, 请尝试重新编译`, err)
|
||||||
|
}
|
||||||
|
if !ztool.Fbj_IsExists(dir) {
|
||||||
|
pf.Info(`不存在Public目录, 释放默认静态文件`)
|
||||||
|
walk := func(relPath string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(`无法获取[%q]的信息: %s`, relPath, err)
|
||||||
|
}
|
||||||
|
if !d.IsDir() {
|
||||||
|
out, err := ztool.Fbj_CreatFile(filepath.Join(dir, relPath))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(`无法创建文件[%q]: %s`, relPath, err)
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
pf.Debug(`导出 [%q]...`, relPath)
|
||||||
|
obj, err := publicFS.Open(relPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(`无法打开文件[%q]: %s`, relPath, err)
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(out, obj); err != nil {
|
||||||
|
return fmt.Errorf(`无法写入文件[%q]: %s`, relPath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := fs.WalkDir(publicFS, `.`, walk); err != nil {
|
||||||
|
pf.Error(`无法释放静态文件: %s`, err)
|
||||||
|
// pf.Warn(`正在使用内置Public目录, 将无法自定义静态文件`)
|
||||||
|
// httpFS = http.FS(publicFS)
|
||||||
|
} else {
|
||||||
|
pf.Info(`全部静态资源导出完成, 祝你使用愉快 ^_^`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pf.Free()
|
||||||
|
// 使用本地public目录
|
||||||
|
// httpFS = gin.Dir(dir, false)
|
||||||
|
// r.GET(`/:file`, func(c *gin.Context) {
|
||||||
|
// file := c.Param(`file`)
|
||||||
|
// switch file {
|
||||||
|
// case `favicon.ico`:
|
||||||
|
// c.FileFromFS(`icon.ico`, httpFS)
|
||||||
|
// // case `lx-custom-source.js`:
|
||||||
|
// // c.FileFromFS(`lx-custom-source.js`, http.FS(publicFS))
|
||||||
|
// default:
|
||||||
|
// c.FileFromFS(file, httpFS)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// 自动填写源脚本参数
|
||||||
|
if env.Config.Script.Auto > 0 {
|
||||||
|
file, _ := publicFS.Open(`lx-custom-source.js`)
|
||||||
|
data, _ := io.ReadAll(file)
|
||||||
|
file.Close()
|
||||||
|
data = bytes.Replace(data,
|
||||||
|
bytesconv.StringToBytes(`http://127.0.0.1:1011/`),
|
||||||
|
bytesconv.StringToBytes(env.Config.Cache.Local_Bind), 1,
|
||||||
|
)
|
||||||
|
if env.Config.Auth.ApiKey_Enable && env.Config.Script.Auto >= 2 {
|
||||||
|
data = bytes.Replace(data,
|
||||||
|
bytesconv.StringToBytes(`apipass = ''`),
|
||||||
|
bytesconv.StringToBytes(ztool.Str_FastConcat(
|
||||||
|
`apipass = '`, env.Config.Auth.ApiKey_Value, `'`,
|
||||||
|
)), 1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
r.GET(`/lx-custom-source.js`, func(c *gin.Context) {
|
||||||
|
var mime string
|
||||||
|
if _, ok := c.GetQuery(`raw`); ok {
|
||||||
|
mime = `application/octet-stream`
|
||||||
|
} else {
|
||||||
|
mime = `text/javascript; charset=utf-8`
|
||||||
|
}
|
||||||
|
c.Render(http.StatusOK, render.Data{
|
||||||
|
ContentType: mime,
|
||||||
|
Data: data,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
r.StaticFileFS(`/lx-custom-source.js`, `lx-custom-source.js`, httpFS)
|
||||||
|
}
|
||||||
|
// 新版源脚本
|
||||||
|
{
|
||||||
|
// 构建文件头
|
||||||
|
var b strings.Builder
|
||||||
|
b.Grow(75 +
|
||||||
|
len(env.Config.Script.Name) +
|
||||||
|
len(env.Config.Script.Descript) +
|
||||||
|
len(env.Config.Script.Version) +
|
||||||
|
len(env.Config.Script.Author) +
|
||||||
|
len(env.Config.Script.Homepage),
|
||||||
|
)
|
||||||
|
b.WriteString("/*!\n * @name ")
|
||||||
|
b.WriteString(env.Config.Script.Name)
|
||||||
|
b.WriteString("\n * @description ")
|
||||||
|
b.WriteString(env.Config.Script.Descript)
|
||||||
|
b.WriteString("\n * @version v")
|
||||||
|
b.WriteString(env.Config.Script.Version)
|
||||||
|
b.WriteString("\n * @author ")
|
||||||
|
b.WriteString(env.Config.Script.Author)
|
||||||
|
b.WriteString("\n * @homepage ")
|
||||||
|
b.WriteString(env.Config.Script.Homepage)
|
||||||
|
b.WriteString("\n */\n")
|
||||||
|
// 构建文件体
|
||||||
|
file, _ := publicFS.Open(`lx-source-script.js`)
|
||||||
|
data, _ := io.ReadAll(file)
|
||||||
|
file.Close()
|
||||||
|
r.GET(`/lx-source-script.js`, func(c *gin.Context) {
|
||||||
|
var mime string
|
||||||
|
if _, ok := c.GetQuery(`raw`); ok {
|
||||||
|
mime = `application/octet-stream`
|
||||||
|
} else {
|
||||||
|
mime = `text/javascript; charset=utf-8`
|
||||||
|
}
|
||||||
|
// 构建文件尾
|
||||||
|
var d strings.Builder
|
||||||
|
d.WriteString(`globalThis.ls={api:{addr:'`)
|
||||||
|
d.WriteString(env.Config.Cache.Local_Bind)
|
||||||
|
d.WriteString(`',pass:'`)
|
||||||
|
if env.Config.Auth.ApiKey_Enable {
|
||||||
|
if env.Config.Script.Auto >= 2 {
|
||||||
|
d.WriteString(env.Config.Auth.ApiKey_Value)
|
||||||
|
} else {
|
||||||
|
if key, ok := c.GetQuery(`key`); ok {
|
||||||
|
d.WriteString(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.WriteString(`'}};`)
|
||||||
|
d.WriteByte('\n')
|
||||||
|
// Render
|
||||||
|
c.Status(http.StatusOK)
|
||||||
|
c.Writer.Header()[`Content-Type`] = []string{mime}
|
||||||
|
c.Writer.WriteString(b.String())
|
||||||
|
c.Writer.WriteString(d.String())
|
||||||
|
c.Writer.Write(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
r.StaticFileFS(`/favicon.ico`, `lx-icon.ico`, httpFS)
|
||||||
|
r.StaticFileFS(`/status`, `status.html`, httpFS)
|
||||||
|
r.StaticFS(`/public`, httpFS)
|
||||||
|
}
|
63
src/server/loadquality.go
Normal file
63
src/server/loadquality.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"lx-source/src/env"
|
||||||
|
"lx-source/src/sources"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// 默认音质
|
||||||
|
defQuality = []string{`128k`, `320k`, `flac`, `flac24bit`}
|
||||||
|
// 试听音质
|
||||||
|
tstQuality = []string{`128k`}
|
||||||
|
// 标准音质
|
||||||
|
stdQuality = []string{`128k`, `320k`, `flac`}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 自动生成支持的音质表
|
||||||
|
func loadQMap() [][]string {
|
||||||
|
m := make([][]string, 6)
|
||||||
|
// 0.wy
|
||||||
|
if env.Config.Source.Enable_Wy {
|
||||||
|
if env.Config.Custom.Wy_Enable {
|
||||||
|
m[sources.I_wy] = defQuality
|
||||||
|
} else {
|
||||||
|
m[sources.I_wy] = tstQuality
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 1.mg
|
||||||
|
if env.Config.Source.Enable_Mg {
|
||||||
|
if env.Config.Custom.Mg_Enable {
|
||||||
|
m[sources.I_mg] = defQuality
|
||||||
|
} else {
|
||||||
|
m[sources.I_mg] = tstQuality
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 2.kw
|
||||||
|
if env.Config.Source.Enable_Kw {
|
||||||
|
if env.Config.Custom.Kw_Enable {
|
||||||
|
m[sources.I_kw] = stdQuality
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 3.kg
|
||||||
|
if env.Config.Source.Enable_Kg {
|
||||||
|
if env.Config.Custom.Kg_Enable {
|
||||||
|
m[sources.I_kg] = defQuality
|
||||||
|
} else {
|
||||||
|
m[sources.I_kg] = tstQuality
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 4.tx
|
||||||
|
if env.Config.Source.Enable_Tx {
|
||||||
|
if env.Config.Custom.Tx_Enable {
|
||||||
|
m[sources.I_tx] = stdQuality
|
||||||
|
} else {
|
||||||
|
m[sources.I_tx] = tstQuality
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 5.lx
|
||||||
|
if env.Config.Source.Enable_Lx {
|
||||||
|
m[sources.I_lx] = defQuality
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 162 KiB |
@ -3,23 +3,19 @@
|
|||||||
* @description Client
|
* @description Client
|
||||||
* version 1.0.1
|
* version 1.0.1
|
||||||
* @author Zxwy
|
* @author Zxwy
|
||||||
* @homepage https://github.com/ZxwyWebSite/lx-source
|
* homepage null
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// 脚本配置
|
// 脚本配置
|
||||||
const version = '1.0.3' // 脚本版本
|
const version = '1.0.3' // 脚本版本
|
||||||
const apiaddr = 'http://127.0.0.1:1011/' // 服务端地址,末尾加斜杠
|
const apiaddr = 'http://127.0.0.1:1011/' // 服务端地址,末尾加斜杠
|
||||||
const apipass = '' // 验证密钥,由服务端自动生成 '${apipass}'
|
const apipass = '' // 验证密钥,填在单引号内
|
||||||
const devmode = true // 调试模式
|
const devmode = true // 调试模式
|
||||||
|
// const timeout = 60 * 1000 // 请求超时(ms)
|
||||||
|
|
||||||
// 常量 & 默认值
|
// 常量 & 默认值
|
||||||
const { EVENT_NAMES, request, on, send } = window.lx ?? globalThis.lx
|
const { EVENT_NAMES, request, on, send } = globalThis.lx
|
||||||
const defs = { type: 'music', actions: ['musicUrl'] }
|
const 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 = {
|
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',
|
'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, */*',
|
'Accept': 'application/json, text/plain, */*',
|
||||||
@ -39,6 +35,7 @@ const conf = {
|
|||||||
|
|
||||||
const httpRequest = (url, options) => new Promise((resolve, reject) => {
|
const httpRequest = (url, options) => new Promise((resolve, reject) => {
|
||||||
options.headers = { ...defheaders, ...options.headers } // 添加默认请求头
|
options.headers = { ...defheaders, ...options.headers } // 添加默认请求头
|
||||||
|
// options.timeout ? options.timeout : timeout // 添加默认请求超时
|
||||||
request(url, options, (err, resp) => {
|
request(url, options, (err, resp) => {
|
||||||
if (err) return reject(err)
|
if (err) return reject(err)
|
||||||
resolve(resp.body)
|
resolve(resp.body)
|
||||||
@ -48,8 +45,8 @@ const httpRequest = (url, options) => new Promise((resolve, reject) => {
|
|||||||
const musicUrl = async (source, info, quality) => {
|
const musicUrl = async (source, info, quality) => {
|
||||||
const start = new Date().getTime();
|
const start = new Date().getTime();
|
||||||
const id = info.hash ?? info.copyrightId ?? info.songmid // 音乐id kg源为hash, mg源为copyrightId
|
const id = info.hash ?? info.copyrightId ?? info.songmid // 音乐id kg源为hash, mg源为copyrightId
|
||||||
const ext = source == 'kg' ? info.albumId : '' //source == 'tx' ? info.strMediaMid
|
//const ext = source == 'kg' ? info.albumId : '' //source == 'tx' ? info.strMediaMid
|
||||||
const query = `${source}/${id}${(ext != '' && ext != void 0) ? '-' + ext : ''}/${quality}`
|
const query = `${source}/${id}/${quality}` //${(ext != '' && ext != void 0) ? '-' + ext : ''}
|
||||||
console.log('创建任务: %s, 音乐信息: %O', query, info)
|
console.log('创建任务: %s, 音乐信息: %O', query, info)
|
||||||
const body = await httpRequest(`${apiaddr}link/${query}`, { method: 'get' });
|
const body = await httpRequest(`${apiaddr}link/${query}`, { method: 'get' });
|
||||||
console.log('返回数据: %O', body, `, 耗时 ${new Date().getTime() - start} ms`)
|
console.log('返回数据: %O', body, `, 耗时 ${new Date().getTime() - start} ms`)
|
||||||
@ -80,8 +77,7 @@ const init = () => {
|
|||||||
'use strict';
|
'use strict';
|
||||||
console.log('初始化脚本, 版本: %s, 服务端地址: %s', version, apiaddr)
|
console.log('初始化脚本, 版本: %s, 服务端地址: %s', version, apiaddr)
|
||||||
var stat = false; var msg = ''; var updUrl = ''; var sourcess = {}
|
var stat = false; var msg = ''; var updUrl = ''; var sourcess = {}
|
||||||
httpRequest(apiaddr, { method: 'get' })
|
httpRequest(apiaddr, { method: 'get', timeout: 1000 * 10 })
|
||||||
.catch((err) => { msg = '初始化失败: ' + err ?? '连接服务端超时'; console.log(msg) })
|
|
||||||
.then((body) => {
|
.then((body) => {
|
||||||
if (!body) { msg = '初始化失败:' + '无返回数据'; return }
|
if (!body) { msg = '初始化失败:' + '无返回数据'; return }
|
||||||
console.log('获取服务端数据成功: %o, 版本: %s', body, body.version)
|
console.log('获取服务端数据成功: %o, 版本: %s', body, body.version)
|
||||||
@ -104,7 +100,7 @@ const init = () => {
|
|||||||
if (source[v] != null /*== true*/) {
|
if (source[v] != null /*== true*/) {
|
||||||
sourcess[v] = {
|
sourcess[v] = {
|
||||||
name: v,
|
name: v,
|
||||||
...defs, // ...defaults,
|
...defs,
|
||||||
qualitys: source[v], // 支持返回音质时启用 使用后端音质表
|
qualitys: source[v], // 支持返回音质时启用 使用后端音质表
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,18 +108,13 @@ const init = () => {
|
|||||||
// 完成初始化
|
// 完成初始化
|
||||||
stat = true
|
stat = true
|
||||||
})
|
})
|
||||||
|
.catch((err) => { msg = '初始化失败: ' + err ?? '连接服务端超时'; console.log(msg) })
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
// 脚本初始化完成后需要发送inited事件告知应用
|
// 脚本初始化完成后需要发送inited事件告知应用
|
||||||
send(EVENT_NAMES.inited, {
|
send(EVENT_NAMES.inited, {
|
||||||
status: stat, // 初始化成功 or 失败 (初始化失败不打开控制台, 使用更新提示接口返回信息)
|
status: stat, // 初始化成功 or 失败 (初始化失败不打开控制台, 使用更新提示接口返回信息)
|
||||||
openDevTools: stat ? devmode : false, // 是否打开开发者工具,方便用于调试脚本 'devmode' or 'stat ? devmode : false'
|
openDevTools: stat ? devmode : false, // 是否打开开发者工具,方便用于调试脚本 'devmode' or 'stat ? devmode : false'
|
||||||
sources: sourcess, // 使用服务端源列表
|
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 : '' })
|
if (msg) send(EVENT_NAMES.updateAlert, { log: '提示:' + msg, updateUrl: updUrl ? apiaddr + updUrl : '' })
|
BIN
src/server/public/lx-icon.ico
Normal file
BIN
src/server/public/lx-icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 162 KiB |
2
src/server/public/lx-source-script.js
Normal file
2
src/server/public/lx-source-script.js
Normal file
File diff suppressed because one or more lines are too long
126
src/server/public/status.html
Normal file
126
src/server/public/status.html
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||||
|
<title>LX Source API</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>LX Source API</h1>
|
||||||
|
当你看到这个页面时,服务已经成功跑起来了~
|
||||||
|
<h2>示例:</h2>
|
||||||
|
<script>
|
||||||
|
const api = './';
|
||||||
|
function l(s, id, q) {
|
||||||
|
let url = `${api}link/${s}/${id[Math.floor(Math.random() * id.length)]}/${q}`;
|
||||||
|
const key = localStorage.getItem('apipass'); if (key) url += `?key=${encodeURIComponent(key)}`;
|
||||||
|
fetch(url)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((res) => {
|
||||||
|
if (res.code != 0 || res.data == '') throw res.msg;
|
||||||
|
window.open(res.data);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
alert(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<ul>
|
||||||
|
<li>0. <a href="./lx-custom-source.js?raw">获取源脚本</a></li>
|
||||||
|
<li>1. <a onclick="l('wy',['473120817','434022356','2112290949','27646205'],'320k')">WY小芸源</a></li>
|
||||||
|
<li>2. <a onclick="l('mg',['6005970X6D1','63278105706','60054702010'],'320k')">MG小蜜源</a></li>
|
||||||
|
<li>3. <a onclick="l('kw',['236891','26428340','133360','112051'],'320k')">KW小蜗源</a></li>
|
||||||
|
<li>4. <a onclick="l('kg',['A90B30D3F51C56841D3FE51D5C5B09A8'],'128k')">KG小枸源</a></li>
|
||||||
|
<li>5. <a onclick="l('tx',['002Le9vl2JlYDK','0013Xlvm0ZKNnA'],'128k')">TX小秋源</a></li>
|
||||||
|
</ul>
|
||||||
|
<h2>状态:</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
已持续运行
|
||||||
|
<strong id="lxs_run_d">20</strong> 天
|
||||||
|
<strong id="lxs_run_h">21</strong> 时
|
||||||
|
<strong id="lxs_run_m">10</strong> 分
|
||||||
|
<strong id="lxs_run_s">08</strong> 秒
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
调用:
|
||||||
|
<strong id="lxs_sum_acc">0</strong>
|
||||||
|
/解析:
|
||||||
|
<strong id="lxs_sum_req">0</strong>
|
||||||
|
/成功:
|
||||||
|
<strong id="lxs_sum_sec">0</strong>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
版本号:
|
||||||
|
<strong id="lxs_ver">dev</strong>
|
||||||
|
</li>
|
||||||
|
<li><strong id="lxs_err"></strong></li>
|
||||||
|
</ul>
|
||||||
|
<script>
|
||||||
|
fetch(api)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((res) => {
|
||||||
|
document.getElementById("lxs_sum_acc").innerText = res.summary.Accessn;
|
||||||
|
document.getElementById("lxs_sum_req").innerText = res.summary.Request;
|
||||||
|
document.getElementById("lxs_sum_sec").innerText = res.summary.Success;
|
||||||
|
const startTime = new Date(res.summary.StartAt * 1000);
|
||||||
|
const updateTimer = () => {
|
||||||
|
const elapsed = new Date() - startTime;
|
||||||
|
document.getElementById("lxs_run_d").innerText = String(Math.floor(elapsed / 864e5)).padStart(2, "0");
|
||||||
|
document.getElementById("lxs_run_h").innerText = String(Math.floor((elapsed % 864e5) / 36e5)).padStart(2, "0");
|
||||||
|
document.getElementById("lxs_run_m").innerText = String(Math.floor((elapsed % 36e5) / 6e4)).padStart(2, "0");
|
||||||
|
document.getElementById("lxs_run_s").innerText = String(Math.floor((elapsed % 6e4) / 1000)).padStart(2, "0");
|
||||||
|
};
|
||||||
|
updateTimer(), setInterval(updateTimer, 1e3);
|
||||||
|
document.getElementById("lxs_ver").innerText = res.version;
|
||||||
|
if (res.auth.apikey && !localStorage.getItem("apipass")) {
|
||||||
|
const key = prompt("请填写API密钥"); if (key) localStorage.setItem("apipass", key);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
document.getElementById("lxs_err").innerText = err;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: #ffffff;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 30px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
color: rgb(100, 100, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #42b983;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul,
|
||||||
|
li {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin-left: -40px;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
192
src/server/router.go
Normal file
192
src/server/router.go
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"lx-source/src/env"
|
||||||
|
"lx-source/src/middleware/dynlink"
|
||||||
|
"lx-source/src/sources"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-contrib/cors"
|
||||||
|
"github.com/gin-contrib/gzip"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
accnum int64
|
||||||
|
reqnum int64
|
||||||
|
secnum int64
|
||||||
|
)
|
||||||
|
|
||||||
|
// 载入路由
|
||||||
|
func InitRouter() *gin.Engine {
|
||||||
|
r := gin.Default()
|
||||||
|
qmap := loadQMap()
|
||||||
|
// Gzip压缩
|
||||||
|
if env.Config.Main.Gzip {
|
||||||
|
r.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{"/file/"})))
|
||||||
|
}
|
||||||
|
// Cors跨域
|
||||||
|
if env.Config.Main.Cors {
|
||||||
|
r.Use(cors.Default())
|
||||||
|
}
|
||||||
|
startime := time.Now().Unix()
|
||||||
|
// 源信息
|
||||||
|
r.GET(`/`, func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
`version`: env.Version, // 服务端程序版本
|
||||||
|
`name`: `lx-music-source`, // 名称
|
||||||
|
`msg`: `Hello~::^-^::~v1~`, // Api大版本
|
||||||
|
// `developer`: []string{`Zxwy`}, // 开发者列表, 可在保留原作者的基础上添加你自己的名字?
|
||||||
|
// 仓库地址
|
||||||
|
// `github`: `https://github.com/ZxwyWebSite/lx-source`,
|
||||||
|
// 可用平台
|
||||||
|
`source`: gin.H{
|
||||||
|
sources.S_wy: qmap[sources.I_wy],
|
||||||
|
sources.S_mg: qmap[sources.I_mg],
|
||||||
|
sources.S_kw: qmap[sources.I_kw],
|
||||||
|
sources.S_kg: qmap[sources.I_kg],
|
||||||
|
sources.S_tx: qmap[sources.I_tx],
|
||||||
|
sources.S_lx: qmap[sources.I_lx],
|
||||||
|
},
|
||||||
|
// 自定义源脚本更新
|
||||||
|
`script`: env.Config.Script.Update, //env.Config.Script,
|
||||||
|
// 数据统计
|
||||||
|
`summary`: gin.H{
|
||||||
|
`StartAt`: startime, // 启动时间
|
||||||
|
`Accessn`: accnum, // 访问次数
|
||||||
|
`Request`: reqnum, // 解析次数
|
||||||
|
`Success`: secnum, // 成功次数
|
||||||
|
},
|
||||||
|
// 验证方式
|
||||||
|
`auth`: gin.H{
|
||||||
|
`apikey`: env.Config.Auth.ApiKey_Enable,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// 静态文件
|
||||||
|
loadPublic(r)
|
||||||
|
// r.StaticFile(`/favicon.ico`, `public/icon.ico`)
|
||||||
|
// r.StaticFile(`/lx-custom-source.js`, `public/lx-custom-source.js`)
|
||||||
|
// 解析接口
|
||||||
|
loadMusic(r)
|
||||||
|
// r.GET(`/link/:s/:id/:q`, auth.InitHandler(linkHandler)...)
|
||||||
|
dynlink.LoadHandler(r)
|
||||||
|
// 动态链?
|
||||||
|
// r.GET(`/file/:t/:x/:f`, dynlink.FileHandler())
|
||||||
|
// if cache, ok := caches.UseCache.(*localcache.Cache); ok {
|
||||||
|
// r.Static(`/file`, cache.Path)
|
||||||
|
// }
|
||||||
|
// if env.Config.Cache.Mode == `local` {
|
||||||
|
// r.Static(`/file`, env.Config.Cache.Local_Path)
|
||||||
|
// }
|
||||||
|
// 功能接口
|
||||||
|
// api := r.Group(`/api`)
|
||||||
|
// {
|
||||||
|
// api.GET(`/:s/:m/:q`) // {source}/{method}/{query}
|
||||||
|
// }
|
||||||
|
// 软件接口
|
||||||
|
// app := r.Group(`/app`)
|
||||||
|
// {
|
||||||
|
// loadLxMusic(app.Group(`/lxmusic`))
|
||||||
|
// loadMusicFree(app.Group(`/musicfree`))
|
||||||
|
// }
|
||||||
|
// 数据接口
|
||||||
|
// r.GET(`/file/:t/:hq/:n`, func(c *gin.Context) {
|
||||||
|
// c.String(http.StatusOK, time.Now().Format(`20060102150405`))
|
||||||
|
// })
|
||||||
|
// 暂不对文件接口进行验证 脚本返回链接无法附加请求头 只可在Get添加Query
|
||||||
|
// g := r.Group(``)
|
||||||
|
// {
|
||||||
|
// g.Use(authHandler)
|
||||||
|
// g.GET(`/link/:s/:id/:q`, linkHandler)
|
||||||
|
// g.Static(`/file`, LocalCachePath)
|
||||||
|
// }
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据返回格式
|
||||||
|
const (
|
||||||
|
cacheHIT = `Cache HIT` // 缓存已命中
|
||||||
|
cacheMISS = `Cache MISS` // 缓存未命中
|
||||||
|
cacheSet = `Cache Seted` // 缓存已设置
|
||||||
|
cacheFAIL = `Cache FAIL` // 缓存未成功
|
||||||
|
|
||||||
|
memHIT = `Memory HIT` // 内存已命中
|
||||||
|
memRej = `Memory Reject` // 内存已拒绝
|
||||||
|
)
|
||||||
|
|
||||||
|
// 外链解析
|
||||||
|
// func linkHandler(c *gin.Context) {
|
||||||
|
// resp.Wrap(c, func() *resp.Resp {
|
||||||
|
// // 获取传入参数 检查合法性
|
||||||
|
// arr := util.ParaArr(c, `s`, `id`, `q`)
|
||||||
|
// s, id, q := arr[0], arr[1], arr[2]
|
||||||
|
// // parms := util.ParaMap(c)
|
||||||
|
// // getParam := func(p string) string { return strings.TrimSuffix(strings.TrimPrefix(c.Param(p), `/`), `/`) } //strings.Trim(c.Param(p), `/`)
|
||||||
|
// // s := parms[`s`] //c.Param(`s`) //getParam(`s`) // source 平台 wy, mg, kw
|
||||||
|
// // id := parms[`id`] //c.Param(`id`) //getParam(`id`) // sid 音乐ID wy: songmid, mg: copyrightId
|
||||||
|
// // q := parms[`q`] //c.Param(`q`) //getParam(`q`) // quality 音质 128k / 320k / flac / flac24bit
|
||||||
|
// env.Loger.NewGroup(`LinkQuery`).Debug(`s: %v, id: %v, q: %v`, s, id, q).Free()
|
||||||
|
// if ztool.Chk_IsNilStr(s, q, id) {
|
||||||
|
// return &resp.Resp{Code: 6, Msg: `参数不全`} // http.StatusBadRequest
|
||||||
|
// }
|
||||||
|
// cquery := caches.NewQuery(s, id, q)
|
||||||
|
// cquery.Request = c.Request
|
||||||
|
// // fmt.Printf("%+v\n", cquery)
|
||||||
|
// defer cquery.Free()
|
||||||
|
// // _, ok := sources.UseSource.Verify(cquery) // 获取请求音质 同时检测是否支持(如kw源没有flac24bit) qualitys[q][s]rquery
|
||||||
|
// // if !ok {
|
||||||
|
// // return &resp.Resp{Code: 6, Msg: `不支持的平台或音质`}
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// // 查询内存
|
||||||
|
// if clink, ok := env.Cache.Get(cquery.Query()); ok {
|
||||||
|
// if str, ok := clink.(string); ok {
|
||||||
|
// env.Loger.NewGroup(`MemCache`).Debug(`MemHIT [%q]=>[%q]`, cquery.Query(), str).Free()
|
||||||
|
// if str == `` {
|
||||||
|
// return &resp.Resp{Code: 2, Msg: memRej} // 拒绝请求,当前一段时间内解析出错 `MemCache Reject`
|
||||||
|
// }
|
||||||
|
// return &resp.Resp{Msg: memHIT, Data: str} // `MemCache HIT`
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// // 查询缓存
|
||||||
|
// var cstat bool
|
||||||
|
// if caches.UseCache != nil {
|
||||||
|
// cstat = caches.UseCache.Stat()
|
||||||
|
// }
|
||||||
|
// sc := env.Loger.NewGroup(`StatCache`)
|
||||||
|
// defer sc.Free()
|
||||||
|
// if cstat {
|
||||||
|
// sc.Debug(`Method: Get, Query: %v`, cquery.Query())
|
||||||
|
// if link := caches.UseCache.Get(cquery); link != `` {
|
||||||
|
// env.Cache.Set(cquery.Query(), link, 3600)
|
||||||
|
// return &resp.Resp{Msg: cacheHIT, Data: link}
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// sc.Debug(`Disabled`)
|
||||||
|
// }
|
||||||
|
// atomic.AddInt64(&reqnum, 1)
|
||||||
|
// // 解析歌曲外链
|
||||||
|
// outlink, emsg := sources.UseSource.GetLink(cquery)
|
||||||
|
// if emsg != `` {
|
||||||
|
// if emsg == sources.Err_Verify { // Verify Failed: 不支持的平台或音质
|
||||||
|
// return &resp.Resp{Code: 6, Msg: ztool.Str_FastConcat(emsg, `: 不支持的平台或音质`)}
|
||||||
|
// }
|
||||||
|
// env.Cache.Set(cquery.Query(), outlink, 600) // 发生错误的10分钟内禁止再次查询
|
||||||
|
// return &resp.Resp{Code: 2, Msg: emsg, Data: outlink}
|
||||||
|
// }
|
||||||
|
// atomic.AddInt64(&secnum, 1)
|
||||||
|
// // 缓存并获取直链 !(s == `kg` || (s == `tx` && !tx_en)) => (s != `kg` && (s != `tx` || tx_en))
|
||||||
|
// if outlink != `` && cstat && cquery.Source != sources.S_kg && (cquery.Source != sources.S_tx || env.Config.Custom.Tx_Enable) {
|
||||||
|
// sc.Debug(`Method: Set, Link: %v`, outlink)
|
||||||
|
// if link := caches.UseCache.Set(cquery, outlink); link != `` {
|
||||||
|
// env.Cache.Set(cquery.Query(), link, 3600)
|
||||||
|
// return &resp.Resp{Msg: cacheSet, Data: link}
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// // 无法获取直链 直接返回原链接
|
||||||
|
// env.Cache.Set(cquery.Query(), outlink, 1200)
|
||||||
|
// return &resp.Resp{Msg: cacheMISS, Data: outlink}
|
||||||
|
// })
|
||||||
|
// }
|
@ -1,239 +1,288 @@
|
|||||||
// 内置解析源
|
// 内置解析源
|
||||||
package builtin
|
package builtin
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"lx-source/src/caches"
|
// "lx-source/src/caches"
|
||||||
"lx-source/src/env"
|
// "lx-source/src/env"
|
||||||
"lx-source/src/sources"
|
// "lx-source/src/sources"
|
||||||
"lx-source/src/sources/custom/kw"
|
// "lx-source/src/sources/custom/kg"
|
||||||
"lx-source/src/sources/custom/tx"
|
// "lx-source/src/sources/custom/kw"
|
||||||
"net/http"
|
// "lx-source/src/sources/custom/mg"
|
||||||
"strconv"
|
// "lx-source/src/sources/custom/tx"
|
||||||
"sync"
|
// "lx-source/src/sources/custom/wy"
|
||||||
"time"
|
// wm "lx-source/src/sources/custom/wy/modules"
|
||||||
|
// "lx-source/src/sources/example"
|
||||||
|
// "net/http"
|
||||||
|
// "strconv"
|
||||||
|
// "sync"
|
||||||
|
// "time"
|
||||||
|
|
||||||
"github.com/ZxwyWebSite/ztool"
|
// "github.com/ZxwyWebSite/ztool"
|
||||||
)
|
// )
|
||||||
|
|
||||||
type Source struct{}
|
// type Source struct{}
|
||||||
|
|
||||||
// 预检 (兼容旧接口)
|
// // 预检 (兼容旧接口)
|
||||||
func (s *Source) Verify(c *caches.Query) (rquery string, ok bool) {
|
// func (s *Source) Verify(c *caches.Query) (rquery string, ok bool) {
|
||||||
rquery, ok = qualitys[c.Quality][c.Source]
|
// rquery, ok = qualitys[c.Quality][c.Source]
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
var (
|
// var (
|
||||||
// 并发对象池 (用户限制在Router处实现)
|
// // 并发对象池 (用户限制在Router处实现)
|
||||||
wy_pool = &sync.Pool{New: func() any { return new(WyApi_Song) }}
|
// wy_pool *sync.Pool
|
||||||
mg_pool = &sync.Pool{New: func() any { return new(MgApi_Song) }}
|
// mg_pool = &sync.Pool{New: func() any { return new(MgApi_Song) }}
|
||||||
// kw_pool = &sync.Pool{New: func() any { return new(KwApi_Song) }}
|
// // kw_pool = &sync.Pool{New: func() any { return new(KwApi_Song) }}
|
||||||
kg_pool = &sync.Pool{New: func() any { return new(KgApi_Song) }}
|
// // kg_pool = &sync.Pool{New: func() any { return new(KgApi_Song) }}
|
||||||
// tx_pool = &sync.Pool{New: func() any { return new(res_tx) }}
|
// // tx_pool = &sync.Pool{New: func() any { return new(res_tx) }}
|
||||||
wv_pool *sync.Pool
|
// wv_pool *sync.Pool
|
||||||
)
|
// )
|
||||||
|
|
||||||
func init() {
|
// func init() {
|
||||||
env.Inits.Add(func() {
|
// env.Inits.Add(func() {
|
||||||
if env.Config.Source.MusicIdVerify {
|
// if env.Config.Source.Enable_Wy {
|
||||||
wv_pool = &sync.Pool{New: func() any { return new(WyApi_Vef) }}
|
// wy_pool = &sync.Pool{New: func() any { return new(wm.PlayInfo) }}
|
||||||
}
|
// if env.Config.Source.MusicIdVerify {
|
||||||
})
|
// wv_pool = &sync.Pool{New: func() any { return new(wm.VerifyInfo) }}
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
// 查询
|
// // 查询
|
||||||
func (s *Source) GetLink(c *caches.Query) (outlink string, msg string) {
|
// func (s *Source) GetLink(c *caches.Query) (outlink string, msg string) {
|
||||||
rquery, ok := s.Verify(c)
|
// rquery, ok := s.Verify(c)
|
||||||
if !ok /*&& c.Source != `tx`*/ {
|
// if !ok /*&& c.Source != `tx`*/ {
|
||||||
msg = sources.Err_Verify //`Verify Failed`
|
// msg = sources.Err_Verify //`Verify Failed`
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
// var outlink string
|
// // var outlink string
|
||||||
jx := env.Loger.NewGroup(`Sources`) //sources.Loger.AppGroup(`builtin`) //env.Loger.NewGroup(`JieXiApis`)
|
// jx := env.Loger.NewGroup(`Sources`) //sources.Loger.AppGroup(`builtin`) //env.Loger.NewGroup(`JieXiApis`)
|
||||||
switch c.Source {
|
// defer jx.Free()
|
||||||
case sources.S_wy:
|
// switch c.Source {
|
||||||
if !env.Config.Custom.Wy_Enable {
|
// case sources.S_wy:
|
||||||
msg = sources.ErrDisable
|
// if !env.Config.Source.Enable_Wy {
|
||||||
return
|
// msg = sources.ErrDisable
|
||||||
}
|
// return
|
||||||
// 可用性验证
|
// }
|
||||||
if env.Config.Source.MusicIdVerify {
|
// if wy.Url != nil {
|
||||||
vef := wv_pool.Get().(*WyApi_Vef)
|
// outlink, msg = wy.Url(c.MusicID, c.Quality)
|
||||||
defer wv_pool.Put(vef)
|
// break
|
||||||
vurl := ztool.Str_FastConcat(`https://`, vef_wy, `&id=`, c.MusicID)
|
// }
|
||||||
_, err := ztool.Net_HttpReq(http.MethodGet, vurl, nil, header_wy, &vef)
|
// // 可用性验证
|
||||||
if err != nil {
|
// if env.Config.Source.MusicIdVerify {
|
||||||
jx.Error(`Wy, VefReq: %s`, err)
|
// vef := wv_pool.Get().(*wm.VerifyInfo)
|
||||||
msg = sources.ErrHttpReq
|
// defer wv_pool.Put(vef)
|
||||||
return
|
// vurl := ztool.Str_FastConcat(`https://`, example.Vef_wy, `&id=`, c.MusicID)
|
||||||
}
|
// _, err := ztool.Net_HttpReq(http.MethodGet, vurl, nil, example.Header_wy, &vef)
|
||||||
jx.Debug(`Wy, Vef: %+v`, vef)
|
// if err != nil {
|
||||||
if vef.Code != 200 || !vef.Success {
|
// jx.Error(`Wy, VefReq: %s`, err)
|
||||||
msg = ztool.Str_FastConcat(`暂不可用:`, vef.Message)
|
// msg = sources.ErrHttpReq
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
}
|
// jx.Debug(`Wy, Vef: %+v`, vef)
|
||||||
// 获取外链
|
// if vef.Code != 200 || !vef.Success {
|
||||||
resp := wy_pool.Get().(*WyApi_Song)
|
// msg = ztool.Str_FastConcat(`暂不可用:`, vef.Message)
|
||||||
defer wy_pool.Put(resp)
|
// return
|
||||||
// 分流逻辑 (暂无其它节点)
|
// }
|
||||||
// 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),
|
// resp := wy_pool.Get().(*wm.PlayInfo)
|
||||||
// }
|
// defer wy_pool.Put(resp)
|
||||||
// url := urls[rand.Intn(len(urls))]
|
// // 分流逻辑 (暂无其它节点)
|
||||||
url := ztool.Str_FastConcat(
|
// // urls := [...]string{
|
||||||
`https://`, api_wy, `&id=`, c.MusicID, `&level=`, rquery,
|
// // ztool.Str_FastConcat(`http://`, api_wy, `?id=`, c.MusicID, `&level=`, rquery, `&noCookie=true`),
|
||||||
`×tamp=`, strconv.FormatInt(time.Now().UnixMilli(), 10),
|
// // ztool.Str_FastConcat(`https://`, api_wy, `&id=`, c.MusicID, `&level=`, rquery, `&encodeType=`, c.Extname),
|
||||||
)
|
// // }
|
||||||
// jx.Debug(`Wy, Url: %v`, url)
|
// // url := urls[rand.Intn(len(urls))]
|
||||||
// wy源增加后端重试 默认3次
|
// url := ztool.Str_FastConcat(
|
||||||
for i := 0; true; i++ {
|
// `https://`, example.Api_wy, `&id=`, c.MusicID, `&level=`, rquery,
|
||||||
_, err := ztool.Net_HttpReq(http.MethodGet, url, nil, header_wy, &resp)
|
// `×tamp=`, strconv.FormatInt(time.Now().UnixMilli(), 10),
|
||||||
if err != nil {
|
// )
|
||||||
jx.Error(`HttpReq, Err: %s, ReTry: %v`, err, i)
|
// // jx.Debug(`Wy, Url: %v`, url)
|
||||||
if i > 3 {
|
// // wy源增加后端重试 默认3次
|
||||||
jx.Error(`Wy, HttpReq: %s`, err)
|
// for i := 0; true; i++ {
|
||||||
msg = sources.ErrHttpReq
|
// _, err := ztool.Net_HttpReq(http.MethodGet, url, nil, example.Header_wy, &resp)
|
||||||
return
|
// if err != nil {
|
||||||
}
|
// jx.Error(`HttpReq, Err: %s, ReTry: %v`, err, i)
|
||||||
time.Sleep(time.Second)
|
// if i > 3 {
|
||||||
continue
|
// jx.Error(`Wy, HttpReq: %s`, err)
|
||||||
}
|
// msg = sources.ErrHttpReq
|
||||||
break
|
// return
|
||||||
}
|
// }
|
||||||
jx.Debug(`Wy, Resp: %+v`, resp)
|
// time.Sleep(time.Second)
|
||||||
if len(resp.Data) == 0 {
|
// continue
|
||||||
msg = `No Data:Api接口忙,请稍后重试`
|
// }
|
||||||
return
|
// break
|
||||||
}
|
// }
|
||||||
var data = resp.Data[0]
|
// jx.Debug(`Wy, Resp: %+v`, resp)
|
||||||
if data.Code != 200 || data.FreeTrialInfo != nil {
|
// if len(resp.Data) == 0 {
|
||||||
// jx.Error("发生错误, 返回数据:\n%#v", resp)
|
// msg = `No Data:Api接口忙,请稍后重试`
|
||||||
msg = `触发风控或专辑单独收费`
|
// return
|
||||||
return
|
// }
|
||||||
}
|
// var data = resp.Data[0]
|
||||||
if data.Level != rquery {
|
// if data.Code != 200 || data.FreeTrialInfo != nil {
|
||||||
msg = ztool.Str_FastConcat(`实际音质不匹配: `, rquery, ` <= `, data.Level) // 实际音质不匹配: exhigh <= standard
|
// // jx.Error("发生错误, 返回数据:\n%#v", resp)
|
||||||
return
|
// msg = `触发风控或专辑单独收费: ` + strconv.Itoa(data.Code)
|
||||||
}
|
// return
|
||||||
// jx.Info(`WyLink, RealQuality: %v`, data.Level)
|
// }
|
||||||
outlink = data.URL
|
// if data.Level != rquery {
|
||||||
case sources.S_mg:
|
// msg = ztool.Str_FastConcat(`实际音质不匹配: `, rquery, ` <= `, data.Level) // 实际音质不匹配: exhigh <= standard
|
||||||
resp := mg_pool.Get().(*MgApi_Song)
|
// if !env.Config.Source.ForceFallback {
|
||||||
defer mg_pool.Put(resp)
|
// return
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// // jx.Info(`WyLink, RealQuality: %v`, data.Level)
|
||||||
|
// outlink = data.URL
|
||||||
|
// case sources.S_mg:
|
||||||
|
// if !env.Config.Source.Enable_Mg {
|
||||||
|
// msg = sources.ErrDisable
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// if len(c.MusicID) != 11 {
|
||||||
|
// msg = sources.E_VefMusicId
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// if mg.Url != nil {
|
||||||
|
// outlink, msg = mg.Url(c.MusicID, c.Quality)
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// resp := mg_pool.Get().(*MgApi_Song)
|
||||||
|
// defer mg_pool.Put(resp)
|
||||||
|
|
||||||
url := ztool.Str_FastConcat(`https://`, api_mg, `?copyrightId=`, c.MusicID, `&type=`, rquery)
|
// url := ztool.Str_FastConcat(`https://`, example.Api_mg, `?copyrightId=`, c.MusicID, `&type=`, rquery)
|
||||||
// jx.Debug(`Mg, Url: %v`, url)
|
// // jx.Debug(`Mg, Url: %v`, url)
|
||||||
_, err := ztool.Net_HttpReq(http.MethodGet, url, nil, header_mg, &resp)
|
// _, err := ztool.Net_HttpReq(http.MethodGet, url, nil, example.Header_mg, &resp)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
jx.Error(`Mg, HttpReq: %s`, err)
|
// jx.Error(`Mg, HttpReq: %s`, err)
|
||||||
msg = sources.ErrHttpReq
|
// msg = sources.ErrHttpReq
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
jx.Debug(`Mg, Resp: %+v`, resp)
|
// jx.Debug(`Mg, Resp: %+v`, resp)
|
||||||
if link := resp.Data.PlayURL; link != `` {
|
// if link := resp.Data.PlayURL; link != `` {
|
||||||
outlink = `https:` + link
|
// outlink = `https:` + link
|
||||||
} // else {
|
// } else {
|
||||||
// jx.Debug(`Mg, Err: %#v`, resp)
|
// msg = ztool.Str_FastConcat(resp.Code, `: `, resp.Msg)
|
||||||
// }
|
// }
|
||||||
case sources.S_kw:
|
// case sources.S_kw:
|
||||||
if !env.Config.Custom.Kw_Enable {
|
// if !env.Config.Source.Enable_Kw {
|
||||||
msg = sources.ErrDisable
|
// msg = sources.ErrDisable
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
ourl, emsg := kw.Url(c.MusicID, c.Quality)
|
// outlink, msg = kw.Url(c.MusicID, c.Quality)
|
||||||
if emsg != `` {
|
// // if emsg != `` {
|
||||||
msg = emsg
|
// // msg = emsg
|
||||||
return
|
// // return
|
||||||
}
|
// // }
|
||||||
outlink = ourl
|
// // outlink = ourl
|
||||||
case sources.S_kg:
|
// case sources.S_kg:
|
||||||
resp := kg_pool.Get().(*KgApi_Song)
|
// if !env.Config.Source.Enable_Kg {
|
||||||
defer kg_pool.Put(resp)
|
// msg = sources.ErrDisable
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// if len(c.MusicID) != 32 {
|
||||||
|
// msg = sources.E_VefMusicId
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// outlink, msg = kg.Url(c.MusicID, c.Quality)
|
||||||
|
// // if emsg != `` {
|
||||||
|
// // msg = emsg
|
||||||
|
// // return
|
||||||
|
// // }
|
||||||
|
// // outlink = ourl
|
||||||
|
// // case sources.S_kg:
|
||||||
|
// // if !env.Config.Custom.Kg_Enable {
|
||||||
|
// // msg = sources.ErrDisable
|
||||||
|
// // return
|
||||||
|
// // }
|
||||||
|
// // resp := kg_pool.Get().(*KgApi_Song)
|
||||||
|
// // defer kg_pool.Put(resp)
|
||||||
|
|
||||||
// sep := strings.Split(c.MusicID, `-`) // 分割 Hash-Album 如 6DC276334F56E22BE2A0E8254D332B45-13097991
|
// // // sep := strings.Split(c.MusicID, `-`) // 分割 Hash-Album 如 6DC276334F56E22BE2A0E8254D332B45-13097991
|
||||||
// alb := func() string {
|
// // // alb := func() string {
|
||||||
// if len(sep) >= 2 {
|
// // // if len(sep) >= 2 {
|
||||||
// return sep[1]
|
// // // return sep[1]
|
||||||
// }
|
// // // }
|
||||||
// return ``
|
// // // return ``
|
||||||
// }()
|
// // // }()
|
||||||
sep := c.Split()
|
// // sep := c.Split()
|
||||||
url := ztool.Str_FastConcat(api_kg, `&hash=`, sep[0], `&album_id=`, sep[1], `&_=`, strconv.FormatInt(time.Now().UnixMilli(), 10))
|
// // 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)
|
// // // jx.Debug(`Kg, Url: %s`, url)
|
||||||
_, err := ztool.Net_HttpReq(http.MethodGet, url, nil, nil, &resp)
|
// // _, err := ztool.Net_HttpReq(http.MethodGet, url, nil, nil, &resp)
|
||||||
if err != nil {
|
// // if err != nil {
|
||||||
jx.Error(`Kg, HttpReq: %s`, err)
|
// // jx.Error(`Kg, HttpReq: %s`, err)
|
||||||
msg = sources.ErrHttpReq
|
// // msg = sources.ErrHttpReq
|
||||||
return
|
// // return
|
||||||
}
|
// // }
|
||||||
jx.Debug(`Kg, Resp: %+v`, resp)
|
// // jx.Debug(`Kg, Resp: %+v`, resp)
|
||||||
if resp.ErrCode != 0 {
|
// // if resp.ErrCode != 0 {
|
||||||
msg = ztool.Str_FastConcat(`Error: `, strconv.Itoa(resp.ErrCode))
|
// // msg = ztool.Str_FastConcat(`Error: `, strconv.Itoa(resp.ErrCode))
|
||||||
return
|
// // return
|
||||||
}
|
// // }
|
||||||
var data KgApi_Data
|
// // var data KgApi_Data
|
||||||
err = ztool.Val_MapToStruct(resp.Data, &data)
|
// // err = ztool.Val_MapToStruct(resp.Data, &data)
|
||||||
if err != nil {
|
// // if err != nil {
|
||||||
msg = err.Error()
|
// // msg = err.Error()
|
||||||
return
|
// // return
|
||||||
}
|
// // }
|
||||||
if data.PlayBackupURL == `` {
|
// // if data.PlayBackupURL == `` {
|
||||||
if data.PlayURL == `` {
|
// // if data.PlayURL == `` {
|
||||||
msg = sources.ErrNoLink
|
// // msg = sources.ErrNoLink
|
||||||
return
|
// // return
|
||||||
}
|
// // }
|
||||||
outlink = data.PlayURL
|
// // outlink = data.PlayURL
|
||||||
}
|
// // }
|
||||||
outlink = data.PlayBackupURL
|
// // outlink = data.PlayBackupURL
|
||||||
case sources.S_tx:
|
// case sources.S_tx:
|
||||||
// sep := c.Split()
|
// if !env.Config.Source.Enable_Tx {
|
||||||
if len(c.MusicID) != 14 {
|
// msg = sources.ErrDisable
|
||||||
msg = sources.E_VefMusicId
|
// return
|
||||||
return
|
// }
|
||||||
}
|
// if len(c.MusicID) != 14 {
|
||||||
ourl, emsg := tx.Url(c.MusicID, c.Quality)
|
// msg = sources.E_VefMusicId
|
||||||
if emsg != `` {
|
// return
|
||||||
msg = emsg
|
// }
|
||||||
return
|
// outlink, msg = tx.Url(c.MusicID, c.Quality)
|
||||||
}
|
// // if emsg != `` {
|
||||||
outlink = ourl
|
// // msg = emsg
|
||||||
// case `otx`:
|
// // return
|
||||||
// resp := tx_pool.Get().(*res_tx)
|
// // }
|
||||||
// defer tx_pool.Put(resp)
|
// // outlink = ourl
|
||||||
|
// // case `otx`:
|
||||||
|
// // resp := tx_pool.Get().(*res_tx)
|
||||||
|
// // defer tx_pool.Put(resp)
|
||||||
|
|
||||||
// sep := c.Split()
|
// // sep := c.Split()
|
||||||
// url := ztool.Str_FastConcat(api_tx,
|
// // 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":{`,
|
// // `{"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 {
|
// // func(s string) string {
|
||||||
// if s == `` {
|
// // if s == `` {
|
||||||
// return ``
|
// // return ``
|
||||||
// }
|
// // }
|
||||||
// return ztool.Str_FastConcat(`"filename":["`, rquery, s, `.`, c.Extname, `"],`)
|
// // return ztool.Str_FastConcat(`"filename":["`, rquery, s, `.`, c.Extname, `"],`)
|
||||||
// }(sep[1]),
|
// // }(sep[1]),
|
||||||
// `"guid":"1535153710","loginflag":1,"platform":"20","songmid":["`, sep[0], `"],"songtype":[0],"uin":"10086"}}}`,
|
// // `"guid":"1535153710","loginflag":1,"platform":"20","songmid":["`, sep[0], `"],"songtype":[0],"uin":"10086"}}}`,
|
||||||
// )
|
// // )
|
||||||
// // jx.Debug(`Tx, Url: %s`, url)
|
// // // jx.Debug(`Tx, Url: %s`, url)
|
||||||
// out, err := ztool.Net_HttpReq(http.MethodGet, url, nil, header_tx, &resp)
|
// // out, err := ztool.Net_HttpReq(http.MethodGet, url, nil, header_tx, &resp)
|
||||||
// if err != nil {
|
// // if err != nil {
|
||||||
// jx.Error(`Tx, HttpReq: %s`, err)
|
// // jx.Error(`Tx, HttpReq: %s`, err)
|
||||||
// msg = errHttpReq
|
// // msg = errHttpReq
|
||||||
// return
|
// // return
|
||||||
// }
|
// // }
|
||||||
// jx.Debug(`Tx, Resp: %s`, out)
|
// // jx.Debug(`Tx, Resp: %s`, out)
|
||||||
// if resp.Code != 0 {
|
// // if resp.Code != 0 {
|
||||||
// msg = ztool.Str_FastConcat(`Error: `, strconv.Itoa(resp.Code))
|
// // msg = ztool.Str_FastConcat(`Error: `, strconv.Itoa(resp.Code))
|
||||||
// return
|
// // return
|
||||||
// }
|
// // }
|
||||||
// if resp.Req0.Data.Midurlinfo[0].Purl == `` {
|
// // if resp.Req0.Data.Midurlinfo[0].Purl == `` {
|
||||||
// msg = errNoLink
|
// // msg = errNoLink
|
||||||
// return
|
// // return
|
||||||
// }
|
// // }
|
||||||
// outlink = ztool.Str_FastConcat(`https://dl.stream.qqmusic.qq.com/`, resp.Req0.Data.Midurlinfo[0].Purl)
|
// // outlink = ztool.Str_FastConcat(`https://dl.stream.qqmusic.qq.com/`, resp.Req0.Data.Midurlinfo[0].Purl)
|
||||||
default:
|
// default:
|
||||||
msg = `不支持的平台`
|
// msg = `不支持的平台`
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
@ -1,293 +1,274 @@
|
|||||||
package builtin
|
package builtin
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"lx-source/src/sources"
|
// "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`
|
|
||||||
// )
|
// )
|
||||||
|
|
||||||
var (
|
// type (
|
||||||
// 音质列表 ( [通用音质][音乐平台]对应音质 )
|
// // 网易音乐接口 (方格/简繁)
|
||||||
/*
|
// // WyApi_Song struct {
|
||||||
注: kg源使用对应hash匹配音质,故为空
|
// // Data []struct {
|
||||||
*/
|
// // ID int `json:"id"`
|
||||||
qualitys = map[string]map[string]string{
|
// // URL string `json:"url"`
|
||||||
sources.Q_128k: {
|
// // Br int `json:"br"`
|
||||||
sources.S_wy: `standard`,
|
// // Size int `json:"size"`
|
||||||
sources.S_mg: `1`,
|
// // Md5 string `json:"md5"`
|
||||||
sources.S_kw: sources.Q_128k,
|
// // Code int `json:"code"`
|
||||||
sources.S_kg: ``,
|
// // Expi int `json:"expi"`
|
||||||
sources.S_tx: `M500`,
|
// // Type string `json:"type"`
|
||||||
},
|
// // Gain float64 `json:"gain"`
|
||||||
sources.Q_320k: {
|
// // Peak float64 `json:"peak"`
|
||||||
sources.S_wy: `exhigh`,
|
// // Fee int `json:"fee"`
|
||||||
sources.S_mg: `2`,
|
// // Uf interface{} `json:"uf"`
|
||||||
sources.S_kw: sources.Q_320k,
|
// // Payed int `json:"payed"`
|
||||||
// sources.S_kg: ``,
|
// // Flag int `json:"flag"`
|
||||||
sources.S_tx: `M800`,
|
// // CanExtend bool `json:"canExtend"`
|
||||||
},
|
// // FreeTrialInfo interface{} `json:"freeTrialInfo"`
|
||||||
sources.Q_flac: {
|
// // Level string `json:"level"`
|
||||||
sources.S_wy: `lossless`,
|
// // EncodeType string `json:"encodeType"`
|
||||||
sources.S_mg: `3`,
|
// // FreeTrialPrivilege struct {
|
||||||
sources.S_kw: `2000k`,
|
// // ResConsumable bool `json:"resConsumable"`
|
||||||
sources.S_tx: `F000`,
|
// // UserConsumable bool `json:"userConsumable"`
|
||||||
},
|
// // ListenType interface{} `json:"listenType"`
|
||||||
sources.Q_fl24: {
|
// // CannotListenReason interface{} `json:"cannotListenReason"`
|
||||||
sources.S_wy: `hires`,
|
// // PlayReason interface{} `json:"playReason"`
|
||||||
sources.S_mg: `4`,
|
// // } `json:"freeTrialPrivilege"`
|
||||||
// sources.S_tx: `RS01`,
|
// // FreeTimeTrialPrivilege struct {
|
||||||
},
|
// // ResConsumable bool `json:"resConsumable"`
|
||||||
// `fl24`: {
|
// // UserConsumable bool `json:"userConsumable"`
|
||||||
// s_wy: `hires`,
|
// // Type int `json:"type"`
|
||||||
// s_mg: `4`,
|
// // RemainTime int `json:"remainTime"`
|
||||||
// },
|
// // } `json:"freeTimeTrialPrivilege"`
|
||||||
}
|
// // URLSource int `json:"urlSource"`
|
||||||
// ApiAddr
|
// // RightSource int `json:"rightSource"`
|
||||||
api_wy string
|
// // PodcastCtrp interface{} `json:"podcastCtrp"`
|
||||||
api_mg string
|
// // EffectTypes interface{} `json:"effectTypes"`
|
||||||
// api_kw string
|
// // Time int `json:"time"`
|
||||||
api_kg string = `https://wwwapi.kugou.com/yy/index.php?r=play/getdata&platid=4&mid=1`
|
// // } `json:"data"`
|
||||||
// api_tx string = `https://u.y.qq.com/cgi-bin/musicu.fcg?data=`
|
// // Code int `json:"code"`
|
||||||
vef_wy string
|
// // }
|
||||||
// Headers
|
// // WyApi_Vef struct {
|
||||||
header_wy map[string]string
|
// // Code int16 `json:"code"`
|
||||||
header_mg map[string]string
|
// // Success bool `json:"success"`
|
||||||
// header_kw map[string]string
|
// // Message string `json:"message"`
|
||||||
// header_tx = map[string]string{`Referer`: `https://y.qq.com/`}
|
// // }
|
||||||
)
|
// // 咪咕音乐接口
|
||||||
|
// MgApi_Song struct {
|
||||||
|
// Code string `json:"code"`
|
||||||
|
// Msg string `json:"msg"`
|
||||||
|
// Data struct {
|
||||||
|
// PlayURL string `json:"playUrl"`
|
||||||
|
// FormatID string `json:"formatId"`
|
||||||
|
// SalePrice string `json:"salePrice"`
|
||||||
|
// BizType string `json:"bizType"`
|
||||||
|
// BizCode string `json:"bizCode"`
|
||||||
|
// AuditionsLength int `json:"auditionsLength"`
|
||||||
|
// } `json:"data"`
|
||||||
|
// }
|
||||||
|
// // 酷我音乐接口 (波点)
|
||||||
|
// // KwApi_Song struct {
|
||||||
|
// // Code int `json:"code"`
|
||||||
|
// // Msg string `json:"msg"`
|
||||||
|
// // ReqID string `json:"reqId"`
|
||||||
|
// // Data struct {
|
||||||
|
// // Duration int `json:"duration"`
|
||||||
|
// // AudioInfo struct {
|
||||||
|
// // Bitrate string `json:"bitrate"`
|
||||||
|
// // Format string `json:"format"`
|
||||||
|
// // Level string `json:"level"`
|
||||||
|
// // Size string `json:"size"`
|
||||||
|
// // } `json:"audioInfo"`
|
||||||
|
// // URL string `json:"url"`
|
||||||
|
// // } `json:"data"`
|
||||||
|
// // ProfileID string `json:"profileId"`
|
||||||
|
// // CurTime int64 `json:"curTime"`
|
||||||
|
// // }
|
||||||
|
// // 酷狗试听接口
|
||||||
|
// // KgApi_Song struct {
|
||||||
|
// // Status int `json:"status"`
|
||||||
|
// // ErrCode int `json:"err_code"`
|
||||||
|
// // Data any `json:"data"`
|
||||||
|
// // }
|
||||||
|
// // KgApi_Data struct {
|
||||||
|
// // // Hash string `json:"hash"`
|
||||||
|
// // // Timelength int `json:"timelength"`
|
||||||
|
// // // Filesize int `json:"filesize"`
|
||||||
|
// // // AudioName string `json:"audio_name"`
|
||||||
|
// // // HaveAlbum int `json:"have_album"`
|
||||||
|
// // // AlbumName string `json:"album_name"`
|
||||||
|
// // // AlbumID any `json:"album_id"`
|
||||||
|
// // // Img string `json:"img"`
|
||||||
|
// // // HaveMv int `json:"have_mv"`
|
||||||
|
// // // VideoID any `json:"video_id"`
|
||||||
|
// // // AuthorName string `json:"author_name"`
|
||||||
|
// // // SongName string `json:"song_name"`
|
||||||
|
// // // Lyrics string `json:"lyrics"`
|
||||||
|
// // // AuthorID any `json:"author_id"`
|
||||||
|
// // // Privilege int `json:"privilege"`
|
||||||
|
// // // Privilege2 string `json:"privilege2"`
|
||||||
|
// // PlayURL string `json:"play_url"`
|
||||||
|
// // // Authors []struct {
|
||||||
|
// // // AuthorID any `json:"author_id"`
|
||||||
|
// // // AuthorName string `json:"author_name"`
|
||||||
|
// // // IsPublish string `json:"is_publish"`
|
||||||
|
// // // SizableAvatar string `json:"sizable_avatar"`
|
||||||
|
// // // EAuthorID string `json:"e_author_id"`
|
||||||
|
// // // Avatar string `json:"avatar"`
|
||||||
|
// // // } `json:"authors"`
|
||||||
|
// // // IsFreePart int `json:"is_free_part"`
|
||||||
|
// // // Bitrate int `json:"bitrate"`
|
||||||
|
// // // RecommendAlbumID string `json:"recommend_album_id"`
|
||||||
|
// // // StoreType string `json:"store_type"`
|
||||||
|
// // // AlbumAudioID int `json:"album_audio_id"`
|
||||||
|
// // // IsPublish int `json:"is_publish"`
|
||||||
|
// // // EAuthorID string `json:"e_author_id"`
|
||||||
|
// // // AudioID any `json:"audio_id"`
|
||||||
|
// // // HasPrivilege bool `json:"has_privilege"`
|
||||||
|
// // PlayBackupURL string `json:"play_backup_url"`
|
||||||
|
// // // SmallLibrarySong int `json:"small_library_song"`
|
||||||
|
// // // EncodeAlbumID string `json:"encode_album_id"`
|
||||||
|
// // // EncodeAlbumAudioID string `json:"encode_album_audio_id"`
|
||||||
|
// // // EVideoID string `json:"e_video_id"`
|
||||||
|
// // }
|
||||||
|
// // 腾讯试听接口
|
||||||
|
// // res_tx struct {
|
||||||
|
// // Code int `json:"code"`
|
||||||
|
// // // Ts int64 `json:"ts"`
|
||||||
|
// // // StartTs int64 `json:"start_ts"`
|
||||||
|
// // // Traceid string `json:"traceid"`
|
||||||
|
// // // Req struct {
|
||||||
|
// // // Code int `json:"code"`
|
||||||
|
// // // Data struct {
|
||||||
|
// // // Expiration int `json:"expiration"`
|
||||||
|
// // // Freeflowsip []string `json:"freeflowsip"`
|
||||||
|
// // // Keepalivefile string `json:"keepalivefile"`
|
||||||
|
// // // Msg string `json:"msg"`
|
||||||
|
// // // Retcode int `json:"retcode"`
|
||||||
|
// // // Servercheck string `json:"servercheck"`
|
||||||
|
// // // Sip []string `json:"sip"`
|
||||||
|
// // // Testfile2G string `json:"testfile2g"`
|
||||||
|
// // // Testfilewifi string `json:"testfilewifi"`
|
||||||
|
// // // Uin string `json:"uin"`
|
||||||
|
// // // Userip string `json:"userip"`
|
||||||
|
// // // Vkey string `json:"vkey"`
|
||||||
|
// // // } `json:"data"`
|
||||||
|
// // // } `json:"req"`
|
||||||
|
// // Req0 struct {
|
||||||
|
// // Code int `json:"code"`
|
||||||
|
// // Data struct {
|
||||||
|
// // // Uin string `json:"uin"`
|
||||||
|
// // // Retcode int `json:"retcode"`
|
||||||
|
// // // VerifyType int `json:"verify_type"`
|
||||||
|
// // // LoginKey string `json:"login_key"`
|
||||||
|
// // // Msg string `json:"msg"`
|
||||||
|
// // // Sip []string `json:"sip"`
|
||||||
|
// // // Thirdip []string `json:"thirdip"`
|
||||||
|
// // // Testfile2G string `json:"testfile2g"`
|
||||||
|
// // // Testfilewifi string `json:"testfilewifi"`
|
||||||
|
// // Midurlinfo []struct {
|
||||||
|
// // // Songmid string `json:"songmid"`
|
||||||
|
// // // Filename string `json:"filename"`
|
||||||
|
// // Purl string `json:"purl"`
|
||||||
|
// // // Errtype string `json:"errtype"`
|
||||||
|
// // // P2Pfromtag int `json:"p2pfromtag"`
|
||||||
|
// // // Qmdlfromtag int `json:"qmdlfromtag"`
|
||||||
|
// // // CommonDownfromtag int `json:"common_downfromtag"`
|
||||||
|
// // // VipDownfromtag int `json:"vip_downfromtag"`
|
||||||
|
// // // Pdl int `json:"pdl"`
|
||||||
|
// // // Premain int `json:"premain"`
|
||||||
|
// // // Hisdown int `json:"hisdown"`
|
||||||
|
// // // Hisbuy int `json:"hisbuy"`
|
||||||
|
// // // UIAlert int `json:"uiAlert"`
|
||||||
|
// // // Isbuy int `json:"isbuy"`
|
||||||
|
// // // Pneedbuy int `json:"pneedbuy"`
|
||||||
|
// // // Pneed int `json:"pneed"`
|
||||||
|
// // // Isonly int `json:"isonly"`
|
||||||
|
// // // Onecan int `json:"onecan"`
|
||||||
|
// // // Result int `json:"result"`
|
||||||
|
// // // Tips string `json:"tips"`
|
||||||
|
// // // Opi48Kurl string `json:"opi48kurl"`
|
||||||
|
// // // Opi96Kurl string `json:"opi96kurl"`
|
||||||
|
// // // Opi192Kurl string `json:"opi192kurl"`
|
||||||
|
// // // Opiflackurl string `json:"opiflackurl"`
|
||||||
|
// // // Opi128Kurl string `json:"opi128kurl"`
|
||||||
|
// // // Opi192Koggurl string `json:"opi192koggurl"`
|
||||||
|
// // // Wififromtag string `json:"wififromtag"`
|
||||||
|
// // // Flowfromtag string `json:"flowfromtag"`
|
||||||
|
// // // Wifiurl string `json:"wifiurl"`
|
||||||
|
// // // Flowurl string `json:"flowurl"`
|
||||||
|
// // // Vkey string `json:"vkey"`
|
||||||
|
// // // Opi30Surl string `json:"opi30surl"`
|
||||||
|
// // // Ekey string `json:"ekey"`
|
||||||
|
// // // AuthSwitch int `json:"auth_switch"`
|
||||||
|
// // // Subcode int `json:"subcode"`
|
||||||
|
// // // Opi96Koggurl string `json:"opi96koggurl"`
|
||||||
|
// // // AuthSwitch2 int `json:"auth_switch2"`
|
||||||
|
// // } `json:"midurlinfo"`
|
||||||
|
// // // Servercheck string `json:"servercheck"`
|
||||||
|
// // // Expiration int `json:"expiration"`
|
||||||
|
// // } `json:"data"`
|
||||||
|
// // } `json:"req_0"`
|
||||||
|
// // }
|
||||||
|
// )
|
||||||
|
|
||||||
func init() {
|
// // const (
|
||||||
// InitBuiltInSource
|
// // // Source
|
||||||
var initdata = struct {
|
// // s_wy = `wy`
|
||||||
Api_Wy *string
|
// // s_mg = `mg`
|
||||||
Api_Mg *string
|
// // s_kw = `kw`
|
||||||
Vef_Wy *string
|
// // s_kg = `kg`
|
||||||
Header_Wy *map[string]string
|
// // s_tx = `tx`
|
||||||
Header_Mg *map[string]string
|
// // // s_lx = `lx`
|
||||||
}{
|
// // )
|
||||||
Api_Wy: &api_wy,
|
|
||||||
Api_Mg: &api_mg,
|
// var (
|
||||||
Vef_Wy: &vef_wy,
|
// // 音质列表 ( [通用音质][音乐平台]对应音质 )
|
||||||
Header_Wy: &header_wy,
|
// /*
|
||||||
Header_Mg: &header_mg,
|
// 注: kg源使用对应hash匹配音质,故为空
|
||||||
}
|
// */
|
||||||
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}
|
// qualitys = map[string]map[string]string{
|
||||||
ztool.Val_GobDecode(data, &initdata)
|
// 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/`}
|
||||||
|
// )
|
||||||
|
3
src/sources/custom/custom.go
Normal file
3
src/sources/custom/custom.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package custom
|
||||||
|
|
||||||
|
// type CSource struct{}
|
@ -1,2 +1,134 @@
|
|||||||
// 账号解析源
|
// 账号解析源
|
||||||
package custom
|
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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
70
src/sources/custom/kg/musicinfo.go
Normal file
70
src/sources/custom/kg/musicinfo.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package kg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/gob"
|
||||||
|
"lx-source/src/env"
|
||||||
|
"lx-source/src/sources"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ZxwyWebSite/ztool"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
gob.Register(musicInfo{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMusicInfo(hash_ string) (info musicInfo, emsg string) {
|
||||||
|
cquery := strings.Join([]string{`kg`, hash_, `info`}, `/`)
|
||||||
|
if cdata, ok := env.Cache.Get(cquery); ok {
|
||||||
|
if cinfo, ok := cdata.(musicInfo); ok {
|
||||||
|
info = cinfo
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body := ztool.Str_FastConcat(
|
||||||
|
`{"area_code":"1","show_privilege":"1","show_album_info":"1","is_publish":"","appid":1005,"clientver":11451,"mid":"211008","dfid":"-","clienttime":"`,
|
||||||
|
strconv.FormatInt(time.Now().Unix(), 10),
|
||||||
|
`","key":"OIlwlieks28dk2k092lksi2UIkp","data":[{"hash":"`,
|
||||||
|
hash_,
|
||||||
|
`"}]}`,
|
||||||
|
)
|
||||||
|
var infoResp struct {
|
||||||
|
Status int `json:"status"`
|
||||||
|
ErrorCode int `json:"error_code"`
|
||||||
|
Errmsg string `json:"errmsg"`
|
||||||
|
Data [][]musicInfo `json:"data"`
|
||||||
|
}
|
||||||
|
err := ztool.Net_Request(
|
||||||
|
http.MethodPost,
|
||||||
|
`http://gateway.kugou.com/v3/album_audio/audio`,
|
||||||
|
strings.NewReader(body),
|
||||||
|
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeader(map[string]string{
|
||||||
|
`KG-THash`: `13a3164`,
|
||||||
|
`KG-RC`: `1`,
|
||||||
|
`KG-Fake`: `0`,
|
||||||
|
`KG-RF`: `00869891`,
|
||||||
|
`User-Agent`: `Android712-AndroidPhone-11451-376-0-FeeCacheUpdate-wifi`,
|
||||||
|
`x-router`: `kmr.service.kugou.com`,
|
||||||
|
})},
|
||||||
|
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&infoResp)},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
emsg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(infoResp.Data) == 0 {
|
||||||
|
if infoResp.Errmsg != `` {
|
||||||
|
emsg = infoResp.Errmsg
|
||||||
|
} else {
|
||||||
|
emsg = `No Data`
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
info = infoResp.Data[0][0]
|
||||||
|
emsg = infoResp.Errmsg
|
||||||
|
env.Cache.Set(cquery, info, sources.C_lx)
|
||||||
|
return
|
||||||
|
}
|
104
src/sources/custom/kg/player.go
Normal file
104
src/sources/custom/kg/player.go
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package kg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"lx-source/src/env"
|
||||||
|
"lx-source/src/sources"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Url(songMid, quality string) (ourl, msg string) {
|
||||||
|
loger := env.Loger.NewGroup(`Kg`)
|
||||||
|
defer loger.Free()
|
||||||
|
rquality, ok := qualityMap[quality]
|
||||||
|
if !ok {
|
||||||
|
msg = sources.E_QNotSupport
|
||||||
|
return
|
||||||
|
}
|
||||||
|
info, emsg := getMusicInfo(strings.ToLower(songMid))
|
||||||
|
if emsg != `` {
|
||||||
|
loger.Error(`GetInfo: %v`, emsg)
|
||||||
|
msg = emsg
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loger.Debug(`Info: %+v`, info)
|
||||||
|
var tHash string
|
||||||
|
switch quality {
|
||||||
|
case sources.Q_128k:
|
||||||
|
tHash = info.AudioInfo.Hash128
|
||||||
|
case sources.Q_320k:
|
||||||
|
tHash = info.AudioInfo.Hash320
|
||||||
|
case sources.Q_flac:
|
||||||
|
tHash = info.AudioInfo.HashFlac
|
||||||
|
case sources.Q_fl24:
|
||||||
|
tHash = info.AudioInfo.HashHigh
|
||||||
|
}
|
||||||
|
if tHash == `` {
|
||||||
|
msg = sources.E_QNotMatch
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tHash = strings.ToLower(tHash)
|
||||||
|
now := time.Now()
|
||||||
|
params := map[string]string{
|
||||||
|
`album_id`: info.AlbumInfo.AlbumID,
|
||||||
|
`userid`: env.Config.Custom.Kg_userId,
|
||||||
|
`area_code`: `1`,
|
||||||
|
`hash`: tHash,
|
||||||
|
`module`: ``,
|
||||||
|
`mid`: mid,
|
||||||
|
`appid`: env.Config.Custom.Kg_Client_AppId,
|
||||||
|
`ssa_flag`: `is_fromtrack`,
|
||||||
|
`clientver`: env.Config.Custom.Kg_Client_Version,
|
||||||
|
`open_time`: now.Format(`20060102`),
|
||||||
|
`vipType`: `6`,
|
||||||
|
`ptype`: `0`,
|
||||||
|
`token`: env.Config.Custom.Kg_token,
|
||||||
|
`auth`: ``,
|
||||||
|
`mtype`: `0`,
|
||||||
|
`album_audio_id`: info.AlbumAudioID,
|
||||||
|
`behavior`: `play`,
|
||||||
|
`clienttime`: strconv.FormatInt(now.Unix(), 10),
|
||||||
|
`pid`: `2`,
|
||||||
|
`key`: getKey(tHash),
|
||||||
|
`dfid`: `-`,
|
||||||
|
`pidversion`: `3001`,
|
||||||
|
|
||||||
|
`quality`: rquality,
|
||||||
|
// `IsFreePart`: `1`,
|
||||||
|
}
|
||||||
|
if !env.Config.Custom.Kg_Enable {
|
||||||
|
params[`IsFreePart`] = `1` // 仅游客登录时允许获取试听
|
||||||
|
}
|
||||||
|
headers := map[string]string{
|
||||||
|
`User-Agent`: `Android712-AndroidPhone-8983-18-0-NetMusic-wifi`,
|
||||||
|
`KG-THash`: `3e5ec6b`,
|
||||||
|
`KG-Rec`: `1`,
|
||||||
|
`KG-RC`: `1`,
|
||||||
|
|
||||||
|
`x-router`: `tracker.kugou.com`,
|
||||||
|
}
|
||||||
|
var resp playInfo
|
||||||
|
err := signRequest(http.MethodGet, url, ``, params, headers, &resp)
|
||||||
|
if err != nil {
|
||||||
|
loger.Error(`Request: %s`, err)
|
||||||
|
msg = sources.ErrHttpReq
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loger.Debug(`Resp: %+v`, resp)
|
||||||
|
switch resp.Status {
|
||||||
|
case 3:
|
||||||
|
msg = `该歌曲在酷狗没有版权,请换源播放`
|
||||||
|
case 2:
|
||||||
|
msg = `链接获取失败:请检查账号是否有会员或数字专辑是否购买`
|
||||||
|
}
|
||||||
|
if resp.Status != 1 {
|
||||||
|
if msg == `` {
|
||||||
|
msg = `链接获取失败,可能是数字专辑或者api失效,Status: ` + strconv.Itoa(resp.Status)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ourl = resp.URL[len(resp.URL)-1]
|
||||||
|
return
|
||||||
|
}
|
265
src/sources/custom/kg/refresh.go
Normal file
265
src/sources/custom/kg/refresh.go
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
package kg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"errors"
|
||||||
|
"lx-source/src/env"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ZxwyWebSite/ztool"
|
||||||
|
"github.com/ZxwyWebSite/ztool/logs"
|
||||||
|
"github.com/ZxwyWebSite/ztool/x/bytesconv"
|
||||||
|
"github.com/ZxwyWebSite/ztool/x/json"
|
||||||
|
"github.com/ZxwyWebSite/ztool/zcypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 通过TOP500榜单获取随机歌曲的mixsongmid
|
||||||
|
func randomMixSongMid() (mid string, err error) {
|
||||||
|
// 声明榜单url
|
||||||
|
const rankUrl = `http://mobilecdnbj.kugou.com/api/v3/rank/song?version=9108&ranktype=1&plat=0&pagesize=100&area_code=1&page=1&rankid=8888&with_res_tag=0&show_portrait_mv=1`
|
||||||
|
// 请求
|
||||||
|
var res rankInfo
|
||||||
|
err = ztool.Net_Request(
|
||||||
|
http.MethodGet,
|
||||||
|
rankUrl, nil,
|
||||||
|
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders()},
|
||||||
|
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&res)},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// fmt.Printf("%#v\n", res)
|
||||||
|
if res.Status != 1 {
|
||||||
|
err = errors.New(res.Error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 随机选择一首歌曲
|
||||||
|
randomSong := res.Data.Info[rand.Intn(len(res.Data.Info))]
|
||||||
|
// fmt.Printf("%#v\n", randomSong)
|
||||||
|
|
||||||
|
// 因为排行榜api不会返回mixsongmid
|
||||||
|
// 所以需要进行一次搜索接口来获取
|
||||||
|
var body searchInfo
|
||||||
|
err = ztool.Net_Request(
|
||||||
|
http.MethodGet,
|
||||||
|
ztool.Str_FastConcat(
|
||||||
|
`https://songsearch.kugou.com/song_search_v2?`,
|
||||||
|
ztool.Net_Values(map[string]string{
|
||||||
|
`keyword`: randomSong.Filename,
|
||||||
|
`area_code`: `1`,
|
||||||
|
`page`: `1`,
|
||||||
|
`pagesize`: `1`,
|
||||||
|
`userid`: `0`,
|
||||||
|
`clientver`: ``,
|
||||||
|
`platform`: `WebFilter`,
|
||||||
|
`filter`: `2`,
|
||||||
|
`iscorrection`: `1`,
|
||||||
|
`privilege_filter`: `0`,
|
||||||
|
}),
|
||||||
|
), nil,
|
||||||
|
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(map[string]string{
|
||||||
|
`Referer`: `https://www.kugou.com`,
|
||||||
|
})},
|
||||||
|
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&body)},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// fmt.Printf("%#v\n", body)
|
||||||
|
if body.Status != 1 {
|
||||||
|
err = errors.New(body.ErrorMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if body.Data.Total == 0 || len(body.Data.Lists) == 0 {
|
||||||
|
err = errors.New(`歌曲搜索失败`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mid = body.Data.Lists[0].MixSongID
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 签到主函数,传入userinfo,响应None就是成功,报错即为不成功
|
||||||
|
func do_account_signin(loger *logs.Logger, now int64) (err error) {
|
||||||
|
// 时间检测
|
||||||
|
if now < env.Config.Custom.Kg_Lite_Interval {
|
||||||
|
loger.Debug(`Key未过期,跳过...`)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// 检查用户配置文件,获取mixsongmid
|
||||||
|
mixid := env.Config.Custom.Kg_Lite_MixId //`582534238`
|
||||||
|
if mixid == `auto` || mixid == `` {
|
||||||
|
for i := 0; true; i++ {
|
||||||
|
mixid, err = randomMixSongMid()
|
||||||
|
if err != nil {
|
||||||
|
loger.Error(`ReTry: %v, Err: %s`, i, err)
|
||||||
|
if i >= 2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// mixid, err = randomMixSongMid()
|
||||||
|
// if err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
loger.Info(`成功获取MixSongMid: ` + mixid)
|
||||||
|
} else {
|
||||||
|
loger.Info(`使用固定MixSongMid: ` + mixid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 声明变量
|
||||||
|
headers := map[string]string{
|
||||||
|
`User-Agent`: ztool.Str_FastConcat(
|
||||||
|
`Android712-AndroidPhone-`,
|
||||||
|
env.Config.Custom.Kg_Client_Version,
|
||||||
|
`-18-0-NetMusic-wifi`,
|
||||||
|
),
|
||||||
|
`KG-THash`: `3e5ec6b`,
|
||||||
|
`KG-Rec`: `1`,
|
||||||
|
`KG-RC`: `1`,
|
||||||
|
`x-router`: `youth.kugou.com`,
|
||||||
|
}
|
||||||
|
body := ztool.Str_FastConcat(
|
||||||
|
`{"mixsongid":"`, mixid, `"}`,
|
||||||
|
)
|
||||||
|
tnow := time.Now()
|
||||||
|
params := map[string]string{
|
||||||
|
`userid`: env.Config.Custom.Kg_userId,
|
||||||
|
`token`: env.Config.Custom.Kg_token,
|
||||||
|
`appid`: env.Config.Custom.Kg_Client_AppId,
|
||||||
|
`clientver`: env.Config.Custom.Kg_Client_Version,
|
||||||
|
`clienttime`: strconv.FormatInt(tnow.Unix(), 10),
|
||||||
|
`mid`: mid,
|
||||||
|
`uuid`: zcypt.HexToString(zcypt.RandomBytes(16)),
|
||||||
|
`dfid`: `-`,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
var out refreshInfo
|
||||||
|
err = signRequest(
|
||||||
|
http.MethodPost,
|
||||||
|
`https://gateway.kugou.com/v2/report/listen_song`,
|
||||||
|
body, params, headers, &out,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
loger.Debug(`Resp: %+v`, out)
|
||||||
|
if out.Status != 1 || out.ErrorCode != 0 {
|
||||||
|
switch out.ErrorCode {
|
||||||
|
case 130012:
|
||||||
|
loger.Info(`今日已签到过,明天再来吧`)
|
||||||
|
case 51002:
|
||||||
|
panic(`登录过期啦!请重新获取账号Token`)
|
||||||
|
default:
|
||||||
|
return errors.New(strconv.Itoa(out.ErrorCode) + `: ` + out.ErrorMsg)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loger.Info(`Lite签到成功`)
|
||||||
|
}
|
||||||
|
tomorrow := time.Date(tnow.Year(), tnow.Month(), tnow.Day()+1, 0, 0, 0, 0, tnow.Location())
|
||||||
|
env.Config.Custom.Kg_Lite_Interval = tomorrow.Unix()
|
||||||
|
|
||||||
|
return env.Cfg.Save(``)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新Token
|
||||||
|
func login_by_token(loger *logs.Logger, now int64) (err error) {
|
||||||
|
// 前置到期检测
|
||||||
|
if now < env.Config.Custom.Kg_Refresh_Interval {
|
||||||
|
loger.Debug(`Key未过期,跳过...`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 获取加密参数
|
||||||
|
var aeskey []byte
|
||||||
|
switch env.Config.Custom.Kg_Client_AppId {
|
||||||
|
case `1005`:
|
||||||
|
aeskey = []byte(`90b8382a1bb4ccdcf063102053fd75b8`)
|
||||||
|
case `3116`:
|
||||||
|
aeskey = []byte(`c24f74ca2820225badc01946dba4fdf7`)
|
||||||
|
default:
|
||||||
|
panic(`当前应用AppId暂不支持此功能`)
|
||||||
|
}
|
||||||
|
// 生成请求数据
|
||||||
|
tnow := time.Now()
|
||||||
|
pbyte, _ := json.Marshal(map[string]any{
|
||||||
|
`clienttime`: tnow.Unix(),
|
||||||
|
`token`: env.Config.Custom.Kg_token,
|
||||||
|
})
|
||||||
|
block, _ := aes.NewCipher(aeskey)
|
||||||
|
encrypter := cipher.NewCBCEncrypter(block, aeskey[block.BlockSize():])
|
||||||
|
padata := zcypt.PKCS7Padding(pbyte, block.BlockSize())
|
||||||
|
encrypted := make([]byte, len(padata))
|
||||||
|
encrypter.CryptBlocks(encrypted, padata)
|
||||||
|
encstr := zcypt.HexToString(encrypted)
|
||||||
|
bodys, _ := json.Marshal(map[string]any{
|
||||||
|
`t1`: 0,
|
||||||
|
`t2`: 0,
|
||||||
|
`p3`: encstr,
|
||||||
|
`userid`: env.Config.Custom.Kg_userId,
|
||||||
|
`clienttime_ms`: tnow.UnixMilli(),
|
||||||
|
})
|
||||||
|
params := map[string]string{
|
||||||
|
`dfid`: `-`,
|
||||||
|
`mid`: `20211008`,
|
||||||
|
`clientver`: env.Config.Custom.Kg_Client_Version,
|
||||||
|
`clienttime`: strconv.FormatInt(tnow.Unix(), 10),
|
||||||
|
`appid`: env.Config.Custom.Kg_Client_AppId,
|
||||||
|
}
|
||||||
|
headers := map[string]string{
|
||||||
|
`User-Agent`: `Android711-1070-10860-14-0-LOGIN-wifi`,
|
||||||
|
`KG-THash`: `7af653c`,
|
||||||
|
`KG-Rec`: `1`,
|
||||||
|
`KG-RC`: `1`,
|
||||||
|
}
|
||||||
|
// 请求对应接口
|
||||||
|
var res loginInfo
|
||||||
|
err = signRequest(
|
||||||
|
http.MethodPost,
|
||||||
|
`http://login.user.kugou.com/v4/login_by_token`,
|
||||||
|
bytesconv.BytesToString(bodys),
|
||||||
|
params, headers, &res,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New(`接口请求失败: ` + err.Error())
|
||||||
|
}
|
||||||
|
loger.Info(`获取数据成功`)
|
||||||
|
loger.Debug(`Resp: %+v`, res)
|
||||||
|
if res.ErrorCode != 0 {
|
||||||
|
return errors.New(`刷新登录失败: ` + strconv.Itoa(res.ErrorCode))
|
||||||
|
}
|
||||||
|
env.Config.Custom.Kg_token = res.Data.Token
|
||||||
|
env.Config.Custom.Kg_userId = strconv.Itoa(res.Data.Userid)
|
||||||
|
next := time.Date(tnow.Year(), tnow.Month(), tnow.Day()+25, 0, 0, 0, 0, tnow.Location())
|
||||||
|
env.Config.Custom.Kg_Refresh_Interval = next.Unix()
|
||||||
|
loger.Info(`刷新登录成功`)
|
||||||
|
return env.Cfg.Save(``)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
env.Inits.Add(func() {
|
||||||
|
if env.Config.Custom.Kg_token != `` {
|
||||||
|
if env.Config.Custom.Kg_Lite_Enable && env.Config.Custom.Kg_Client_AppId == `3116` {
|
||||||
|
env.Tasker.Add(`kg_litsign`, do_account_signin, 86000, true)
|
||||||
|
}
|
||||||
|
if env.Config.Custom.Kg_Refresh_Enable {
|
||||||
|
env.Tasker.Add(`kg_refresh`, login_by_token, 86000, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*if env.Config.Custom.Kg_Lite_Enable {
|
||||||
|
if env.Config.Custom.Kg_Client_AppId == `3116` && env.Config.Custom.Kg_token != `` {
|
||||||
|
env.Tasker.Add(`kg_litsign`, do_account_signin, 86000, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if env.Config.Custom.Kg_Refresh_Enable && env.Config.Custom.Kg_token != `` {
|
||||||
|
env.Tasker.Add(`kg_refresh`, login_by_token, 86000, true)
|
||||||
|
}*/
|
||||||
|
})
|
||||||
|
}
|
503
src/sources/custom/kg/types.go
Normal file
503
src/sources/custom/kg/types.go
Normal file
@ -0,0 +1,503 @@
|
|||||||
|
package kg
|
||||||
|
|
||||||
|
type (
|
||||||
|
musicInfo struct {
|
||||||
|
AlbumAudioID string `json:"album_audio_id"`
|
||||||
|
// AudioID string `json:"audio_id"`
|
||||||
|
// AuthorName string `json:"author_name"`
|
||||||
|
// OriAudioName string `json:"ori_audio_name"`
|
||||||
|
// OfficialSongname string `json:"official_songname"`
|
||||||
|
// Songname string `json:"songname"`
|
||||||
|
// Remark string `json:"remark"`
|
||||||
|
// SuffixAudioName string `json:"suffix_audio_name"`
|
||||||
|
// IsSearch string `json:"is_search"`
|
||||||
|
// IsOriginal string `json:"is_original"`
|
||||||
|
// IsPublish string `json:"is_publish"`
|
||||||
|
// MixsongType string `json:"mixsong_type"`
|
||||||
|
// Version string `json:"version"`
|
||||||
|
// Language string `json:"language"`
|
||||||
|
// Bpm string `json:"bpm"`
|
||||||
|
// BpmType string `json:"bpm_type"`
|
||||||
|
// BpmDesc string `json:"bpm_desc"`
|
||||||
|
// PublishDate string `json:"publish_date"`
|
||||||
|
// Extname string `json:"extname"`
|
||||||
|
AlbumInfo struct {
|
||||||
|
AlbumID string `json:"album_id"`
|
||||||
|
// AlbumName string `json:"album_name"`
|
||||||
|
// PublishDate string `json:"publish_date"`
|
||||||
|
// Category string `json:"category"`
|
||||||
|
// IsPublish string `json:"is_publish"`
|
||||||
|
// SizableCover string `json:"sizable_cover"`
|
||||||
|
} `json:"album_info"`
|
||||||
|
// Classification []struct {
|
||||||
|
// Usage string `json:"usage"`
|
||||||
|
// Status string `json:"status"`
|
||||||
|
// Level string `json:"level"`
|
||||||
|
// ResType string `json:"res_type"`
|
||||||
|
// Type string `json:"type"`
|
||||||
|
// ID string `json:"id"`
|
||||||
|
// ResID string `json:"res_id"`
|
||||||
|
// } `json:"classification"`
|
||||||
|
AudioInfo struct {
|
||||||
|
// IsFileHead string `json:"is_file_head"`
|
||||||
|
// IsFileHead320 string `json:"is_file_head_320"`
|
||||||
|
// AudioID string `json:"audio_id"`
|
||||||
|
// Hash string `json:"hash"`
|
||||||
|
// Filesize string `json:"filesize"`
|
||||||
|
// Timelength string `json:"timelength"`
|
||||||
|
// Bitrate string `json:"bitrate"`
|
||||||
|
Hash128 string `json:"hash_128"`
|
||||||
|
// Filesize128 string `json:"filesize_128"`
|
||||||
|
// Timelength128 string `json:"timelength_128"`
|
||||||
|
Hash320 string `json:"hash_320"`
|
||||||
|
// Filesize320 string `json:"filesize_320"`
|
||||||
|
// Timelength320 string `json:"timelength_320"`
|
||||||
|
HashFlac string `json:"hash_flac"`
|
||||||
|
// FilesizeFlac string `json:"filesize_flac"`
|
||||||
|
// TimelengthFlac string `json:"timelength_flac"`
|
||||||
|
// BitrateFlac string `json:"bitrate_flac"`
|
||||||
|
HashHigh string `json:"hash_high"`
|
||||||
|
// FilesizeHigh string `json:"filesize_high"`
|
||||||
|
// TimelengthHigh string `json:"timelength_high"`
|
||||||
|
// BitrateHigh string `json:"bitrate_high"`
|
||||||
|
HashSuper string `json:"hash_super"`
|
||||||
|
// FilesizeSuper string `json:"filesize_super"`
|
||||||
|
// TimelengthSuper string `json:"timelength_super"`
|
||||||
|
// BitrateSuper string `json:"bitrate_super"`
|
||||||
|
// HashVinylrecord string `json:"hash_vinylrecord"`
|
||||||
|
// FilesizeVinylrecord string `json:"filesize_vinylrecord"`
|
||||||
|
// TimelengthVinylrecord string `json:"timelength_vinylrecord"`
|
||||||
|
// BitrateVinylrecord string `json:"bitrate_vinylrecord"`
|
||||||
|
// HashMultichannel string `json:"hash_multichannel"`
|
||||||
|
// FilesizeMultichannel string `json:"filesize_multichannel"`
|
||||||
|
// TimelengthMultichannel string `json:"timelength_multichannel"`
|
||||||
|
// BitrateMultichannel string `json:"bitrate_multichannel"`
|
||||||
|
// HashDolby448 string `json:"hash_dolby_448"`
|
||||||
|
// FilesizeDolby448 string `json:"filesize_dolby_448"`
|
||||||
|
// TimelengthDolby448 string `json:"timelength_dolby_448"`
|
||||||
|
// BitrateDolby448 string `json:"bitrate_dolby_448"`
|
||||||
|
// HashDolby640 string `json:"hash_dolby_640"`
|
||||||
|
// FilesizeDolby640 string `json:"filesize_dolby_640"`
|
||||||
|
// TimelengthDolby640 string `json:"timelength_dolby_640"`
|
||||||
|
// BitrateDolby640 string `json:"bitrate_dolby_640"`
|
||||||
|
// HashDolby768 string `json:"hash_dolby_768"`
|
||||||
|
// FilesizeDolby768 string `json:"filesize_dolby_768"`
|
||||||
|
// TimelengthDolby768 string `json:"timelength_dolby_768"`
|
||||||
|
// BitrateDolby768 string `json:"bitrate_dolby_768"`
|
||||||
|
// AudioGroupID string `json:"audio_group_id"`
|
||||||
|
// ExtnameSuper string `json:"extname_super"`
|
||||||
|
// Extname string `json:"extname"`
|
||||||
|
// FailProcess int `json:"fail_process"`
|
||||||
|
// PayType int `json:"pay_type"`
|
||||||
|
// Type string `json:"type"`
|
||||||
|
// OldCpy int `json:"old_cpy"`
|
||||||
|
// Privilege string `json:"privilege"`
|
||||||
|
// Privilege128 string `json:"privilege_128"`
|
||||||
|
// Privilege320 string `json:"privilege_320"`
|
||||||
|
// PrivilegeFlac string `json:"privilege_flac"`
|
||||||
|
// PrivilegeHigh string `json:"privilege_high"`
|
||||||
|
// PrivilegeSuper string `json:"privilege_super"`
|
||||||
|
// PrivilegeVinylrecord string `json:"privilege_vinylrecord"`
|
||||||
|
// PrivilegeMultichannel string `json:"privilege_multichannel"`
|
||||||
|
// PrivilegeDolby448 string `json:"privilege_dolby_448"`
|
||||||
|
// PrivilegeDolby640 string `json:"privilege_dolby_640"`
|
||||||
|
// PrivilegeDolby768 string `json:"privilege_dolby_768"`
|
||||||
|
// TransParam struct {
|
||||||
|
// HashOffset struct {
|
||||||
|
// StartByte int `json:"start_byte"`
|
||||||
|
// EndByte int `json:"end_byte"`
|
||||||
|
// StartMs int `json:"start_ms"`
|
||||||
|
// EndMs int `json:"end_ms"`
|
||||||
|
// OffsetHash string `json:"offset_hash"`
|
||||||
|
// FileType int `json:"file_type"`
|
||||||
|
// ClipHash string `json:"clip_hash"`
|
||||||
|
// } `json:"hash_offset"`
|
||||||
|
// MusicpackAdvance int `json:"musicpack_advance"`
|
||||||
|
// PayBlockTpl int `json:"pay_block_tpl"`
|
||||||
|
// Display int `json:"display"`
|
||||||
|
// DisplayRate int `json:"display_rate"`
|
||||||
|
// CpyGrade int `json:"cpy_grade"`
|
||||||
|
// CpyLevel int `json:"cpy_level"`
|
||||||
|
// Cid int `json:"cid"`
|
||||||
|
// CpyAttr0 int `json:"cpy_attr0"`
|
||||||
|
// Classmap struct {
|
||||||
|
// Attr0 int `json:"attr0"`
|
||||||
|
// } `json:"classmap"`
|
||||||
|
// InitPubDay int `json:"init_pub_day"`
|
||||||
|
// Qualitymap struct {
|
||||||
|
// Attr0 int `json:"attr0"`
|
||||||
|
// } `json:"qualitymap"`
|
||||||
|
// Language string `json:"language"`
|
||||||
|
// } `json:"trans_param"`
|
||||||
|
} `json:"audio_info"`
|
||||||
|
}
|
||||||
|
playInfo struct {
|
||||||
|
// Hash string `json:"hash"`
|
||||||
|
// Classmap struct {
|
||||||
|
// Attr0 int `json:"attr0"`
|
||||||
|
// } `json:"classmap"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
// Volume float64 `json:"volume"`
|
||||||
|
// StdHashTime int `json:"std_hash_time"`
|
||||||
|
URL []string `json:"url"`
|
||||||
|
// StdHash string `json:"std_hash"`
|
||||||
|
// TransParam struct {
|
||||||
|
// Display int `json:"display"`
|
||||||
|
// DisplayRate int `json:"display_rate"`
|
||||||
|
// } `json:"trans_param"`
|
||||||
|
// FileHead int `json:"fileHead"`
|
||||||
|
// VolumePeak float64 `json:"volume_peak"`
|
||||||
|
// BitRate int `json:"bitRate"`
|
||||||
|
// TimeLength int `json:"timeLength"`
|
||||||
|
// VolumeGain int `json:"volume_gain"`
|
||||||
|
// Q int `json:"q"`
|
||||||
|
// FileName string `json:"fileName"`
|
||||||
|
// ExtName string `json:"extName"`
|
||||||
|
// FileSize int `json:"fileSize"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type rankInfo struct {
|
||||||
|
Data struct {
|
||||||
|
// Timestamp int `json:"timestamp"`
|
||||||
|
// Total int `json:"total"`
|
||||||
|
Info []struct {
|
||||||
|
// LastSort int `json:"last_sort"`
|
||||||
|
// Authors []struct {
|
||||||
|
// SizableAvatar string `json:"sizable_avatar"`
|
||||||
|
// IsPublish int `json:"is_publish"`
|
||||||
|
// AuthorName string `json:"author_name"`
|
||||||
|
// AuthorID int `json:"author_id"`
|
||||||
|
// } `json:"authors"`
|
||||||
|
// RankCount int `json:"rank_count"`
|
||||||
|
// RankIDPublishDate string `json:"rank_id_publish_date"`
|
||||||
|
// Songname string `json:"songname"`
|
||||||
|
// TopicURL320 string `json:"topic_url_320"`
|
||||||
|
// Sqhash string `json:"sqhash"`
|
||||||
|
// FailProcess int `json:"fail_process"`
|
||||||
|
// PayType int `json:"pay_type"`
|
||||||
|
// RecommendReason string `json:"recommend_reason"`
|
||||||
|
// RpType string `json:"rp_type"`
|
||||||
|
// AlbumID string `json:"album_id"`
|
||||||
|
// PrivilegeHigh int `json:"privilege_high"`
|
||||||
|
// TopicURLSq string `json:"topic_url_sq"`
|
||||||
|
// RankCid int `json:"rank_cid"`
|
||||||
|
// Inlist int `json:"inlist"`
|
||||||
|
// Three20Filesize int `json:"320filesize"`
|
||||||
|
// PkgPrice320 int `json:"pkg_price_320"`
|
||||||
|
// Feetype int `json:"feetype"`
|
||||||
|
// Price320 int `json:"price_320"`
|
||||||
|
// DurationHigh int `json:"duration_high"`
|
||||||
|
// FailProcess320 int `json:"fail_process_320"`
|
||||||
|
// Zone string `json:"zone"`
|
||||||
|
// TopicURL string `json:"topic_url"`
|
||||||
|
// RpPublish int `json:"rp_publish"`
|
||||||
|
// TransObj struct {
|
||||||
|
// RankShowSort int `json:"rank_show_sort"`
|
||||||
|
// } `json:"trans_obj"`
|
||||||
|
// Hash string `json:"hash"`
|
||||||
|
// Sqfilesize int `json:"sqfilesize"`
|
||||||
|
// Sqprivilege int `json:"sqprivilege"`
|
||||||
|
// PayTypeSq int `json:"pay_type_sq"`
|
||||||
|
// Bitrate int `json:"bitrate"`
|
||||||
|
// PkgPriceSq int `json:"pkg_price_sq"`
|
||||||
|
// HasAccompany int `json:"has_accompany"`
|
||||||
|
// Musical interface{} `json:"musical"`
|
||||||
|
// PayType320 int `json:"pay_type_320"`
|
||||||
|
// Issue int `json:"issue"`
|
||||||
|
// ExtnameSuper string `json:"extname_super"`
|
||||||
|
// DurationSuper int `json:"duration_super"`
|
||||||
|
// BitrateSuper int `json:"bitrate_super"`
|
||||||
|
// HashHigh string `json:"hash_high"`
|
||||||
|
// Duration int `json:"duration"`
|
||||||
|
// Three20Hash string `json:"320hash"`
|
||||||
|
// PriceSq int `json:"price_sq"`
|
||||||
|
// OldCpy int `json:"old_cpy"`
|
||||||
|
// AlbumAudioID int `json:"album_audio_id"`
|
||||||
|
// M4Afilesize int `json:"m4afilesize"`
|
||||||
|
// PkgPrice int `json:"pkg_price"`
|
||||||
|
// First int `json:"first"`
|
||||||
|
// AudioID int `json:"audio_id"`
|
||||||
|
// HashSuper string `json:"hash_super"`
|
||||||
|
// Addtime string `json:"addtime"`
|
||||||
|
// FilesizeHigh int `json:"filesize_high"`
|
||||||
|
// Price int `json:"price"`
|
||||||
|
// Privilege int `json:"privilege"`
|
||||||
|
// AlbumSizableCover string `json:"album_sizable_cover"`
|
||||||
|
// Mvdata []struct {
|
||||||
|
// Typ int `json:"typ"`
|
||||||
|
// Trk string `json:"trk"`
|
||||||
|
// Hash string `json:"hash"`
|
||||||
|
// ID string `json:"id"`
|
||||||
|
// } `json:"mvdata,omitempty"`
|
||||||
|
// Sort int `json:"sort"`
|
||||||
|
// TransParam struct {
|
||||||
|
// CpyLevel int `json:"cpy_level"`
|
||||||
|
// Classmap struct {
|
||||||
|
// Attr0 int `json:"attr0"`
|
||||||
|
// } `json:"classmap"`
|
||||||
|
// CpyGrade int `json:"cpy_grade"`
|
||||||
|
// Qualitymap struct {
|
||||||
|
// Attr0 int `json:"attr0"`
|
||||||
|
// } `json:"qualitymap"`
|
||||||
|
// PayBlockTpl int `json:"pay_block_tpl"`
|
||||||
|
// Cid int `json:"cid"`
|
||||||
|
// Language string `json:"language"`
|
||||||
|
// HashMultitrack string `json:"hash_multitrack"`
|
||||||
|
// CpyAttr0 int `json:"cpy_attr0"`
|
||||||
|
// Ipmap struct {
|
||||||
|
// Attr0 int `json:"attr0"`
|
||||||
|
// } `json:"ipmap"`
|
||||||
|
// AppidBlock string `json:"appid_block"`
|
||||||
|
// MusicpackAdvance int `json:"musicpack_advance"`
|
||||||
|
// Display int `json:"display"`
|
||||||
|
// DisplayRate int `json:"display_rate"`
|
||||||
|
// } `json:"trans_param"`
|
||||||
|
// FilesizeSuper int `json:"filesize_super"`
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
// BitrateHigh int `json:"bitrate_high"`
|
||||||
|
// Remark string `json:"remark"`
|
||||||
|
// Extname string `json:"extname"`
|
||||||
|
// Filesize int `json:"filesize"`
|
||||||
|
// Isfirst int `json:"isfirst"`
|
||||||
|
// Mvhash string `json:"mvhash"`
|
||||||
|
// Three20Privilege int `json:"320privilege"`
|
||||||
|
// PrivilegeSuper int `json:"privilege_super"`
|
||||||
|
// FailProcessSq int `json:"fail_process_sq"`
|
||||||
|
} `json:"info"`
|
||||||
|
} `json:"data"`
|
||||||
|
// Errcode int `json:"errcode"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type searchInfo struct {
|
||||||
|
Data struct {
|
||||||
|
// AlgPath string `json:"AlgPath"`
|
||||||
|
// Aggregation struct {
|
||||||
|
// } `json:"aggregation"`
|
||||||
|
// Allowerr int `json:"allowerr"`
|
||||||
|
// Chinesecount int `json:"chinesecount"`
|
||||||
|
// Correctionforce int `json:"correctionforce"`
|
||||||
|
// Correctionrelate string `json:"correctionrelate"`
|
||||||
|
// Correctionsubject string `json:"correctionsubject"`
|
||||||
|
// Correctiontip string `json:"correctiontip"`
|
||||||
|
// Correctiontype int `json:"correctiontype"`
|
||||||
|
// From int `json:"from"`
|
||||||
|
// Isshareresult int `json:"isshareresult"`
|
||||||
|
// Istag int `json:"istag"`
|
||||||
|
// Istagresult int `json:"istagresult"`
|
||||||
|
Lists []struct {
|
||||||
|
// A320Privilege int `json:"A320Privilege"`
|
||||||
|
// ASQPrivilege int `json:"ASQPrivilege"`
|
||||||
|
// Accompany int `json:"Accompany"`
|
||||||
|
// AlbumAux string `json:"AlbumAux"`
|
||||||
|
// AlbumID string `json:"AlbumID"`
|
||||||
|
// AlbumName string `json:"AlbumName"`
|
||||||
|
// AlbumPrivilege int `json:"AlbumPrivilege"`
|
||||||
|
// AudioCdn int `json:"AudioCdn"`
|
||||||
|
// Audioid int `json:"Audioid"`
|
||||||
|
// Auxiliary string `json:"Auxiliary"`
|
||||||
|
// Bitrate int `json:"Bitrate"`
|
||||||
|
// Category int `json:"Category"`
|
||||||
|
// Duration int `json:"Duration"`
|
||||||
|
// ExtName string `json:"ExtName"`
|
||||||
|
// FailProcess int `json:"FailProcess"`
|
||||||
|
// FileHash string `json:"FileHash"`
|
||||||
|
// FileName string `json:"FileName"`
|
||||||
|
// FileSize int `json:"FileSize"`
|
||||||
|
// FoldType int `json:"FoldType"`
|
||||||
|
// Grp []interface{} `json:"Grp"`
|
||||||
|
// HQBitrate int `json:"HQBitrate"`
|
||||||
|
// HQDuration int `json:"HQDuration"`
|
||||||
|
// HQExtName string `json:"HQExtName"`
|
||||||
|
// HQFailProcess int `json:"HQFailProcess"`
|
||||||
|
// HQFileHash string `json:"HQFileHash"`
|
||||||
|
// HQFileSize int `json:"HQFileSize"`
|
||||||
|
// HQPayType int `json:"HQPayType"`
|
||||||
|
// HQPkgPrice int `json:"HQPkgPrice"`
|
||||||
|
// HQPrice int `json:"HQPrice"`
|
||||||
|
// HQPrivilege int `json:"HQPrivilege"`
|
||||||
|
// HasAlbum int `json:"HasAlbum"`
|
||||||
|
// HeatLevel int `json:"HeatLevel"`
|
||||||
|
// HiFiQuality int `json:"HiFiQuality"`
|
||||||
|
// ID string `json:"ID"`
|
||||||
|
// Image string `json:"Image"`
|
||||||
|
// IsOriginal int `json:"IsOriginal"`
|
||||||
|
// M4ASize int `json:"M4aSize"`
|
||||||
|
// MatchFlag int `json:"MatchFlag"`
|
||||||
|
MixSongID string `json:"MixSongID"`
|
||||||
|
// MvHash string `json:"MvHash"`
|
||||||
|
// MvTrac int `json:"MvTrac"`
|
||||||
|
// MvType int `json:"MvType"`
|
||||||
|
// OldCpy int `json:"OldCpy"`
|
||||||
|
// OriOtherName string `json:"OriOtherName"`
|
||||||
|
// OriSongName string `json:"OriSongName"`
|
||||||
|
// OtherName string `json:"OtherName"`
|
||||||
|
// OwnerCount int `json:"OwnerCount"`
|
||||||
|
// PayType int `json:"PayType"`
|
||||||
|
// PkgPrice int `json:"PkgPrice"`
|
||||||
|
// Price int `json:"Price"`
|
||||||
|
// Privilege int `json:"Privilege"`
|
||||||
|
// Publish int `json:"Publish"`
|
||||||
|
// PublishAge int `json:"PublishAge"`
|
||||||
|
// PublishTime string `json:"PublishTime"`
|
||||||
|
// QualityLevel int `json:"QualityLevel"`
|
||||||
|
// RankID int `json:"RankId"`
|
||||||
|
// Res struct {
|
||||||
|
// FailProcess int `json:"FailProcess"`
|
||||||
|
// PayType int `json:"PayType"`
|
||||||
|
// PkgPrice int `json:"PkgPrice"`
|
||||||
|
// Price int `json:"Price"`
|
||||||
|
// Privilege int `json:"Privilege"`
|
||||||
|
// } `json:"Res"`
|
||||||
|
// ResBitrate int `json:"ResBitrate"`
|
||||||
|
// ResDuration int `json:"ResDuration"`
|
||||||
|
// ResFileHash string `json:"ResFileHash"`
|
||||||
|
// ResFileSize int `json:"ResFileSize"`
|
||||||
|
// SQBitrate int `json:"SQBitrate"`
|
||||||
|
// SQDuration int `json:"SQDuration"`
|
||||||
|
// SQExtName string `json:"SQExtName"`
|
||||||
|
// SQFailProcess int `json:"SQFailProcess"`
|
||||||
|
// SQFileHash string `json:"SQFileHash"`
|
||||||
|
// SQFileSize int `json:"SQFileSize"`
|
||||||
|
// SQPayType int `json:"SQPayType"`
|
||||||
|
// SQPkgPrice int `json:"SQPkgPrice"`
|
||||||
|
// SQPrice int `json:"SQPrice"`
|
||||||
|
// SQPrivilege int `json:"SQPrivilege"`
|
||||||
|
// Scid int `json:"Scid"`
|
||||||
|
// ShowingFlag int `json:"ShowingFlag"`
|
||||||
|
// SingerID []int `json:"SingerId"`
|
||||||
|
// SingerName string `json:"SingerName"`
|
||||||
|
// Singers []struct {
|
||||||
|
// ID int `json:"id"`
|
||||||
|
// IPID int `json:"ip_id"`
|
||||||
|
// Name string `json:"name"`
|
||||||
|
// } `json:"Singers"`
|
||||||
|
// SongLabel string `json:"SongLabel"`
|
||||||
|
// SongName string `json:"SongName"`
|
||||||
|
// Source string `json:"Source"`
|
||||||
|
// SourceID int `json:"SourceID"`
|
||||||
|
// Suffix string `json:"Suffix"`
|
||||||
|
// SuperBitrate int `json:"SuperBitrate"`
|
||||||
|
// SuperDuration int `json:"SuperDuration"`
|
||||||
|
// SuperExtName string `json:"SuperExtName"`
|
||||||
|
// SuperFileHash string `json:"SuperFileHash"`
|
||||||
|
// SuperFileSize int `json:"SuperFileSize"`
|
||||||
|
// TagContent string `json:"TagContent"`
|
||||||
|
// TagDetails []struct {
|
||||||
|
// Content string `json:"content"`
|
||||||
|
// Rankid int `json:"rankid"`
|
||||||
|
// Type int `json:"type"`
|
||||||
|
// Version int `json:"version"`
|
||||||
|
// } `json:"TagDetails"`
|
||||||
|
// TopID int `json:"TopID"`
|
||||||
|
// TopicRemark string `json:"TopicRemark"`
|
||||||
|
// TopicURL string `json:"TopicUrl"`
|
||||||
|
// Type string `json:"Type"`
|
||||||
|
// Uploader string `json:"Uploader"`
|
||||||
|
// UploaderContent string `json:"UploaderContent"`
|
||||||
|
// MvTotal int `json:"mvTotal"`
|
||||||
|
// Mvdata []struct {
|
||||||
|
// Hash string `json:"hash"`
|
||||||
|
// ID string `json:"id"`
|
||||||
|
// Trk string `json:"trk"`
|
||||||
|
// Typ int `json:"typ"`
|
||||||
|
// } `json:"mvdata"`
|
||||||
|
// RecommendType int `json:"recommend_type"`
|
||||||
|
// TransParam struct {
|
||||||
|
// AppidBlock string `json:"appid_block"`
|
||||||
|
// Cid int `json:"cid"`
|
||||||
|
// Classmap struct {
|
||||||
|
// Attr0 int `json:"attr0"`
|
||||||
|
// } `json:"classmap"`
|
||||||
|
// CpyAttr0 int `json:"cpy_attr0"`
|
||||||
|
// CpyGrade int `json:"cpy_grade"`
|
||||||
|
// CpyLevel int `json:"cpy_level"`
|
||||||
|
// Display int `json:"display"`
|
||||||
|
// DisplayRate int `json:"display_rate"`
|
||||||
|
// HashMultitrack string `json:"hash_multitrack"`
|
||||||
|
// Ipmap struct {
|
||||||
|
// Attr0 int `json:"attr0"`
|
||||||
|
// } `json:"ipmap"`
|
||||||
|
// Language string `json:"language"`
|
||||||
|
// MusicpackAdvance int `json:"musicpack_advance"`
|
||||||
|
// PayBlockTpl int `json:"pay_block_tpl"`
|
||||||
|
// Qualitymap struct {
|
||||||
|
// Attr0 int `json:"attr0"`
|
||||||
|
// } `json:"qualitymap"`
|
||||||
|
// SongnameSuffix string `json:"songname_suffix"`
|
||||||
|
// } `json:"trans_param"`
|
||||||
|
// Vvid string `json:"vvid"`
|
||||||
|
} `json:"lists"`
|
||||||
|
// Page int `json:"page"`
|
||||||
|
// Pagesize int `json:"pagesize"`
|
||||||
|
// Searchfull int `json:"searchfull"`
|
||||||
|
// SecAggre struct {
|
||||||
|
// } `json:"sec_aggre"`
|
||||||
|
// SecAggreV2 []interface{} `json:"sec_aggre_v2"`
|
||||||
|
// SectagInfo struct {
|
||||||
|
// IsSectag int `json:"is_sectag"`
|
||||||
|
// } `json:"sectag_info"`
|
||||||
|
// Size int `json:"size"`
|
||||||
|
// Subjecttype int `json:"subjecttype"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
} `json:"data"`
|
||||||
|
// ErrorCode int `json:"error_code"`
|
||||||
|
ErrorMsg string `json:"error_msg"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type refreshInfo struct {
|
||||||
|
// Data string `json:"data"`
|
||||||
|
ErrorCode int `json:"error_code"`
|
||||||
|
ErrorMsg string `json:"error_msg"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type loginInfo struct {
|
||||||
|
Data struct {
|
||||||
|
// ArttoyAvatar string `json:"arttoy_avatar"`
|
||||||
|
// BcCode string `json:"bc_code"`
|
||||||
|
// Birthday string `json:"birthday"`
|
||||||
|
// BirthdayMmdd string `json:"birthday_mmdd"`
|
||||||
|
// Exp int `json:"exp"`
|
||||||
|
// IsVip int `json:"is_vip"`
|
||||||
|
// MBeginTime string `json:"m_begin_time"`
|
||||||
|
// MEndTime string `json:"m_end_time"`
|
||||||
|
// MIsOld int `json:"m_is_old"`
|
||||||
|
// MType int `json:"m_type"`
|
||||||
|
// Mobile int `json:"mobile"`
|
||||||
|
// Nickname string `json:"nickname"`
|
||||||
|
// Pic string `json:"pic"`
|
||||||
|
// Qq int `json:"qq"`
|
||||||
|
// RegTime string `json:"reg_time"`
|
||||||
|
// RoamBeginTime string `json:"roam_begin_time"`
|
||||||
|
// RoamEndTime string `json:"roam_end_time"`
|
||||||
|
// RoamList struct {
|
||||||
|
// } `json:"roam_list"`
|
||||||
|
// RoamType int `json:"roam_type"`
|
||||||
|
// Score int `json:"score"`
|
||||||
|
// Servertime string `json:"servertime"`
|
||||||
|
// Sex int `json:"sex"`
|
||||||
|
// SuVipBeginTime string `json:"su_vip_begin_time"`
|
||||||
|
// SuVipClearday string `json:"su_vip_clearday"`
|
||||||
|
// SuVipEndTime string `json:"su_vip_end_time"`
|
||||||
|
// SuVipYEndtime string `json:"su_vip_y_endtime"`
|
||||||
|
// T1 string `json:"t1"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
// TotpServerTimestamp int `json:"totp_server_timestamp"`
|
||||||
|
// UserType int `json:"user_type"`
|
||||||
|
// UserYType int `json:"user_y_type"`
|
||||||
|
Userid int `json:"userid"`
|
||||||
|
// Username string `json:"username"`
|
||||||
|
// VipBeginTime string `json:"vip_begin_time"`
|
||||||
|
// VipEndTime string `json:"vip_end_time"`
|
||||||
|
// VipToken string `json:"vip_token"`
|
||||||
|
// VipType int `json:"vip_type"`
|
||||||
|
// Wechat int `json:"wechat"`
|
||||||
|
// YType int `json:"y_type"`
|
||||||
|
} `json:"data"`
|
||||||
|
ErrorCode int `json:"error_code"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
}
|
123
src/sources/custom/kg/utils.go
Normal file
123
src/sources/custom/kg/utils.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package kg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"lx-source/src/env"
|
||||||
|
"lx-source/src/sources"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ZxwyWebSite/ztool"
|
||||||
|
"github.com/ZxwyWebSite/ztool/x/slices"
|
||||||
|
"github.com/ZxwyWebSite/ztool/zcypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// qualityHashMap = map[string]string{
|
||||||
|
// sources.Q_128k: `hash_128`,
|
||||||
|
// sources.Q_320k: `hash_320`,
|
||||||
|
// sources.Q_flac: `hash_flac`,
|
||||||
|
// sources.Q_fl24: `hash_high`,
|
||||||
|
// }
|
||||||
|
qualityMap = map[string]string{
|
||||||
|
sources.Q_128k: `128`,
|
||||||
|
sources.Q_320k: `320`,
|
||||||
|
sources.Q_flac: sources.Q_flac,
|
||||||
|
sources.Q_fl24: `high`,
|
||||||
|
|
||||||
|
sources.Q_master: `viper_atmos`,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// signkey = `OIlwieks28dk2k092lksi2UIkp`
|
||||||
|
// pidversec = `57ae12eb6890223e355ccfcb74edf70d`
|
||||||
|
// clientver = `12029`
|
||||||
|
url = `https://gateway.kugou.com/v5/url`
|
||||||
|
// appid = `1005`
|
||||||
|
mid = `211008`
|
||||||
|
)
|
||||||
|
|
||||||
|
func sortDict(dictionary map[string]string) ([]string, int) {
|
||||||
|
length := len(dictionary)
|
||||||
|
var keys = make([]string, 0, length)
|
||||||
|
for k := range dictionary {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
slices.Sort(keys)
|
||||||
|
return keys, length
|
||||||
|
}
|
||||||
|
|
||||||
|
// func sign(params map[string]string, body string) string {
|
||||||
|
// keys, lens := sortDict(params)
|
||||||
|
// var b strings.Builder
|
||||||
|
// for i := 0; i < lens; i++ {
|
||||||
|
// b.WriteString(keys[i])
|
||||||
|
// b.WriteByte('=')
|
||||||
|
// b.WriteString(params[keys[i]])
|
||||||
|
// }
|
||||||
|
// // b.WriteString(body)
|
||||||
|
// return zcypt.MD5EncStr(ztool.Str_FastConcat(signkey, b.String(), signkey))
|
||||||
|
// }
|
||||||
|
|
||||||
|
func signRequest(method string, url string, body string, params, headers map[string]string, out any) error {
|
||||||
|
keys, lens := sortDict(params)
|
||||||
|
// buildSignatureParams
|
||||||
|
var b strings.Builder
|
||||||
|
for i := 0; i < lens; i++ {
|
||||||
|
b.WriteString(keys[i])
|
||||||
|
b.WriteByte('=')
|
||||||
|
b.WriteString(params[keys[i]])
|
||||||
|
}
|
||||||
|
b.WriteString(body)
|
||||||
|
// buildRequestParams
|
||||||
|
var c strings.Builder
|
||||||
|
for j := 0; j < lens; j++ {
|
||||||
|
c.WriteString(keys[j])
|
||||||
|
c.WriteByte('=')
|
||||||
|
c.WriteString(params[keys[j]])
|
||||||
|
c.WriteByte('&')
|
||||||
|
}
|
||||||
|
c.WriteString(`signature`)
|
||||||
|
c.WriteByte('=')
|
||||||
|
c.WriteString(zcypt.MD5EncStr(ztool.Str_FastConcat(
|
||||||
|
env.Config.Custom.Kg_Client_SignKey,
|
||||||
|
b.String(), env.Config.Custom.Kg_Client_SignKey,
|
||||||
|
)))
|
||||||
|
|
||||||
|
url = ztool.Str_FastConcat(url, `?`, c.String())
|
||||||
|
// ztool.Cmd_FastPrintln(url)
|
||||||
|
return ztool.Net_Request(
|
||||||
|
method, url, strings.NewReader(body),
|
||||||
|
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeader(headers)},
|
||||||
|
[]ztool.Net_ResHandlerFunc{ //func(res *http.Response) error {
|
||||||
|
// body, err := io.ReadAll(res.Body)
|
||||||
|
// fmt.Printf("%s, %s, %s\n", body, err, res.Status)
|
||||||
|
// return ztool.Err_EsContinue
|
||||||
|
// },
|
||||||
|
ztool.Net_ResToStruct(out),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getKey(hash_ string) string {
|
||||||
|
return zcypt.MD5EncStr(ztool.Str_FastConcat(
|
||||||
|
strings.ToLower(hash_), env.Config.Custom.Kg_Client_PidVerSec,
|
||||||
|
env.Config.Custom.Kg_Client_AppId, mid, env.Config.Custom.Kg_userId,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析版权字段 (Copilot)
|
||||||
|
/*
|
||||||
|
// 定义一个函数,接受一个整数作为参数,返回一个布尔值的切片,表示每一位的状态
|
||||||
|
依次为: 下载是否付费 \ 下载是否禁止 \ 播放是否付费 \ 播放是否禁止
|
||||||
|
https://open.kugou.com/docs/open-player/#/android-sdk?v=1&id=%e6%ad%8c%e6%9b%b2%e5%ad%97%e6%ae%b5%e8%a7%a3%e6%9e%90
|
||||||
|
*/
|
||||||
|
// func parsePrivilege(privilege int) []bool {
|
||||||
|
// result := make([]bool, 4) // 创建一个长度为4的切片
|
||||||
|
// for i := 0; i < 4; i++ {
|
||||||
|
// // 用位运算符&来判断每一位是否为1,如果是则将对应的切片元素设为true
|
||||||
|
// if privilege&(1<<i) != 0 {
|
||||||
|
// result[i] = true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return result
|
||||||
|
// }
|
@ -9,6 +9,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
|
||||||
"github.com/ZxwyWebSite/ztool/x/bytesconv"
|
"github.com/ZxwyWebSite/ztool/x/bytesconv"
|
||||||
|
"github.com/ZxwyWebSite/ztool/zcypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 常量和数组
|
// 常量和数组
|
||||||
@ -250,8 +251,9 @@ func encrypt(msg []byte, key []byte) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// base64编码函数
|
// base64编码函数
|
||||||
func Base64_encrypt(msg string) string {
|
func base64_encrypt(msg string) string {
|
||||||
b1 := encrypt(bytesconv.StringToBytes(msg), SECRET_KEY)
|
b1 := encrypt(bytesconv.StringToBytes(msg), SECRET_KEY)
|
||||||
s := base64.StdEncoding.EncodeToString(b1)
|
return zcypt.Base64ToString(base64.StdEncoding, b1)
|
||||||
return s //strings.ReplaceAll(s, "\n", ``)
|
// s := base64.StdEncoding.EncodeToString(b1)
|
||||||
|
// return s //strings.ReplaceAll(s, "\n", ``)
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
package kw
|
package kw
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"lx-source/src/env"
|
"lx-source/src/env"
|
||||||
"lx-source/src/sources"
|
"lx-source/src/sources"
|
||||||
"lx-source/src/sources/custom/utils"
|
"lx-source/src/sources/custom/utils"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ZxwyWebSite/ztool"
|
"github.com/ZxwyWebSite/ztool"
|
||||||
|
"github.com/ZxwyWebSite/ztool/x/bytesconv"
|
||||||
|
"github.com/ZxwyWebSite/ztool/zcypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -19,6 +23,7 @@ var (
|
|||||||
|
|
||||||
parsemod bool
|
parsemod bool
|
||||||
convtype string
|
convtype string
|
||||||
|
desource string
|
||||||
// desParse func([]byte, *playInfo) error
|
// desParse func([]byte, *playInfo) error
|
||||||
// desParse func(any) ztool.Net_ResHandlerFunc
|
// desParse func(any) ztool.Net_ResHandlerFunc
|
||||||
)
|
)
|
||||||
@ -28,7 +33,7 @@ func init() {
|
|||||||
loger := env.Loger.NewGroup(`KwInit`)
|
loger := env.Loger.NewGroup(`KwInit`)
|
||||||
switch env.Config.Custom.Kw_Mode {
|
switch env.Config.Custom.Kw_Mode {
|
||||||
case `0`, `bdapi`:
|
case `0`, `bdapi`:
|
||||||
loger.Debug(`Use bdapi`)
|
loger.Debug(`use bdapi`)
|
||||||
if ztool.Chk_IsNilStr(
|
if ztool.Chk_IsNilStr(
|
||||||
env.Config.Custom.Kw_Bd_Uid,
|
env.Config.Custom.Kw_Bd_Uid,
|
||||||
env.Config.Custom.Kw_Bd_Token,
|
env.Config.Custom.Kw_Bd_Token,
|
||||||
@ -36,36 +41,47 @@ func init() {
|
|||||||
) {
|
) {
|
||||||
loger.Fatal(`使用bdapi且验证参数为空`)
|
loger.Fatal(`使用bdapi且验证参数为空`)
|
||||||
}
|
}
|
||||||
|
// bdheader[`token`] = env.Config.Custom.Kw_Bd_Token
|
||||||
bdheader[`uid`] = env.Config.Custom.Kw_Bd_Uid
|
bdheader[`uid`] = env.Config.Custom.Kw_Bd_Uid
|
||||||
bdheader[`devId`] = env.Config.Custom.Kw_Bd_DevId
|
bdheader[`devId`] = env.Config.Custom.Kw_Bd_DevId
|
||||||
kw_pool = &sync.Pool{New: func() any { return new(kwApi_Song) }}
|
kw_pool = &sync.Pool{New: func() any { return new(kwApi_Song) }}
|
||||||
Url = bdapi
|
Url = bdapi
|
||||||
case `1`, `kwdes`:
|
case `1`, `kwdes`:
|
||||||
|
Url = kwdes
|
||||||
switch env.Config.Custom.Kw_Des_Type {
|
switch env.Config.Custom.Kw_Des_Type {
|
||||||
case `0`, `text`:
|
case `0`, `text`:
|
||||||
loger.Debug(`Use kwdes_text`)
|
loger.Debug(`use kwdes text`)
|
||||||
convtype = `convert_url2`
|
convtype = `convert_url2`
|
||||||
// desParse = txtParse
|
// desParse = txtParse
|
||||||
case `1`, `json`:
|
case `1`, `json`:
|
||||||
loger.Debug(`Use kwdes_json`)
|
loger.Debug(`use kwdes json`)
|
||||||
convtype = `convert_url_with_sign`
|
convtype = `convert_url_with_sign`
|
||||||
// desParse = ztool.Net_ResToStruct
|
// desParse = ztool.Net_ResToStruct
|
||||||
parsemod = true
|
parsemod = true
|
||||||
|
case `2`, `anti`:
|
||||||
|
loger.Debug(`use kwdes anti`)
|
||||||
|
Url = manti
|
||||||
default:
|
default:
|
||||||
loger.Fatal(`未定义的返回格式,请检查配置 [Custom].Kw_Des_Type`)
|
loger.Fatal(`未定义的返回格式,请检查配置 [Custom].Kw_Des_Type`)
|
||||||
}
|
}
|
||||||
desheader[`User-Agent`] = env.Config.Custom.Kw_Des_Header
|
desheader[`User-Agent`] = env.Config.Custom.Kw_Des_Header
|
||||||
kw_pool = &sync.Pool{New: func() any { return new(playInfo) }}
|
kw_pool = &sync.Pool{New: func() any { return new(playInfo) }}
|
||||||
Url = kwdes
|
if env.Config.Custom.Kw_Des_Source != `` {
|
||||||
|
desource = env.Config.Custom.Kw_Des_Source
|
||||||
|
} else {
|
||||||
|
dec, _ := zcypt.HexDecode([]byte{0x36, 0x62, 0x37, 0x37, 0x37, 0x30, 0x36, 0x63, 0x36, 0x31, 0x37, 0x39, 0x36, 0x35, 0x37, 0x32, 0x36, 0x38, 0x36, 0x34, 0x35, 0x66, 0x36, 0x31, 0x37, 0x32, 0x35, 0x66, 0x33, 0x35, 0x32, 0x65, 0x33, 0x31, 0x32, 0x65, 0x33, 0x30, 0x32, 0x65, 0x33, 0x30, 0x35, 0x66, 0x34, 0x32, 0x35, 0x66, 0x36, 0x61, 0x36, 0x39, 0x36, 0x31, 0x36, 0x62, 0x36, 0x66, 0x36, 0x65, 0x36, 0x37, 0x35, 0x66, 0x37, 0x36, 0x36, 0x38, 0x32, 0x65, 0x36, 0x31, 0x37, 0x30, 0x36, 0x62})
|
||||||
|
desource = bytesconv.BytesToString(dec)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
loger.Fatal(`未定义的接口模式,请检查配置 [Custom].Kw_Mode`)
|
loger.Fatal(`未定义的接口模式,请检查配置 [Custom].Kw_Mode`)
|
||||||
}
|
}
|
||||||
loger = nil
|
loger.Free()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func bdapi(songMid, quality string) (ourl, msg string) {
|
func bdapi(songMid, quality string) (ourl, msg string) {
|
||||||
loger := env.Loger.NewGroup(`Kw`)
|
loger := env.Loger.NewGroup(`Kw`)
|
||||||
|
defer loger.Free()
|
||||||
info, ok := fileInfo[quality]
|
info, ok := fileInfo[quality]
|
||||||
if !ok {
|
if !ok {
|
||||||
msg = sources.E_QNotSupport
|
msg = sources.E_QNotSupport
|
||||||
@ -78,7 +94,7 @@ func bdapi(songMid, quality string) (ourl, msg string) {
|
|||||||
`https://bd-api.kuwo.cn/api/service/music/downloadInfo/`, songMid,
|
`https://bd-api.kuwo.cn/api/service/music/downloadInfo/`, songMid,
|
||||||
`?isMv=0&format=`, info.E,
|
`?isMv=0&format=`, info.E,
|
||||||
`&br=`, info.H, info.E, //`&level=`,
|
`&br=`, info.H, info.E, //`&level=`,
|
||||||
`&uin=`, env.Config.Custom.Kw_Bd_Uid,
|
`&uid=`, env.Config.Custom.Kw_Bd_Uid,
|
||||||
`&token=`, env.Config.Custom.Kw_Bd_Token,
|
`&token=`, env.Config.Custom.Kw_Bd_Token,
|
||||||
)
|
)
|
||||||
// jx.Debug(`Kw, Url: %s`, url)
|
// jx.Debug(`Kw, Url: %s`, url)
|
||||||
@ -91,7 +107,7 @@ func bdapi(songMid, quality string) (ourl, msg string) {
|
|||||||
loger.Debug(`Resp: %+v`, resp)
|
loger.Debug(`Resp: %+v`, resp)
|
||||||
if resp.Code != 200 || resp.Data.AudioInfo.Bitrate == `1` {
|
if resp.Code != 200 || resp.Data.AudioInfo.Bitrate == `1` {
|
||||||
// jx.Debug(`Kw, Err: %#v`, resp)
|
// jx.Debug(`Kw, Err: %#v`, resp)
|
||||||
msg = ztool.Str_FastConcat(`failed: `, resp.Msg)
|
msg = ztool.Str_FastConcat(strconv.Itoa(resp.Code), `: `, resp.Msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ourl = utils.DelQuery(resp.Data.URL) //strings.Split(resp.Data.URL, `?`)[0]
|
ourl = utils.DelQuery(resp.Data.URL) //strings.Split(resp.Data.URL, `?`)[0]
|
||||||
@ -100,6 +116,7 @@ func bdapi(songMid, quality string) (ourl, msg string) {
|
|||||||
|
|
||||||
func kwdes(songMid, quality string) (ourl, msg string) {
|
func kwdes(songMid, quality string) (ourl, msg string) {
|
||||||
loger := env.Loger.NewGroup(`Kw`)
|
loger := env.Loger.NewGroup(`Kw`)
|
||||||
|
defer loger.Free()
|
||||||
infoFile, ok := fileInfo[quality]
|
infoFile, ok := fileInfo[quality]
|
||||||
if !ok {
|
if !ok {
|
||||||
msg = sources.E_QNotSupport
|
msg = sources.E_QNotSupport
|
||||||
@ -107,13 +124,13 @@ func kwdes(songMid, quality string) (ourl, msg string) {
|
|||||||
}
|
}
|
||||||
target_url := ztool.Str_FastConcat(
|
target_url := ztool.Str_FastConcat(
|
||||||
`https://mobi.kuwo.cn/mobi.s?f=kuwo&q=`,
|
`https://mobi.kuwo.cn/mobi.s?f=kuwo&q=`,
|
||||||
Base64_encrypt(ztool.Str_FastConcat(
|
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`,
|
`corp=kuwo&p2p=1&sig=0¬race=0&priority=bitrate&network=WIFI&mode=down`,
|
||||||
|
`&source=`, desource,
|
||||||
`&type=`, convtype,
|
`&type=`, convtype,
|
||||||
`&br=`, infoFile.H, infoFile.E,
|
`&br=`, infoFile.H, infoFile.E,
|
||||||
`&format=`, infoFile.E,
|
`&format=`, infoFile.E,
|
||||||
`&rid=`, songMid,
|
`&rid=`, songMid,
|
||||||
`&priority=bitrate&loginUid=0&network=WIFI&loginSid=0&mode=down`,
|
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
if parsemod {
|
if parsemod {
|
||||||
@ -129,6 +146,7 @@ func kwdes(songMid, quality string) (ourl, msg string) {
|
|||||||
msg = sources.ErrHttpReq
|
msg = sources.ErrHttpReq
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
resp.Data.URL = utils.DelQuery(resp.Data.URL)
|
||||||
loger.Debug(`Resp: %+v`, resp)
|
loger.Debug(`Resp: %+v`, resp)
|
||||||
if resp.Code != http.StatusOK {
|
if resp.Code != http.StatusOK {
|
||||||
msg = ztool.Str_FastConcat(`failed: `, resp.Msg)
|
msg = ztool.Str_FastConcat(`failed: `, resp.Msg)
|
||||||
@ -136,11 +154,13 @@ func kwdes(songMid, quality string) (ourl, msg string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
realQuality := strconv.Itoa(resp.Data.Bitrate)
|
realQuality := strconv.Itoa(resp.Data.Bitrate)
|
||||||
if realQuality != infoFile.H[:len(infoFile.H)-1] {
|
if realQuality != infoFile.H[:len(infoFile.H)-1] /*&& resp.Data.Bitrate != 1*/ {
|
||||||
msg = sources.E_QNotMatch
|
msg = sources.E_QNotMatch
|
||||||
|
if !env.Config.Source.ForceFallback {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ourl = utils.DelQuery(resp.Data.URL) //resp.Data.URL[:strings.Index(resp.Data.URL, `?`)]
|
}
|
||||||
|
ourl = resp.Data.URL //resp.Data.URL[:strings.Index(resp.Data.URL, `?`)]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ztool.Net_Request(http.MethodGet, target_url, nil,
|
ztool.Net_Request(http.MethodGet, target_url, nil,
|
||||||
@ -164,8 +184,10 @@ func kwdes(songMid, quality string) (ourl, msg string) {
|
|||||||
realQuality := infoData[`bitrate`]
|
realQuality := infoData[`bitrate`]
|
||||||
if realQuality != infoFile.H[:len(infoFile.H)-1] {
|
if realQuality != infoFile.H[:len(infoFile.H)-1] {
|
||||||
msg = sources.E_QNotMatch
|
msg = sources.E_QNotMatch
|
||||||
|
if !env.Config.Source.ForceFallback {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
ourl = utils.DelQuery(infoData[`url`]) //infoData[`url`][:strings.Index(infoData[`url`], `?`)]
|
ourl = utils.DelQuery(infoData[`url`]) //infoData[`url`][:strings.Index(infoData[`url`], `?`)]
|
||||||
return
|
return
|
||||||
},
|
},
|
||||||
@ -173,3 +195,133 @@ func kwdes(songMid, quality string) (ourl, msg string) {
|
|||||||
)
|
)
|
||||||
return
|
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,7 +13,7 @@ type (
|
|||||||
Format string `json:"format"`
|
Format string `json:"format"`
|
||||||
P2PAudiosourceid string `json:"p2p_audiosourceid"`
|
P2PAudiosourceid string `json:"p2p_audiosourceid"`
|
||||||
Rid int `json:"rid"`
|
Rid int `json:"rid"`
|
||||||
Source string `json:"source"`
|
// Source string `json:"source"`
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
Msg string `json:"msg"`
|
Msg string `json:"msg"`
|
||||||
|
@ -13,11 +13,11 @@ var (
|
|||||||
H string // 专用音质
|
H string // 专用音质
|
||||||
}{
|
}{
|
||||||
sources.Q_128k: {
|
sources.Q_128k: {
|
||||||
E: `mp3`,
|
E: sources.X_mp3,
|
||||||
H: sources.Q_128k,
|
H: sources.Q_128k,
|
||||||
},
|
},
|
||||||
sources.Q_320k: {
|
sources.Q_320k: {
|
||||||
E: `mp3`,
|
E: sources.X_mp3,
|
||||||
H: sources.Q_320k,
|
H: sources.Q_320k,
|
||||||
},
|
},
|
||||||
sources.Q_flac: {
|
sources.Q_flac: {
|
||||||
@ -40,25 +40,64 @@ var (
|
|||||||
// `User-Agent`: `okhttp/3.10.0`,
|
// `User-Agent`: `okhttp/3.10.0`,
|
||||||
}
|
}
|
||||||
bdheader = map[string]string{
|
bdheader = map[string]string{
|
||||||
`channel`: `qq`,
|
`channel`: `guanfang`,
|
||||||
`plat`: `ar`,
|
`plat`: `ar`,
|
||||||
`net`: `wifi`,
|
`net`: `wifi`,
|
||||||
`ver`: `3.1.2`,
|
`ver`: `3.1.4`,
|
||||||
// `uid`: ``,
|
`api-ver`: `application/json`,
|
||||||
// `devId`: `0`,
|
|
||||||
|
`user-agent`: `Dart/2.18 (dart:io)`, //`Dalvik/2.1.0 (Linux; U; Android 7.1.1; OPPO R9sk Build/NMF26F)`,
|
||||||
}
|
}
|
||||||
|
// bdsreg = regexp.MustCompile(`[^a-zA-Z0-9]`)
|
||||||
)
|
)
|
||||||
|
|
||||||
func mkMap(data []byte) map[string]string {
|
func mkMap(data []byte) map[string]string {
|
||||||
out := make(map[string]string)
|
out := make(map[string]string)
|
||||||
sep := bytes.Split(data, []byte{13, 10})
|
sep := bytes.Split(data, []byte{13, 10})
|
||||||
for i, r := 0, len(sep); i < r; i++ {
|
for i, r := 0, len(sep); i < r; i++ {
|
||||||
pat := bytes.Split(sep[i], []byte{61})
|
var s = sep[i]
|
||||||
if len(pat) == 2 {
|
if p := bytes.IndexByte(s, '='); p != -1 {
|
||||||
|
out[bytesconv.BytesToString(s[:p])] = bytesconv.BytesToString(s[p+1:])
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
out[`_`] += bytesconv.BytesToString(s) + `;`
|
||||||
|
}
|
||||||
|
/*pat := bytes.Split(sep[i], []byte{61})
|
||||||
|
if len(pat) >= 2 {
|
||||||
out[bytesconv.BytesToString(pat[0])] = bytesconv.BytesToString(pat[1])
|
out[bytesconv.BytesToString(pat[0])] = bytesconv.BytesToString(pat[1])
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
out[`_`] += bytesconv.BytesToString(pat[0]) + `;`
|
out[`_`] += bytesconv.BytesToString(pat[0]) + `;`*/
|
||||||
}
|
}
|
||||||
return out
|
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
|
||||||
|
}*/
|
||||||
|
41
src/sources/custom/mg/albuminfo.go
Normal file
41
src/sources/custom/mg/albuminfo.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package mg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/gob"
|
||||||
|
"errors"
|
||||||
|
"lx-source/src/env"
|
||||||
|
"lx-source/src/sources"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ZxwyWebSite/ztool"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
gob.Register(albumInfo{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAlbumInfo(aid string) (info albumInfo, err error) {
|
||||||
|
cquery := strings.Join([]string{`mg`, aid, `ainfo`}, `/`)
|
||||||
|
if cdata, ok := env.Cache.Get(cquery); ok {
|
||||||
|
if cinfo, ok := cdata.(albumInfo); ok {
|
||||||
|
info = cinfo
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = ztool.Net_Request(
|
||||||
|
http.MethodGet, ztool.Str_FastConcat(
|
||||||
|
`https://app.c.nf.migu.cn/MIGUM2.0/v1.0/content/resourceinfo.do?needSimple=01&resourceId=`, aid, `&resourceType=2003`,
|
||||||
|
), nil,
|
||||||
|
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders()},
|
||||||
|
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&info)},
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
if len(info.Resource) == 0 {
|
||||||
|
err = errors.New(`no Album Resource`)
|
||||||
|
} else {
|
||||||
|
env.Cache.Set(cquery, info, sources.C_lx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
41
src/sources/custom/mg/musicinfo.go
Normal file
41
src/sources/custom/mg/musicinfo.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package mg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/gob"
|
||||||
|
"errors"
|
||||||
|
"lx-source/src/env"
|
||||||
|
"lx-source/src/sources"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ZxwyWebSite/ztool"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
gob.Register(musicInfo{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMusicInfo(cid string) (info musicInfo, err error) {
|
||||||
|
cquery := strings.Join([]string{`mg`, cid, `minfo`}, `/`)
|
||||||
|
if cdata, ok := env.Cache.Get(cquery); ok {
|
||||||
|
if cinfo, ok := cdata.(musicInfo); ok {
|
||||||
|
info = cinfo
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = ztool.Net_Request(
|
||||||
|
http.MethodGet, ztool.Str_FastConcat(
|
||||||
|
`https://c.musicapp.migu.cn/MIGUM2.0/v1.0/content/resourceinfo.do?copyrightId=`, cid, `&resourceType=2`,
|
||||||
|
), nil,
|
||||||
|
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders()},
|
||||||
|
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&info)},
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
if len(info.Resource) == 0 {
|
||||||
|
err = errors.New(`no Music Resource`)
|
||||||
|
} else {
|
||||||
|
env.Cache.Set(cquery, info, sources.C_lx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
185
src/sources/custom/mg/player.go
Normal file
185
src/sources/custom/mg/player.go
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
package mg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"lx-source/src/env"
|
||||||
|
"lx-source/src/sources"
|
||||||
|
"lx-source/src/sources/custom/utils"
|
||||||
|
"lx-source/src/sources/example"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ZxwyWebSite/ztool"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Url func(string, string) (string, string)
|
||||||
|
|
||||||
|
mg_pool *sync.Pool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
env.Inits.Add(func() {
|
||||||
|
loger := env.Loger.NewGroup(`MgInit`)
|
||||||
|
switch env.Config.Custom.Mg_Mode {
|
||||||
|
case `0`, `builtin`:
|
||||||
|
loger.Debug(`use builtin`)
|
||||||
|
mg_pool = &sync.Pool{New: func() any { return new(mgApi_Song) }}
|
||||||
|
Url = builtin
|
||||||
|
case `1`, `custom`:
|
||||||
|
loger.Debug(`use custom`)
|
||||||
|
if ztool.Chk_IsNilStr(
|
||||||
|
// env.Config.Custom.Mg_Usr_VerId,
|
||||||
|
// env.Config.Custom.Mg_Usr_Token,
|
||||||
|
env.Config.Custom.Mg_Usr_OSVer,
|
||||||
|
env.Config.Custom.Mg_Usr_ReqUA,
|
||||||
|
) {
|
||||||
|
loger.Fatal(`使用自定义账号且用户参数为空`)
|
||||||
|
}
|
||||||
|
mg_pool = &sync.Pool{New: func() any { return new(playInfo) }}
|
||||||
|
Url = mcustom
|
||||||
|
case `2`, `malbum`:
|
||||||
|
loger.Debug(`use malbum`)
|
||||||
|
Url = malbum
|
||||||
|
default:
|
||||||
|
loger.Fatal(`未定义的接口模式,请检查配置 [Custom].Mg_Mode`)
|
||||||
|
}
|
||||||
|
loger.Free()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtin(songMid, quality string) (ourl, msg string) {
|
||||||
|
loger := env.Loger.NewGroup(`Mg`)
|
||||||
|
defer loger.Free()
|
||||||
|
rquality, ok := qualitys[quality]
|
||||||
|
if !ok {
|
||||||
|
msg = sources.E_QNotSupport
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp := mg_pool.Get().(*mgApi_Song)
|
||||||
|
defer mg_pool.Put(resp)
|
||||||
|
url := ztool.Str_FastConcat(`https://`, example.Api_mg, `?copyrightId=`, songMid, `&type=`, rquality)
|
||||||
|
err := ztool.Net_Request(
|
||||||
|
http.MethodGet, url, nil,
|
||||||
|
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(example.Header_mg)},
|
||||||
|
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&resp)},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
loger.Error(`HttpReq: %s`, err)
|
||||||
|
msg = sources.ErrHttpReq
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loger.Debug(`Resp: %+v`, resp)
|
||||||
|
// ourl, msg = resp.Url(rquality)
|
||||||
|
if resp.Data.PlayURL != `` {
|
||||||
|
ourl = `https:` + utils.DelQuery(resp.Data.PlayURL)
|
||||||
|
} else {
|
||||||
|
msg = ztool.Str_FastConcat(resp.Code, `: `, resp.Msg)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func mcustom(songMid, quality string) (ourl, msg string) {
|
||||||
|
loger := env.Loger.NewGroup(`Mg`)
|
||||||
|
defer loger.Free()
|
||||||
|
rquality, ok := qualityMap[quality]
|
||||||
|
if !ok {
|
||||||
|
msg = sources.E_QNotSupport
|
||||||
|
return
|
||||||
|
}
|
||||||
|
url := ztool.Str_FastConcat(
|
||||||
|
`https://app.c.nf.migu.cn/MIGUM2.0/strategy/listen-url/v2.4?toneFlag=`, rquality,
|
||||||
|
`&songId=`, songMid,
|
||||||
|
`&resourceType=2`,
|
||||||
|
)
|
||||||
|
resp := mg_pool.Get().(*playInfo)
|
||||||
|
defer mg_pool.Put(resp)
|
||||||
|
err := ztool.Net_Request(
|
||||||
|
http.MethodGet, url, nil,
|
||||||
|
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(map[string]string{
|
||||||
|
`User-Agent`: env.Config.Custom.Mg_Usr_ReqUA,
|
||||||
|
`aversionid`: env.Config.Custom.Mg_Usr_VerId,
|
||||||
|
`token`: env.Config.Custom.Mg_Usr_Token,
|
||||||
|
`channel`: `0146832`,
|
||||||
|
`language`: `Chinese`,
|
||||||
|
`ua`: `Android_migu`,
|
||||||
|
`mode`: `android`,
|
||||||
|
`os`: `Android ` + env.Config.Custom.Mg_Usr_OSVer,
|
||||||
|
})},
|
||||||
|
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&resp)},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
loger.Error(`Request: %s`, err)
|
||||||
|
msg = sources.ErrHttpReq
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loger.Debug(`Resp: %+v`, resp)
|
||||||
|
if resp.Code != `000000` {
|
||||||
|
msg = resp.Info
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if resp.Data.URL == `` {
|
||||||
|
msg = `No Data: 无返回链接`
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if resp.Data.AudioFormatType != rquality {
|
||||||
|
msg = ztool.Str_FastConcat(`实际音质不匹配: `, rquality, ` <= `, resp.Data.AudioFormatType)
|
||||||
|
if !env.Config.Source.ForceFallback {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ourl = utils.DelQuery(resp.Data.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func malbum(songMid, quality string) (ourl, msg string) {
|
||||||
|
loger := env.Loger.NewGroup(`Mg`)
|
||||||
|
defer loger.Free()
|
||||||
|
rquality, ok := qualityMap[quality]
|
||||||
|
if !ok {
|
||||||
|
msg = sources.E_QNotSupport
|
||||||
|
return
|
||||||
|
}
|
||||||
|
minfo, err := getMusicInfo(songMid)
|
||||||
|
if err != nil {
|
||||||
|
msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loger.Debug(`mInfo: %+v`, minfo)
|
||||||
|
var hasQuality bool
|
||||||
|
for _, v := range minfo.Resource[0].NewRateFormats {
|
||||||
|
if hasQuality = v.FormatType == rquality; hasQuality {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasQuality {
|
||||||
|
msg = sources.E_QNotMatch
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ainfo, err := getAlbumInfo(minfo.Resource[0].AlbumID)
|
||||||
|
if err != nil {
|
||||||
|
msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loger.Debug(`aInfo: %+v`, ainfo)
|
||||||
|
for _, v := range ainfo.Resource[0].SongItems {
|
||||||
|
if v.CopyrightID == songMid {
|
||||||
|
for _, w := range v.NewRateFormats {
|
||||||
|
if w.FormatType == rquality {
|
||||||
|
if rquality == `PQ` || rquality == `HQ` {
|
||||||
|
ourl = w.URL
|
||||||
|
} else {
|
||||||
|
ourl = w.AndroidURL
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ourl == `` {
|
||||||
|
msg = sources.E_NoLink
|
||||||
|
} else {
|
||||||
|
ourl = `https://freetyst.nf.migu.cn` + ourl[24:]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
37
src/sources/custom/mg/refresh.go
Normal file
37
src/sources/custom/mg/refresh.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package mg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"lx-source/src/env"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/ZxwyWebSite/ztool"
|
||||||
|
"github.com/ZxwyWebSite/ztool/logs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func refresh(loger *logs.Logger, now int64) error {
|
||||||
|
var out map[string]any
|
||||||
|
err := ztool.Net_Request(
|
||||||
|
http.MethodPost,
|
||||||
|
`https://m.music.migu.cn/migumusic/h5/user/auth/userActiveNotice`,
|
||||||
|
nil,
|
||||||
|
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(mgheader)},
|
||||||
|
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&out)},
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
if out[`code`].(int) != http.StatusOK {
|
||||||
|
return errors.New(out[`msg`].(string))
|
||||||
|
} else {
|
||||||
|
loger.Info(`咪咕session保活成功`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
env.Inits.Add(func() {
|
||||||
|
if env.Config.Custom.Mg_Refresh_Enable && false {
|
||||||
|
env.Tasker.Add(`mg_refresh`, refresh, 86000, true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
533
src/sources/custom/mg/types.go
Normal file
533
src/sources/custom/mg/types.go
Normal file
@ -0,0 +1,533 @@
|
|||||||
|
package mg
|
||||||
|
|
||||||
|
type playInfo struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Data struct {
|
||||||
|
AudioFormatType string `json:"audioFormatType"`
|
||||||
|
FreeListenType string `json:"freeListenType"`
|
||||||
|
HaveVisualMv bool `json:"haveVisualMv"`
|
||||||
|
LrcURL string `json:"lrcUrl"`
|
||||||
|
MrcURL string `json:"mrcUrl"`
|
||||||
|
Song struct {
|
||||||
|
Album string `json:"album"`
|
||||||
|
AlbumID string `json:"albumId"`
|
||||||
|
AlbumPinyin string `json:"albumPinyin"`
|
||||||
|
ContentID string `json:"contentId"`
|
||||||
|
CopyrightID string `json:"copyrightId"`
|
||||||
|
CopyrightType int `json:"copyrightType"`
|
||||||
|
Duration int `json:"duration"`
|
||||||
|
ForeverListen bool `json:"foreverListen"`
|
||||||
|
HaveShockRing int `json:"haveShockRing"`
|
||||||
|
Img1 string `json:"img1"`
|
||||||
|
Img2 string `json:"img2"`
|
||||||
|
Img3 string `json:"img3"`
|
||||||
|
MvCopyrightType int `json:"mvCopyrightType"`
|
||||||
|
ResourceType string `json:"resourceType"`
|
||||||
|
RestrictType int `json:"restrictType"`
|
||||||
|
RingToneID string `json:"ringToneId"`
|
||||||
|
ShowTags []string `json:"showTags"`
|
||||||
|
SingerList []struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Img string `json:"img"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
NameSpelling string `json:"nameSpelling"`
|
||||||
|
} `json:"singerList"`
|
||||||
|
SongID string `json:"songId"`
|
||||||
|
SongName string `json:"songName"`
|
||||||
|
SongPinyin string `json:"songPinyin"`
|
||||||
|
} `json:"song"`
|
||||||
|
TrcURL string `json:"trcUrl"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
} `json:"data"`
|
||||||
|
Info string `json:"info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (resp *playInfo) Url(rquality string) (ourl, emsg string) {
|
||||||
|
// if resp.Code != `000000` {
|
||||||
|
// emsg = resp.Info
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// if resp.Data.URL == `` {
|
||||||
|
// emsg = `No Data: 无返回链接`
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// if resp.Data.AudioFormatType != rquality {
|
||||||
|
// emsg = ztool.Str_FastConcat(`实际音质不匹配: `, rquality, ` <= `, resp.Data.AudioFormatType)
|
||||||
|
// if !env.Config.Source.ForceFallback {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ourl = utils.DelQuery(resp.Data.URL)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
type mgApi_Song struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Data struct {
|
||||||
|
PlayURL string `json:"playUrl"`
|
||||||
|
FormatID string `json:"formatId"`
|
||||||
|
SalePrice string `json:"salePrice"`
|
||||||
|
BizType string `json:"bizType"`
|
||||||
|
BizCode string `json:"bizCode"`
|
||||||
|
AuditionsLength int `json:"auditionsLength"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (resp *mgApi_Song) Url(string) (ourl, emsg string) {
|
||||||
|
// if resp.Data.PlayURL != `` {
|
||||||
|
// ourl = `https:` + utils.DelQuery(resp.Data.PlayURL)
|
||||||
|
// } else {
|
||||||
|
// emsg = ztool.Str_FastConcat(resp.Code, `: `, resp.Msg)
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
type musicInfo struct {
|
||||||
|
// Code string `json:"code"`
|
||||||
|
// Info string `json:"info"`
|
||||||
|
Resource []struct {
|
||||||
|
// ResourceType string `json:"resourceType"`
|
||||||
|
// RefID string `json:"refId"`
|
||||||
|
// CopyrightID string `json:"copyrightId"`
|
||||||
|
// ContentID string `json:"contentId"`
|
||||||
|
// SongID string `json:"songId"`
|
||||||
|
// SongName string `json:"songName"`
|
||||||
|
// SingerID string `json:"singerId"`
|
||||||
|
// Singer string `json:"singer"`
|
||||||
|
AlbumID string `json:"albumId"`
|
||||||
|
// Album string `json:"album"`
|
||||||
|
// AlbumImgs []struct {
|
||||||
|
// ImgSizeType string `json:"imgSizeType"`
|
||||||
|
// Img string `json:"img"`
|
||||||
|
// FileID string `json:"fileId"`
|
||||||
|
// WebpImg string `json:"webpImg"`
|
||||||
|
// } `json:"albumImgs"`
|
||||||
|
// OpNumItem struct {
|
||||||
|
// PlayNum int `json:"playNum"`
|
||||||
|
// PlayNumDesc string `json:"playNumDesc"`
|
||||||
|
// KeepNum int `json:"keepNum"`
|
||||||
|
// KeepNumDesc string `json:"keepNumDesc"`
|
||||||
|
// CommentNum int `json:"commentNum"`
|
||||||
|
// CommentNumDesc string `json:"commentNumDesc"`
|
||||||
|
// ShareNum int `json:"shareNum"`
|
||||||
|
// ShareNumDesc string `json:"shareNumDesc"`
|
||||||
|
// OrderNumByWeek int `json:"orderNumByWeek"`
|
||||||
|
// OrderNumByWeekDesc string `json:"orderNumByWeekDesc"`
|
||||||
|
// OrderNumByTotal int `json:"orderNumByTotal"`
|
||||||
|
// OrderNumByTotalDesc string `json:"orderNumByTotalDesc"`
|
||||||
|
// ThumbNum int `json:"thumbNum"`
|
||||||
|
// ThumbNumDesc string `json:"thumbNumDesc"`
|
||||||
|
// FollowNum int `json:"followNum"`
|
||||||
|
// FollowNumDesc string `json:"followNumDesc"`
|
||||||
|
// SubscribeNum int `json:"subscribeNum"`
|
||||||
|
// SubscribeNumDesc string `json:"subscribeNumDesc"`
|
||||||
|
// LivePlayNum int `json:"livePlayNum"`
|
||||||
|
// LivePlayNumDesc string `json:"livePlayNumDesc"`
|
||||||
|
// PopularNum int `json:"popularNum"`
|
||||||
|
// PopularNumDesc string `json:"popularNumDesc"`
|
||||||
|
// BookingNum int `json:"bookingNum"`
|
||||||
|
// BookingNumDesc string `json:"bookingNumDesc"`
|
||||||
|
// SettingNum int `json:"settingNum"`
|
||||||
|
// SettingNumDesc string `json:"settingNumDesc"`
|
||||||
|
// CallNum int `json:"callNum"`
|
||||||
|
// CallNumDesc string `json:"callNumDesc"`
|
||||||
|
// CallingPlayNum int `json:"callingPlayNum"`
|
||||||
|
// CallingPlayNumDesc string `json:"callingPlayNumDesc"`
|
||||||
|
// CallingPlayDuration int `json:"callingPlayDuration"`
|
||||||
|
// CallingPlayDurationDesc string `json:"callingPlayDurationDesc"`
|
||||||
|
// CalledPlayDuration int `json:"calledPlayDuration"`
|
||||||
|
// CalledPlayDurationDesc string `json:"calledPlayDurationDesc"`
|
||||||
|
// RingtoneAppPlayNum int `json:"ringtoneAppPlayNum"`
|
||||||
|
// RingtoneAppPlayNumDesc string `json:"ringtoneAppPlayNumDesc"`
|
||||||
|
// RingtoneAppSettingNum int `json:"ringtoneAppSettingNum"`
|
||||||
|
// RingtoneAppSettingNumDesc string `json:"ringtoneAppSettingNumDesc"`
|
||||||
|
// } `json:"opNumItem"`
|
||||||
|
// ToneControl string `json:"toneControl"`
|
||||||
|
// RelatedSongs []struct {
|
||||||
|
// ResourceType string `json:"resourceType"`
|
||||||
|
// ResourceTypeName string `json:"resourceTypeName"`
|
||||||
|
// CopyrightID string `json:"copyrightId"`
|
||||||
|
// ProductID string `json:"productId"`
|
||||||
|
// } `json:"relatedSongs"`
|
||||||
|
// RateFormats []struct {
|
||||||
|
// ResourceType string `json:"resourceType"`
|
||||||
|
// FormatType string `json:"formatType"`
|
||||||
|
// Format string `json:"format"`
|
||||||
|
// Size string `json:"size"`
|
||||||
|
// FileType string `json:"fileType,omitempty"`
|
||||||
|
// Price string `json:"price"`
|
||||||
|
// AndroidFileType string `json:"androidFileType,omitempty"`
|
||||||
|
// IosFileType string `json:"iosFileType,omitempty"`
|
||||||
|
// IosSize string `json:"iosSize,omitempty"`
|
||||||
|
// AndroidSize string `json:"androidSize,omitempty"`
|
||||||
|
// IosFormat string `json:"iosFormat,omitempty"`
|
||||||
|
// AndroidFormat string `json:"androidFormat,omitempty"`
|
||||||
|
// IosAccuracyLevel string `json:"iosAccuracyLevel,omitempty"`
|
||||||
|
// AndroidAccuracyLevel string `json:"androidAccuracyLevel,omitempty"`
|
||||||
|
// } `json:"rateFormats"`
|
||||||
|
NewRateFormats []struct {
|
||||||
|
// ResourceType string `json:"resourceType"`
|
||||||
|
FormatType string `json:"formatType"`
|
||||||
|
// Format string `json:"format"`
|
||||||
|
// Size string `json:"size"`
|
||||||
|
// FileType string `json:"fileType,omitempty"`
|
||||||
|
// Price string `json:"price"`
|
||||||
|
// AndroidFileType string `json:"androidFileType,omitempty"`
|
||||||
|
// IosFileType string `json:"iosFileType,omitempty"`
|
||||||
|
// IosSize string `json:"iosSize,omitempty"`
|
||||||
|
// AndroidSize string `json:"androidSize,omitempty"`
|
||||||
|
// IosFormat string `json:"iosFormat,omitempty"`
|
||||||
|
// AndroidFormat string `json:"androidFormat,omitempty"`
|
||||||
|
// IosAccuracyLevel string `json:"iosAccuracyLevel,omitempty"`
|
||||||
|
// AndroidAccuracyLevel string `json:"androidAccuracyLevel,omitempty"`
|
||||||
|
} `json:"newRateFormats"`
|
||||||
|
// LrcURL string `json:"lrcUrl"`
|
||||||
|
// TagList []struct {
|
||||||
|
// ResourceType string `json:"resourceType"`
|
||||||
|
// TagID string `json:"tagId"`
|
||||||
|
// TagName string `json:"tagName"`
|
||||||
|
// TagDesc string `json:"tagDesc,omitempty"`
|
||||||
|
// } `json:"tagList"`
|
||||||
|
// DigitalColumnID string `json:"digitalColumnId"`
|
||||||
|
// Copyright string `json:"copyright"`
|
||||||
|
// ValidStatus bool `json:"validStatus"`
|
||||||
|
// SongDescs string `json:"songDescs"`
|
||||||
|
// SongAliasName string `json:"songAliasName"`
|
||||||
|
// IsInDAlbum string `json:"isInDAlbum"`
|
||||||
|
// IsInSideDalbum string `json:"isInSideDalbum"`
|
||||||
|
// IsInSalesPeriod string `json:"isInSalesPeriod"`
|
||||||
|
// SongType string `json:"songType"`
|
||||||
|
// MrcURL string `json:"mrcUrl"`
|
||||||
|
// InvalidateDate string `json:"invalidateDate"`
|
||||||
|
// DalbumID string `json:"dalbumId"`
|
||||||
|
// TrcURL string `json:"trcUrl"`
|
||||||
|
// VipType string `json:"vipType"`
|
||||||
|
// ScopeOfcopyright string `json:"scopeOfcopyright"`
|
||||||
|
// AuditionsType string `json:"auditionsType"`
|
||||||
|
// FirstIcon string `json:"firstIcon"`
|
||||||
|
// TranslateName string `json:"translateName"`
|
||||||
|
// ChargeAuditions string `json:"chargeAuditions"`
|
||||||
|
// OldChargeAuditions string `json:"oldChargeAuditions"`
|
||||||
|
// SongIcon string `json:"songIcon"`
|
||||||
|
// CodeRate struct {
|
||||||
|
// PQ struct {
|
||||||
|
// CodeRateChargeAuditions string `json:"codeRateChargeAuditions"`
|
||||||
|
// IsCodeRateDownload string `json:"isCodeRateDownload"`
|
||||||
|
// CodeRateFileSize string `json:"codeRateFileSize"`
|
||||||
|
// } `json:"PQ"`
|
||||||
|
// HQ struct {
|
||||||
|
// CodeRateChargeAuditions string `json:"codeRateChargeAuditions"`
|
||||||
|
// IsCodeRateDownload string `json:"isCodeRateDownload"`
|
||||||
|
// } `json:"HQ"`
|
||||||
|
// SQ struct {
|
||||||
|
// CodeRateChargeAuditions string `json:"codeRateChargeAuditions"`
|
||||||
|
// IsCodeRateDownload string `json:"isCodeRateDownload"`
|
||||||
|
// ContentIDSQ string `json:"contentIdSQ"`
|
||||||
|
// } `json:"SQ"`
|
||||||
|
// } `json:"codeRate"`
|
||||||
|
// IsDownload string `json:"isDownload"`
|
||||||
|
// CopyrightType string `json:"copyrightType"`
|
||||||
|
// HasMv string `json:"hasMv"`
|
||||||
|
// TopQuality string `json:"topQuality"`
|
||||||
|
// PreSale string `json:"preSale"`
|
||||||
|
// IsShare string `json:"isShare"`
|
||||||
|
// IsCollection string `json:"isCollection"`
|
||||||
|
// Length string `json:"length"`
|
||||||
|
// SingerImg struct {
|
||||||
|
// Num421 struct {
|
||||||
|
// SingerName string `json:"singerName"`
|
||||||
|
// MiguImgItems []struct {
|
||||||
|
// ImgSizeType string `json:"imgSizeType"`
|
||||||
|
// Img string `json:"img"`
|
||||||
|
// FileID string `json:"fileId"`
|
||||||
|
// WebpImg string `json:"webpImg"`
|
||||||
|
// } `json:"miguImgItems"`
|
||||||
|
// } `json:"421"`
|
||||||
|
// } `json:"singerImg"`
|
||||||
|
// SongNamePinyin string `json:"songNamePinyin"`
|
||||||
|
// AlbumNamePinyin string `json:"albumNamePinyin"`
|
||||||
|
// Artists []struct {
|
||||||
|
// ID string `json:"id"`
|
||||||
|
// Name string `json:"name"`
|
||||||
|
// NameSpelling string `json:"nameSpelling"`
|
||||||
|
// } `json:"artists"`
|
||||||
|
// LandscapImg string `json:"landscapImg"`
|
||||||
|
// VipLogo string `json:"vipLogo"`
|
||||||
|
// VipDownload string `json:"vipDownload"`
|
||||||
|
// FirstPublish string `json:"firstPublish"`
|
||||||
|
// ShowTag []string `json:"showTag"`
|
||||||
|
// MaterialValidStatus bool `json:"materialValidStatus"`
|
||||||
|
// NeedEncrypt string `json:"needEncrypt"`
|
||||||
|
// ForeverListen bool `json:"foreverListen"`
|
||||||
|
// HasAssociatedRing bool `json:"hasAssociatedRing"`
|
||||||
|
} `json:"resource"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type albumInfo struct {
|
||||||
|
// Code string `json:"code"`
|
||||||
|
// Info string `json:"info"`
|
||||||
|
Resource []struct {
|
||||||
|
// ResourceType string `json:"resourceType"`
|
||||||
|
// AlbumID string `json:"albumId"`
|
||||||
|
// ImgItems []struct {
|
||||||
|
// ImgSizeType string `json:"imgSizeType"`
|
||||||
|
// Img string `json:"img"`
|
||||||
|
// FileID string `json:"fileId"`
|
||||||
|
// WebpImg string `json:"webpImg"`
|
||||||
|
// } `json:"imgItems"`
|
||||||
|
// Title string `json:"title"`
|
||||||
|
// Singer string `json:"singer"`
|
||||||
|
// SingerID string `json:"singerId"`
|
||||||
|
// SingerImgs []struct {
|
||||||
|
// ImgSizeType string `json:"imgSizeType"`
|
||||||
|
// Img string `json:"img"`
|
||||||
|
// FileID string `json:"fileId"`
|
||||||
|
// WebpImg string `json:"webpImg"`
|
||||||
|
// } `json:"singerImgs"`
|
||||||
|
// Summary string `json:"summary"`
|
||||||
|
// TotalCount string `json:"totalCount"`
|
||||||
|
// PublishTime string `json:"publishTime"`
|
||||||
|
// PublishCorp string `json:"publishCorp"`
|
||||||
|
// OpNumItem struct {
|
||||||
|
// PlayNum int `json:"playNum"`
|
||||||
|
// PlayNumDesc string `json:"playNumDesc"`
|
||||||
|
// KeepNum int `json:"keepNum"`
|
||||||
|
// KeepNumDesc string `json:"keepNumDesc"`
|
||||||
|
// CommentNum int `json:"commentNum"`
|
||||||
|
// CommentNumDesc string `json:"commentNumDesc"`
|
||||||
|
// ShareNum int `json:"shareNum"`
|
||||||
|
// ShareNumDesc string `json:"shareNumDesc"`
|
||||||
|
// OrderNumByWeek int `json:"orderNumByWeek"`
|
||||||
|
// OrderNumByWeekDesc string `json:"orderNumByWeekDesc"`
|
||||||
|
// OrderNumByTotal int `json:"orderNumByTotal"`
|
||||||
|
// OrderNumByTotalDesc string `json:"orderNumByTotalDesc"`
|
||||||
|
// ThumbNum int `json:"thumbNum"`
|
||||||
|
// ThumbNumDesc string `json:"thumbNumDesc"`
|
||||||
|
// FollowNum int `json:"followNum"`
|
||||||
|
// FollowNumDesc string `json:"followNumDesc"`
|
||||||
|
// SubscribeNum int `json:"subscribeNum"`
|
||||||
|
// SubscribeNumDesc string `json:"subscribeNumDesc"`
|
||||||
|
// LivePlayNum int `json:"livePlayNum"`
|
||||||
|
// LivePlayNumDesc string `json:"livePlayNumDesc"`
|
||||||
|
// PopularNum int `json:"popularNum"`
|
||||||
|
// PopularNumDesc string `json:"popularNumDesc"`
|
||||||
|
// BookingNum int `json:"bookingNum"`
|
||||||
|
// BookingNumDesc string `json:"bookingNumDesc"`
|
||||||
|
// SettingNum int `json:"settingNum"`
|
||||||
|
// SettingNumDesc string `json:"settingNumDesc"`
|
||||||
|
// CallNum int `json:"callNum"`
|
||||||
|
// CallNumDesc string `json:"callNumDesc"`
|
||||||
|
// CallingPlayNum int `json:"callingPlayNum"`
|
||||||
|
// CallingPlayNumDesc string `json:"callingPlayNumDesc"`
|
||||||
|
// CallingPlayDuration int `json:"callingPlayDuration"`
|
||||||
|
// CallingPlayDurationDesc string `json:"callingPlayDurationDesc"`
|
||||||
|
// CalledPlayDuration int `json:"calledPlayDuration"`
|
||||||
|
// CalledPlayDurationDesc string `json:"calledPlayDurationDesc"`
|
||||||
|
// RingtoneAppPlayNum int `json:"ringtoneAppPlayNum"`
|
||||||
|
// RingtoneAppPlayNumDesc string `json:"ringtoneAppPlayNumDesc"`
|
||||||
|
// RingtoneAppSettingNum int `json:"ringtoneAppSettingNum"`
|
||||||
|
// RingtoneAppSettingNumDesc string `json:"ringtoneAppSettingNumDesc"`
|
||||||
|
// } `json:"opNumItem"`
|
||||||
|
// Tags []struct {
|
||||||
|
// ResourceType string `json:"resourceType"`
|
||||||
|
// TagID string `json:"tagId"`
|
||||||
|
// TagName string `json:"tagName"`
|
||||||
|
// TagDesc string `json:"tagDesc,omitempty"`
|
||||||
|
// } `json:"tags"`
|
||||||
|
// AlbumAliasName string `json:"albumAliasName"`
|
||||||
|
// AlbumClass string `json:"albumClass"`
|
||||||
|
// Language string `json:"language"`
|
||||||
|
// PublishCompany string `json:"publishCompany"`
|
||||||
|
// PublishDate string `json:"publishDate"`
|
||||||
|
// TranslateName string `json:"translateName"`
|
||||||
|
SongItems []struct {
|
||||||
|
// ResourceType string `json:"resourceType"`
|
||||||
|
// RefID string `json:"refId"`
|
||||||
|
CopyrightID string `json:"copyrightId"`
|
||||||
|
// ContentID string `json:"contentId"`
|
||||||
|
// SongID string `json:"songId"`
|
||||||
|
// SongName string `json:"songName"`
|
||||||
|
// SingerID string `json:"singerId"`
|
||||||
|
// Singer string `json:"singer"`
|
||||||
|
// AlbumID string `json:"albumId"`
|
||||||
|
// Album string `json:"album"`
|
||||||
|
// AlbumImgs []struct {
|
||||||
|
// ImgSizeType string `json:"imgSizeType"`
|
||||||
|
// Img string `json:"img"`
|
||||||
|
// FileID string `json:"fileId"`
|
||||||
|
// WebpImg string `json:"webpImg"`
|
||||||
|
// } `json:"albumImgs"`
|
||||||
|
// OpNumItem struct {
|
||||||
|
// PlayNum int `json:"playNum"`
|
||||||
|
// PlayNumDesc string `json:"playNumDesc"`
|
||||||
|
// KeepNum int `json:"keepNum"`
|
||||||
|
// KeepNumDesc string `json:"keepNumDesc"`
|
||||||
|
// CommentNum int `json:"commentNum"`
|
||||||
|
// CommentNumDesc string `json:"commentNumDesc"`
|
||||||
|
// ShareNum int `json:"shareNum"`
|
||||||
|
// ShareNumDesc string `json:"shareNumDesc"`
|
||||||
|
// OrderNumByWeek int `json:"orderNumByWeek"`
|
||||||
|
// OrderNumByWeekDesc string `json:"orderNumByWeekDesc"`
|
||||||
|
// OrderNumByTotal int `json:"orderNumByTotal"`
|
||||||
|
// OrderNumByTotalDesc string `json:"orderNumByTotalDesc"`
|
||||||
|
// ThumbNum int `json:"thumbNum"`
|
||||||
|
// ThumbNumDesc string `json:"thumbNumDesc"`
|
||||||
|
// FollowNum int `json:"followNum"`
|
||||||
|
// FollowNumDesc string `json:"followNumDesc"`
|
||||||
|
// SubscribeNum int `json:"subscribeNum"`
|
||||||
|
// SubscribeNumDesc string `json:"subscribeNumDesc"`
|
||||||
|
// LivePlayNum int `json:"livePlayNum"`
|
||||||
|
// LivePlayNumDesc string `json:"livePlayNumDesc"`
|
||||||
|
// PopularNum int `json:"popularNum"`
|
||||||
|
// PopularNumDesc string `json:"popularNumDesc"`
|
||||||
|
// BookingNum int `json:"bookingNum"`
|
||||||
|
// BookingNumDesc string `json:"bookingNumDesc"`
|
||||||
|
// SettingNum int `json:"settingNum"`
|
||||||
|
// SettingNumDesc string `json:"settingNumDesc"`
|
||||||
|
// CallNum int `json:"callNum"`
|
||||||
|
// CallNumDesc string `json:"callNumDesc"`
|
||||||
|
// CallingPlayNum int `json:"callingPlayNum"`
|
||||||
|
// CallingPlayNumDesc string `json:"callingPlayNumDesc"`
|
||||||
|
// CallingPlayDuration int `json:"callingPlayDuration"`
|
||||||
|
// CallingPlayDurationDesc string `json:"callingPlayDurationDesc"`
|
||||||
|
// CalledPlayDuration int `json:"calledPlayDuration"`
|
||||||
|
// CalledPlayDurationDesc string `json:"calledPlayDurationDesc"`
|
||||||
|
// RingtoneAppPlayNum int `json:"ringtoneAppPlayNum"`
|
||||||
|
// RingtoneAppPlayNumDesc string `json:"ringtoneAppPlayNumDesc"`
|
||||||
|
// RingtoneAppSettingNum int `json:"ringtoneAppSettingNum"`
|
||||||
|
// RingtoneAppSettingNumDesc string `json:"ringtoneAppSettingNumDesc"`
|
||||||
|
// } `json:"opNumItem"`
|
||||||
|
// ToneControl string `json:"toneControl"`
|
||||||
|
// RelatedSongs []struct {
|
||||||
|
// ResourceType string `json:"resourceType"`
|
||||||
|
// ResourceTypeName string `json:"resourceTypeName"`
|
||||||
|
// CopyrightID string `json:"copyrightId"`
|
||||||
|
// ProductID string `json:"productId"`
|
||||||
|
// } `json:"relatedSongs"`
|
||||||
|
// RateFormats []struct {
|
||||||
|
// ResourceType string `json:"resourceType"`
|
||||||
|
// FormatType string `json:"formatType"`
|
||||||
|
// URL string `json:"url,omitempty"`
|
||||||
|
// Format string `json:"format"`
|
||||||
|
// Size string `json:"size"`
|
||||||
|
// FileType string `json:"fileType,omitempty"`
|
||||||
|
// Price string `json:"price"`
|
||||||
|
// IosURL string `json:"iosUrl,omitempty"`
|
||||||
|
// AndroidURL string `json:"androidUrl,omitempty"`
|
||||||
|
// AndroidFileType string `json:"androidFileType,omitempty"`
|
||||||
|
// IosFileType string `json:"iosFileType,omitempty"`
|
||||||
|
// IosSize string `json:"iosSize,omitempty"`
|
||||||
|
// AndroidSize string `json:"androidSize,omitempty"`
|
||||||
|
// IosFormat string `json:"iosFormat,omitempty"`
|
||||||
|
// AndroidFormat string `json:"androidFormat,omitempty"`
|
||||||
|
// IosAccuracyLevel string `json:"iosAccuracyLevel,omitempty"`
|
||||||
|
// AndroidAccuracyLevel string `json:"androidAccuracyLevel,omitempty"`
|
||||||
|
// } `json:"rateFormats"`
|
||||||
|
NewRateFormats []struct {
|
||||||
|
// ResourceType string `json:"resourceType"`
|
||||||
|
FormatType string `json:"formatType"`
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
// Format string `json:"format"`
|
||||||
|
// Size string `json:"size"`
|
||||||
|
// FileType string `json:"fileType,omitempty"`
|
||||||
|
// Price string `json:"price"`
|
||||||
|
// IosURL string `json:"iosUrl,omitempty"`
|
||||||
|
AndroidURL string `json:"androidUrl,omitempty"`
|
||||||
|
// AndroidFileType string `json:"androidFileType,omitempty"`
|
||||||
|
// IosFileType string `json:"iosFileType,omitempty"`
|
||||||
|
// IosSize string `json:"iosSize,omitempty"`
|
||||||
|
// AndroidSize string `json:"androidSize,omitempty"`
|
||||||
|
// IosFormat string `json:"iosFormat,omitempty"`
|
||||||
|
// AndroidFormat string `json:"androidFormat,omitempty"`
|
||||||
|
// IosAccuracyLevel string `json:"iosAccuracyLevel,omitempty"`
|
||||||
|
// AndroidAccuracyLevel string `json:"androidAccuracyLevel,omitempty"`
|
||||||
|
} `json:"newRateFormats"`
|
||||||
|
// LrcURL string `json:"lrcUrl"`
|
||||||
|
// TagList []struct {
|
||||||
|
// ResourceType string `json:"resourceType"`
|
||||||
|
// TagID string `json:"tagId"`
|
||||||
|
// TagName string `json:"tagName"`
|
||||||
|
// TagDesc string `json:"tagDesc,omitempty"`
|
||||||
|
// } `json:"tagList"`
|
||||||
|
// DigitalColumnID string `json:"digitalColumnId"`
|
||||||
|
// Copyright string `json:"copyright"`
|
||||||
|
// ValidStatus bool `json:"validStatus"`
|
||||||
|
// SongDescs string `json:"songDescs"`
|
||||||
|
// SongAliasName string `json:"songAliasName"`
|
||||||
|
// IsInDAlbum string `json:"isInDAlbum"`
|
||||||
|
// IsInSideDalbum string `json:"isInSideDalbum"`
|
||||||
|
// IsInSalesPeriod string `json:"isInSalesPeriod"`
|
||||||
|
// SongType string `json:"songType"`
|
||||||
|
// MrcURL string `json:"mrcUrl"`
|
||||||
|
// InvalidateDate string `json:"invalidateDate"`
|
||||||
|
// DalbumID string `json:"dalbumId"`
|
||||||
|
// TrackNumber string `json:"trackNumber"`
|
||||||
|
// TrcURL string `json:"trcUrl"`
|
||||||
|
// Disc string `json:"disc"`
|
||||||
|
// VipType string `json:"vipType"`
|
||||||
|
// ScopeOfcopyright string `json:"scopeOfcopyright"`
|
||||||
|
// AuditionsType string `json:"auditionsType"`
|
||||||
|
// FirstIcon string `json:"firstIcon"`
|
||||||
|
// TranslateName string `json:"translateName,omitempty"`
|
||||||
|
// ChargeAuditions string `json:"chargeAuditions"`
|
||||||
|
// OldChargeAuditions string `json:"oldChargeAuditions"`
|
||||||
|
// SongIcon string `json:"songIcon"`
|
||||||
|
// CodeRate struct {
|
||||||
|
// PQ struct {
|
||||||
|
// CodeRateChargeAuditions string `json:"codeRateChargeAuditions"`
|
||||||
|
// IsCodeRateDownload string `json:"isCodeRateDownload"`
|
||||||
|
// CodeRateFileSize string `json:"codeRateFileSize"`
|
||||||
|
// } `json:"PQ"`
|
||||||
|
// HQ struct {
|
||||||
|
// CodeRateChargeAuditions string `json:"codeRateChargeAuditions"`
|
||||||
|
// IsCodeRateDownload string `json:"isCodeRateDownload"`
|
||||||
|
// } `json:"HQ"`
|
||||||
|
// SQ struct {
|
||||||
|
// CodeRateChargeAuditions string `json:"codeRateChargeAuditions"`
|
||||||
|
// IsCodeRateDownload string `json:"isCodeRateDownload"`
|
||||||
|
// ContentIDSQ string `json:"contentIdSQ"`
|
||||||
|
// } `json:"SQ"`
|
||||||
|
// } `json:"codeRate"`
|
||||||
|
// IsDownload string `json:"isDownload"`
|
||||||
|
// CopyrightType string `json:"copyrightType"`
|
||||||
|
// HasMv string `json:"hasMv"`
|
||||||
|
// TopQuality string `json:"topQuality"`
|
||||||
|
// PreSale string `json:"preSale"`
|
||||||
|
// IsShare string `json:"isShare"`
|
||||||
|
// IsCollection string `json:"isCollection"`
|
||||||
|
// Length string `json:"length"`
|
||||||
|
// SingerImg struct {
|
||||||
|
// Num421 struct {
|
||||||
|
// SingerName string `json:"singerName"`
|
||||||
|
// MiguImgItems []struct {
|
||||||
|
// ImgSizeType string `json:"imgSizeType"`
|
||||||
|
// Img string `json:"img"`
|
||||||
|
// FileID string `json:"fileId"`
|
||||||
|
// WebpImg string `json:"webpImg"`
|
||||||
|
// } `json:"miguImgItems"`
|
||||||
|
// } `json:"421"`
|
||||||
|
// } `json:"singerImg"`
|
||||||
|
// SongNamePinyin string `json:"songNamePinyin"`
|
||||||
|
// AlbumNamePinyin string `json:"albumNamePinyin"`
|
||||||
|
// Artists []struct {
|
||||||
|
// ID string `json:"id"`
|
||||||
|
// Name string `json:"name"`
|
||||||
|
// NameSpelling string `json:"nameSpelling"`
|
||||||
|
// } `json:"artists"`
|
||||||
|
// LandscapImg string `json:"landscapImg"`
|
||||||
|
// VipLogo string `json:"vipLogo"`
|
||||||
|
// VipDownload string `json:"vipDownload"`
|
||||||
|
// FirstPublish string `json:"firstPublish"`
|
||||||
|
// ShowTag []string `json:"showTag"`
|
||||||
|
// MaterialValidStatus bool `json:"materialValidStatus"`
|
||||||
|
// NeedEncrypt string `json:"needEncrypt"`
|
||||||
|
// ForeverListen bool `json:"foreverListen"`
|
||||||
|
// HasAssociatedRing bool `json:"hasAssociatedRing"`
|
||||||
|
// LoginListenFlag string `json:"loginListenFlag,omitempty"`
|
||||||
|
// MvCopyright string `json:"mvCopyright,omitempty"`
|
||||||
|
// ForeverListenFlag string `json:"foreverListenFlag,omitempty"`
|
||||||
|
} `json:"songItems"`
|
||||||
|
} `json:"resource"`
|
||||||
|
}
|
46
src/sources/custom/mg/utils.go
Normal file
46
src/sources/custom/mg/utils.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package mg
|
||||||
|
|
||||||
|
import "lx-source/src/sources"
|
||||||
|
|
||||||
|
// const (
|
||||||
|
// q_128k = `PQ`
|
||||||
|
// q_320k = `HQ`
|
||||||
|
// q_flac = `SQ`
|
||||||
|
// q_fl24 = `ZQ`
|
||||||
|
// )
|
||||||
|
|
||||||
|
var (
|
||||||
|
qualityMap = map[string]string{
|
||||||
|
sources.Q_128k: `PQ`,
|
||||||
|
sources.Q_320k: `HQ`,
|
||||||
|
sources.Q_flac: `SQ`,
|
||||||
|
sources.Q_fl24: `ZQ`,
|
||||||
|
}
|
||||||
|
// qualityMapReverse = map[string]string{
|
||||||
|
// q_128k: sources.Q_128k,
|
||||||
|
// q_320k: sources.Q_320k,
|
||||||
|
// q_flac: sources.Q_flac,
|
||||||
|
// q_fl24: sources.Q_fl24,
|
||||||
|
// }
|
||||||
|
qualitys = map[string]string{
|
||||||
|
sources.Q_128k: `1`,
|
||||||
|
sources.Q_320k: `2`,
|
||||||
|
sources.Q_flac: `3`,
|
||||||
|
sources.Q_fl24: `4`,
|
||||||
|
|
||||||
|
sources.Q_master: `5`,
|
||||||
|
}
|
||||||
|
// qualitysReverse = map[string]string {
|
||||||
|
// `000009`: sources.Q_128k,
|
||||||
|
// `020010`: sources.Q_320k,
|
||||||
|
// `011002`: sources.Q_flac,
|
||||||
|
// `011005`: sources.Q_fl24,
|
||||||
|
// }
|
||||||
|
mgheader = map[string]string{
|
||||||
|
`Origin`: `https://m.music.migu.cn`,
|
||||||
|
`Referer`: `https://m.music.migu.cn/v4/`,
|
||||||
|
`By`: ``,
|
||||||
|
`channel`: ``,
|
||||||
|
`Cookie`: ``,
|
||||||
|
}
|
||||||
|
)
|
@ -1,11 +1,5 @@
|
|||||||
package tx
|
package tx
|
||||||
|
|
||||||
import (
|
|
||||||
"lx-source/src/env"
|
|
||||||
|
|
||||||
"github.com/ZxwyWebSite/ztool/logs"
|
|
||||||
)
|
|
||||||
|
|
||||||
// func Info(songMid string) (info any, msg string) {
|
// func Info(songMid string) (info any, msg string) {
|
||||||
// req, emsg := getMusicInfo(songMid)
|
// req, emsg := getMusicInfo(songMid)
|
||||||
// if emsg != `` {
|
// if emsg != `` {
|
||||||
@ -29,14 +23,3 @@ import (
|
|||||||
// }
|
// }
|
||||||
// return
|
// 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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
355
src/sources/custom/tx/login.go
Normal file
355
src/sources/custom/tx/login.go
Normal file
@ -0,0 +1,355 @@
|
|||||||
|
package tx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"lx-source/src/env"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"net/http/cookiejar"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
_ "unsafe"
|
||||||
|
|
||||||
|
"github.com/ZxwyWebSite/ztool"
|
||||||
|
"github.com/ZxwyWebSite/ztool/logs"
|
||||||
|
"github.com/ZxwyWebSite/ztool/x/json"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:linkname request github.com/ZxwyWebSite/ztool.request
|
||||||
|
func request(client *http.Client, method, url string, body io.Reader, reqh []ztool.Net_ReqHandlerFunc, resh []ztool.Net_ResHandlerFunc) error
|
||||||
|
|
||||||
|
// QQ快速登录 - 直接使用本机已登录账号
|
||||||
|
func Qlogin_graph(l *logs.Logger) error {
|
||||||
|
// 参考文章: https://learnku.com/articles/33970
|
||||||
|
|
||||||
|
jar, _ := cookiejar.New(nil)
|
||||||
|
client := &http.Client{
|
||||||
|
Timeout: time.Second * 10,
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
},
|
||||||
|
Jar: jar,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step0 - 获取本机QQ服务地址
|
||||||
|
// (应该没有人会占用4301端口导致qq切换备用端口吧,先不写了...懒)
|
||||||
|
err := request(
|
||||||
|
client, http.MethodGet, `https://localhost.ptlogin2.qq.com:4301/pc_querystatus`, nil,
|
||||||
|
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders()},
|
||||||
|
[]ztool.Net_ResHandlerFunc{func(res *http.Response) error {
|
||||||
|
_, err := io.Copy(io.Discard, res.Body)
|
||||||
|
return err
|
||||||
|
}},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New(`step0: 无法连接本机QQ服务`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step1 - 获取 pt_local_token
|
||||||
|
var pt_local_token string
|
||||||
|
err = request(
|
||||||
|
client, http.MethodGet,
|
||||||
|
`https://xui.ptlogin2.qq.com/cgi-bin/xlogin?appid=716027609&daid=383&style=33&login_text=%E7%99%BB%E5%BD%95&hide_title_bar=1&hide_border=1&target=self&s_url=https://graph.qq.com/oauth2.0/login_jump&pt_3rd_aid=100497308&pt_feedback_link=https://support.qq.com/products/77942?customInfo=.appid100497308&theme=2&verify_theme=`,
|
||||||
|
nil,
|
||||||
|
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(map[string]string{
|
||||||
|
`Referer`: `https://graph.qq.com/`,
|
||||||
|
})},
|
||||||
|
[]ztool.Net_ResHandlerFunc{func(res *http.Response) error {
|
||||||
|
for _, v := range res.Cookies() {
|
||||||
|
if v.Name == `pt_local_token` {
|
||||||
|
pt_local_token = v.Value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err := io.Copy(io.Discard, res.Body)
|
||||||
|
return err
|
||||||
|
}},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l.Info(`pt_local_token: %v`, pt_local_token)
|
||||||
|
|
||||||
|
// Step2 - 获取本机登录的 QQ 号
|
||||||
|
var url2 strings.Builder
|
||||||
|
url2.WriteString(`https://localhost.ptlogin2.qq.com:4301/pt_get_uins?callback=ptui_getuins_CB&r=`)
|
||||||
|
url2.WriteString(strconv.FormatFloat(rand.Float64(), 'f', -1, 64))
|
||||||
|
url2.WriteString(`&pt_local_tk=`)
|
||||||
|
url2.WriteString(pt_local_token)
|
||||||
|
var out2 []struct {
|
||||||
|
Uin int `json:"uin"`
|
||||||
|
// FaceIndex int `json:"face_index"`
|
||||||
|
// Gender int `json:"gender"`
|
||||||
|
Nickname string `json:"nickname"`
|
||||||
|
// ClientType int `json:"client_type"`
|
||||||
|
// UinFlag int `json:"uin_flag"`
|
||||||
|
// Account int `json:"account"`
|
||||||
|
}
|
||||||
|
var header2 = map[string]string{
|
||||||
|
`Referer`: `https://xui.ptlogin2.qq.com/`,
|
||||||
|
// `Cookie`: `pt_local_token=` + pt_local_token,
|
||||||
|
}
|
||||||
|
err = request(
|
||||||
|
client, http.MethodGet, url2.String(), nil,
|
||||||
|
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(header2)},
|
||||||
|
[]ztool.Net_ResHandlerFunc{func(res *http.Response) error {
|
||||||
|
data, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sep_b := bytes.IndexByte(data, '[')
|
||||||
|
sep_e := bytes.LastIndexByte(data, ']')
|
||||||
|
if sep_b == -1 || sep_e == -1 {
|
||||||
|
return errors.New(`step2: 无法解析返回数据`)
|
||||||
|
}
|
||||||
|
sep := data[sep_b : sep_e+1]
|
||||||
|
return json.Unmarshal(sep, &out2)
|
||||||
|
}},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var uin string
|
||||||
|
length := len(out2)
|
||||||
|
switch length {
|
||||||
|
case 0:
|
||||||
|
return errors.New(`step2: 无可用账号`)
|
||||||
|
case 1:
|
||||||
|
uin = strconv.Itoa(out2[0].Uin)
|
||||||
|
default:
|
||||||
|
fmt.Println(`请选择要登录的账号:`)
|
||||||
|
for i, v := range out2 {
|
||||||
|
fmt.Println(i, v.Nickname, v.Uin)
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
fmt.Print(`输入序号: `)
|
||||||
|
var input string
|
||||||
|
fmt.Scanln(&input)
|
||||||
|
i, err := strconv.Atoi(input)
|
||||||
|
if err != nil {
|
||||||
|
l.Error(`err: %v`, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if i >= length {
|
||||||
|
l.Error(`err: 下标越界`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
uin = strconv.Itoa(out2[i].Uin)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.Info(`uin: %v`, uin)
|
||||||
|
|
||||||
|
// Step3 - 获取 clientkey
|
||||||
|
var url3 strings.Builder
|
||||||
|
url3.WriteString(`https://localhost.ptlogin2.qq.com:4301/pt_get_st?clientuin=`)
|
||||||
|
url3.WriteString(uin)
|
||||||
|
url3.WriteString(`&r=`)
|
||||||
|
url3.WriteString(strconv.FormatFloat(rand.Float64(), 'f', -1, 64))
|
||||||
|
url3.WriteString(`&pt_local_tk=`)
|
||||||
|
url3.WriteString(pt_local_token)
|
||||||
|
url3.WriteString(`&callback=__jp0`)
|
||||||
|
// var clientkey string
|
||||||
|
err = request(
|
||||||
|
client, http.MethodGet, url3.String(), nil,
|
||||||
|
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(header2)},
|
||||||
|
[]ztool.Net_ResHandlerFunc{func(res *http.Response) error {
|
||||||
|
/*for _, v := range res.Cookies() {
|
||||||
|
if v.Name == `clientkey` {
|
||||||
|
clientkey = v.Value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
_, err := io.Copy(io.Discard, res.Body)
|
||||||
|
return err
|
||||||
|
}},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step4 - 获取 skey
|
||||||
|
var url4 strings.Builder
|
||||||
|
url4.WriteString(`https://ssl.ptlogin2.qq.com/jump?clientuin=`)
|
||||||
|
url4.WriteString(uin)
|
||||||
|
url4.WriteString(`&keyindex=9&pt_aid=716027609&daid=383&u1=https://graph.qq.com/oauth2.0/login_jump&pt_local_tk=`)
|
||||||
|
url4.WriteString(pt_local_token)
|
||||||
|
url4.WriteString(`&pt_3rd_aid=100497308&ptopt=1&style=40`)
|
||||||
|
/*var cookie4 strings.Builder
|
||||||
|
cookie4.WriteString(`pt_local_token=`)
|
||||||
|
cookie4.WriteString(pt_local_token)
|
||||||
|
cookie4.WriteByte(';')
|
||||||
|
cookie4.WriteString(`clientuin=`)
|
||||||
|
cookie4.WriteString(uin)
|
||||||
|
cookie4.WriteByte(';')
|
||||||
|
cookie4.WriteString(`clientkey=`)
|
||||||
|
cookie4.WriteString(clientkey)*/
|
||||||
|
var jurl string
|
||||||
|
err = request(
|
||||||
|
client, http.MethodGet, url4.String(), nil,
|
||||||
|
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders( /*map[string]string{
|
||||||
|
`Referer`: `https://xui.ptlogin2.qq.com/`,
|
||||||
|
`Cookie`: cookie4.String(),
|
||||||
|
}*/header2)},
|
||||||
|
[]ztool.Net_ResHandlerFunc{func(res *http.Response) error {
|
||||||
|
data, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sep_b := bytes.IndexByte(data, ',')
|
||||||
|
sep_e := bytes.LastIndexByte(data, ' ')
|
||||||
|
if sep_b == -1 || sep_e == -1 {
|
||||||
|
return errors.New(`step4: 无法解析返回数据`)
|
||||||
|
}
|
||||||
|
jurl = string(data[sep_b+3 : sep_e-2])
|
||||||
|
return nil
|
||||||
|
}},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step5 - 获取 p_skey
|
||||||
|
var p_skey string
|
||||||
|
err = request(
|
||||||
|
client, http.MethodGet, jurl, nil,
|
||||||
|
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(header2)},
|
||||||
|
[]ztool.Net_ResHandlerFunc{func(res *http.Response) error {
|
||||||
|
for _, v := range res.Cookies() {
|
||||||
|
if v.Name == `p_skey` && v.Value != `` {
|
||||||
|
p_skey = v.Value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err := io.Copy(io.Discard, res.Body)
|
||||||
|
return err
|
||||||
|
}},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step6 - 登录账号
|
||||||
|
getGtk := func(skey string) string {
|
||||||
|
var hash = 5381
|
||||||
|
for _, v := range skey {
|
||||||
|
hash += (hash << 5) + int(v)
|
||||||
|
}
|
||||||
|
return strconv.Itoa(hash & 0x7fffffff)
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
var authcode string
|
||||||
|
err = request(
|
||||||
|
client, http.MethodPost,
|
||||||
|
`https://graph.qq.com/oauth2.0/authorize`,
|
||||||
|
strings.NewReader(ztool.Str_FastConcat(
|
||||||
|
`response_type=code&client_id=100497308&redirect_uri=https%3A%2F%2Fy.qq.com%2Fportal%2Fwx_redirect.html%3Flogin_type%3D1%26surl%3Dhttps%3A%2F%2Fy.qq.com%2F&scope=get_user_info%2Cget_app_friends&state=state&switch=&from_ptlogin=1&src=1&update_auth=1&openapi=1010_1030`,
|
||||||
|
`&g_tk=`, getGtk(p_skey),
|
||||||
|
`&auth_time=`, strconv.FormatInt(now.UnixMilli(), 10),
|
||||||
|
`&ui=`, strings.ToUpper(uuid.NewString()),
|
||||||
|
)),
|
||||||
|
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(map[string]string{
|
||||||
|
`Referer`: `https://graph.qq.com/oauth2.0/show?which=Login&display=pc&response_type=code&client_id=100497308&redirect_uri=https%3A%2F%2Fy.qq.com%2Fportal%2Fwx_redirect.html%3Flogin_type%3D1%26surl%3Dhttps%3A%2F%2Fy.qq.com%2F&state=state&display=pc&scope=get_user_info%2Cget_app_friends`,
|
||||||
|
`Content-Type`: `application/x-www-form-urlencoded`,
|
||||||
|
})},
|
||||||
|
[]ztool.Net_ResHandlerFunc{func(res *http.Response) error {
|
||||||
|
/*if res.StatusCode != 302 {
|
||||||
|
return errors.New(`step6: not redirect`)
|
||||||
|
}*/
|
||||||
|
location := res.Header[`Location`][0]
|
||||||
|
l.Info(`loc: %v`, location)
|
||||||
|
loc, err := url.Parse(location)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
authcode = loc.Query()[`code`][0]
|
||||||
|
return nil
|
||||||
|
}},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l.Info(`authcode: %v`, authcode)
|
||||||
|
var out6 struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
// Ts int64 `json:"ts"`
|
||||||
|
// StartTs int64 `json:"start_ts"`
|
||||||
|
// Traceid string `json:"traceid"`
|
||||||
|
Req struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Data struct {
|
||||||
|
// Openid string `json:"openid"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
ExpiredAt int `json:"expired_at"`
|
||||||
|
// Musicid int `json:"musicid"`
|
||||||
|
Musickey string `json:"musickey"`
|
||||||
|
// MusickeyCreateTime int `json:"musickeyCreateTime"`
|
||||||
|
// FirstLogin int `json:"first_login"`
|
||||||
|
// ErrMsg string `json:"errMsg"`
|
||||||
|
// SessionKey string `json:"sessionKey"`
|
||||||
|
// Unionid string `json:"unionid"`
|
||||||
|
StrMusicid string `json:"str_musicid"`
|
||||||
|
// Errtip string `json:"errtip"`
|
||||||
|
// Nick string `json:"nick"`
|
||||||
|
// Logo string `json:"logo"`
|
||||||
|
// FeedbackURL string `json:"feedbackURL"`
|
||||||
|
// EncryptUin string `json:"encryptUin"`
|
||||||
|
// Userip string `json:"userip"`
|
||||||
|
// LastLoginTime int `json:"lastLoginTime"`
|
||||||
|
// KeyExpiresIn int `json:"keyExpiresIn"`
|
||||||
|
// RefreshKey string `json:"refresh_key"`
|
||||||
|
// LoginType int `json:"loginType"`
|
||||||
|
// Prompt2Bind int `json:"prompt2bind"`
|
||||||
|
// LogoffStatus int `json:"logoffStatus"`
|
||||||
|
// OtherAccounts []interface{} `json:"otherAccounts"`
|
||||||
|
// OtherPhoneNo string `json:"otherPhoneNo"`
|
||||||
|
// Token string `json:"token"`
|
||||||
|
// IsPrized int `json:"isPrized"`
|
||||||
|
// IsShowDevManage int `json:"isShowDevManage"`
|
||||||
|
// ErrTip2 string `json:"errTip2"`
|
||||||
|
// Tip3 string `json:"tip3"`
|
||||||
|
// EncryptedPhoneNo string `json:"encryptedPhoneNo"`
|
||||||
|
// PhoneNo string `json:"phoneNo"`
|
||||||
|
// BindAccountType int `json:"bindAccountType"`
|
||||||
|
// NeedRefreshKeyIn int `json:"needRefreshKeyIn"`
|
||||||
|
} `json:"data"`
|
||||||
|
} `json:"req"`
|
||||||
|
}
|
||||||
|
err = request(
|
||||||
|
client, http.MethodPost,
|
||||||
|
`https://u.y.qq.com/cgi-bin/musicu.fcg`,
|
||||||
|
strings.NewReader(ztool.Str_FastConcat(
|
||||||
|
`{"comm":{"g_tk":5381,"platform":"yqq","ct":24,"cv":0},"req":{"module":"QQConnectLogin.LoginServer","method":"QQLogin","param":{"code":"`, authcode, `"}}}`,
|
||||||
|
)),
|
||||||
|
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(map[string]string{
|
||||||
|
`Referer`: `https://y.qq.com/`,
|
||||||
|
`Content-Type`: `application/x-www-form-urlencoded`,
|
||||||
|
})},
|
||||||
|
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&out6)},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l.Info(`res: %+v`, out6)
|
||||||
|
l.Info(`登录成功`)
|
||||||
|
|
||||||
|
env.Config.Custom.Tx_Enable = true
|
||||||
|
env.Config.Custom.Tx_Uuin = out6.Req.Data.StrMusicid
|
||||||
|
env.Config.Custom.Tx_Ukey = out6.Req.Data.Musickey
|
||||||
|
env.Config.Custom.Tx_Refresh_Enable = false
|
||||||
|
// env.Config.Custom.Tx_Refresh_Interval = time.Date(now.Year(), now.Month(), now.Day()+5, 0, 0, 0, 0, now.Location()).Unix()
|
||||||
|
// env.Config.Custom.Tx_RefreshToken = out6.Req.Data.RefreshToken
|
||||||
|
// env.Config.Custom.Tx_AccessToken = out6.Req.Data.AccessToken
|
||||||
|
|
||||||
|
return env.Cfg.Save(``)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QQ扫码登录(todo)
|
||||||
|
// func qlogin_qr_()
|
@ -1,200 +1,27 @@
|
|||||||
package tx
|
package tx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/gob"
|
||||||
|
"lx-source/src/env"
|
||||||
|
"lx-source/src/sources"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/ZxwyWebSite/ztool"
|
"github.com/ZxwyWebSite/ztool"
|
||||||
"github.com/ZxwyWebSite/ztool/x/bytesconv"
|
"github.com/ZxwyWebSite/ztool/x/bytesconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
type musicInfo struct {
|
func init() {
|
||||||
// Info struct {
|
gob.Register(musicInfo{})
|
||||||
// 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) {
|
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}}}`)
|
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 {
|
var infoResp struct {
|
||||||
Code int `json:"code"`
|
Code int `json:"code"`
|
||||||
@ -216,5 +43,6 @@ func getMusicInfo(songMid string) (infoBody musicInfo, emsg string) {
|
|||||||
return //nil, `获取音乐信息失败`
|
return //nil, `获取音乐信息失败`
|
||||||
}
|
}
|
||||||
infoBody = infoResp.Req.Data
|
infoBody = infoResp.Req.Data
|
||||||
|
env.Cache.Set(cquery, infoBody, sources.C_lx)
|
||||||
return //infoBody.Req.Data, ``
|
return //infoBody.Req.Data, ``
|
||||||
}
|
}
|
||||||
|
@ -8,62 +8,6 @@ import (
|
|||||||
"github.com/ZxwyWebSite/ztool/x/bytesconv"
|
"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获取逻辑:
|
音乐URL获取逻辑:
|
||||||
if需要付费播放and无账号信息:
|
if需要付费播放and无账号信息:
|
||||||
@ -87,33 +31,54 @@ type playInfo struct {
|
|||||||
返回结果
|
返回结果
|
||||||
更新:
|
更新:
|
||||||
可通过 goto loop 实现,但可能会导致逻辑混乱 (想使用账号获取正常链接却返回试听链接)
|
可通过 goto loop 实现,但可能会导致逻辑混乱 (想使用账号获取正常链接却返回试听链接)
|
||||||
|
2024-03-16:
|
||||||
|
正常获取->128k获取->试听获取
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func Url(songMid, quality string) (ourl, msg string) {
|
func Url(songMid, quality string) (ourl, msg string) {
|
||||||
loger := env.Loger.NewGroup(`Tx`)
|
loger := env.Loger.NewGroup(`Tx`)
|
||||||
|
defer loger.Free()
|
||||||
infoFile, ok := fileInfo[quality]
|
infoFile, ok := fileInfo[quality]
|
||||||
if !ok || (!env.Config.Custom.Tx_Enable && quality != sources.Q_128k) {
|
if !ok || (!env.Config.Custom.Tx_Enable && quality != sources.Q_128k) {
|
||||||
msg = sources.E_QNotSupport //`不支持的音质`
|
msg = sources.E_QNotSupport //`不支持的音质`
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
infoBody, emsg := getMusicInfo(songMid)
|
infoBody, emsg := getMusicInfo(songMid)
|
||||||
loger.Debug(`infoBody: %+v`, infoBody)
|
|
||||||
if emsg != `` {
|
if emsg != `` {
|
||||||
|
loger.Error(`GetInfo: %v`, emsg)
|
||||||
msg = emsg
|
msg = emsg
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
loger.Debug(`infoBody: %+v`, infoBody)
|
||||||
var uauthst, uuin string = env.Config.Custom.Tx_Ukey, env.Config.Custom.Tx_Uuin
|
var uauthst, uuin string = env.Config.Custom.Tx_Ukey, env.Config.Custom.Tx_Uuin
|
||||||
if uuin == `` || !env.Config.Custom.Tx_Enable {
|
if uuin == `` || !env.Config.Custom.Tx_Enable {
|
||||||
uuin = `1535153710`
|
uuin = `1535153710`
|
||||||
}
|
}
|
||||||
var strFileName string
|
var strFileName string
|
||||||
tryLink := infoBody.TrackInfo.Pay.PayPlay == 1 && /*uauthst == ``&&*/ !env.Config.Custom.Tx_Enable
|
tryLink := infoBody.TrackInfo.Pay.PayPlay == 1 && /*uauthst == ``&&*/ !env.Config.Custom.Tx_Enable
|
||||||
|
Loop:
|
||||||
if tryLink {
|
if tryLink {
|
||||||
strFileName = ztool.Str_FastConcat(`RS02`, infoBody.TrackInfo.Vs[0], `.mp3`)
|
if infoBody.TrackInfo.Vs[0] == `` {
|
||||||
} else {
|
msg = sources.ErrNoLink
|
||||||
strFileName = ztool.Str_FastConcat(infoFile.H, infoBody.TrackInfo.File.MediaMid, infoFile.E)
|
return
|
||||||
}
|
}
|
||||||
requestBody := ztool.Str_FastConcat(`{"comm":{"authst":"`, uauthst, `","ct":"26","cv":"2010101","qq":"`, uuin, `","v":"2010101"},"req_0":{"method":"CgiGetVkey","module":"vkey.GetVkeyServer","param":{"filename":["`, strFileName, `"],"guid":"114514","loginflag":1,"platform":"20","songmid":["`, songMid, `"],"songtype":[0],"uin":"10086"}}}`)
|
strFileName = ztool.Str_FastConcat(`RS02`, infoBody.TrackInfo.Vs[0], `.`, sources.X_mp3)
|
||||||
|
} else {
|
||||||
|
strFileName = ztool.Str_FastConcat(infoFile.H, infoBody.TrackInfo.File.MediaMid, `.`, infoFile.E)
|
||||||
|
}
|
||||||
|
requestBody := ztool.Str_FastConcat(
|
||||||
|
`{"comm":{"authst":"`,
|
||||||
|
uauthst,
|
||||||
|
`","ct":"26","cv":"2010101","qq":"`,
|
||||||
|
uuin,
|
||||||
|
`","v":"2010101"},"req_0":{"method":"CgiGetVkey","module":"vkey.GetVkeyServer","param":{"filename":["`,
|
||||||
|
strFileName,
|
||||||
|
`"],"guid":"20211008","loginflag":1,"platform":"20","songmid":["`,
|
||||||
|
songMid,
|
||||||
|
`"],"songtype":[0],"uin":"`,
|
||||||
|
uuin,
|
||||||
|
`"}}}`,
|
||||||
|
)
|
||||||
var infoResp struct {
|
var infoResp struct {
|
||||||
Code int `json:"code"`
|
Code int `json:"code"`
|
||||||
// Ts int64 `json:"ts"`
|
// Ts int64 `json:"ts"`
|
||||||
@ -123,12 +88,28 @@ func Url(songMid, quality string) (ourl, msg string) {
|
|||||||
}
|
}
|
||||||
err := signRequest(bytesconv.StringToBytes(requestBody), &infoResp)
|
err := signRequest(bytesconv.StringToBytes(requestBody), &infoResp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
loger.Error(`Request: %s`, err)
|
||||||
msg = err.Error()
|
msg = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
loger.Debug(`infoResp: %+v`, infoResp)
|
loger.Debug(`infoResp: %+v`, infoResp)
|
||||||
|
if len(infoResp.Req0.Data.Midurlinfo) == 0 {
|
||||||
|
msg = `No Data: 无返回数据`
|
||||||
|
return
|
||||||
|
}
|
||||||
infoData := infoResp.Req0.Data.Midurlinfo[0]
|
infoData := infoResp.Req0.Data.Midurlinfo[0]
|
||||||
if infoData.Purl == `` {
|
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 //`无法获取音乐链接`
|
msg = sources.E_NoLink //`无法获取音乐链接`
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -139,8 +120,10 @@ func Url(songMid, quality string) (ourl, msg string) {
|
|||||||
// }
|
// }
|
||||||
if realQuality != infoFile.H && !tryLink {
|
if realQuality != infoFile.H && !tryLink {
|
||||||
msg = sources.E_QNotMatch
|
msg = sources.E_QNotMatch
|
||||||
|
if !env.Config.Source.ForceFallback {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ourl = ztool.Str_FastConcat(`https://ws.stream.qqmusic.qq.com/`, infoData.Purl)
|
}
|
||||||
|
ourl = env.Config.Custom.Tx_CDNUrl + infoData.Purl
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package tx
|
package tx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"lx-source/src/env"
|
"lx-source/src/env"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
@ -78,25 +80,15 @@ type refreshData struct {
|
|||||||
第一次载入时会刷新一次测试可用性&同步过期时间 (默认7天)
|
第一次载入时会刷新一次测试可用性&同步过期时间 (默认7天)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func refresh(loger *logs.Logger) {
|
func refresh(loger *logs.Logger, now int64) error {
|
||||||
// loger := env.Loger.NewGroup(`refresh_login`)
|
// 前置检测
|
||||||
if env.Config.Custom.Tx_Ukey == `` || !env.Config.Custom.Tx_Refresh_Enable {
|
if now < env.Config.Custom.Tx_Refresh_Interval {
|
||||||
return
|
|
||||||
}
|
|
||||||
if time.Now().Unix() < env.Config.Custom.Tx_Refresh_Interval {
|
|
||||||
loger.Debug(`Key未过期,跳过...`)
|
loger.Debug(`Key未过期,跳过...`)
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
// 刷新逻辑 (注:QQ登录最常用所以先判断QHL开头)
|
||||||
var body, surl string
|
var body, surl string
|
||||||
if strings.HasPrefix(env.Config.Custom.Tx_Ukey, `W_X`) {
|
if strings.HasPrefix(env.Config.Custom.Tx_Ukey, `Q_H_L`) {
|
||||||
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(
|
body = ztool.Str_FastConcat(
|
||||||
`{"req1":{"method":"QQLogin","module":"QQConnectLogin.LoginServer","param":{"expired_in":7776000,"musicid":`,
|
`{"req1":{"method":"QQLogin","module":"QQConnectLogin.LoginServer","param":{"expired_in":7776000,"musicid":`,
|
||||||
env.Config.Custom.Tx_Uuin,
|
env.Config.Custom.Tx_Uuin,
|
||||||
@ -105,43 +97,69 @@ func refresh(loger *logs.Logger) {
|
|||||||
`"}}}`,
|
`"}}}`,
|
||||||
)
|
)
|
||||||
surl = `6`
|
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 {
|
} else {
|
||||||
loger.Error(`未知的qqmusic_key格式`)
|
// 致命错误(删除任务)
|
||||||
env.Config.Custom.Tx_Refresh_Enable = false // 本次启动阻止继续执行
|
panic(`未知的 qqmusic_key 格式, 请检查配置 [Custom].Tx_Ukey`)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
loger.Debug(`Body: %v`, body)
|
loger.Debug(`Body: %v`, body)
|
||||||
var resp refreshData
|
var resp refreshData
|
||||||
signature := sign(bytesconv.StringToBytes(body))
|
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),
|
ztool.Str_FastConcat(`https://u`, surl, `.y.qq.com/cgi-bin/musics.fcg?sign=`, signature),
|
||||||
strings.NewReader(body),
|
strings.NewReader(body),
|
||||||
[]ztool.Net_ReqHandlerFunc{
|
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeaders(header)},
|
||||||
ztool.Net_ReqAddHeaders(header),
|
[]ztool.Net_ResHandlerFunc{ztool.Net_ResToStruct(&resp)},
|
||||||
},
|
|
||||||
[]ztool.Net_ResHandlerFunc{
|
|
||||||
ztool.Net_ResToStruct(&resp),
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
loger.Error(`请求Api失败: %s`, err)
|
// loger.Error(`请求Api失败: %s`, err)
|
||||||
return
|
return errors.New(`请求Api失败: ` + err.Error())
|
||||||
}
|
}
|
||||||
|
loger.Debug(`Resp: %+v`, resp)
|
||||||
if resp.Req1.Code != 0 {
|
if resp.Req1.Code != 0 {
|
||||||
loger.Warn("刷新登录失败, code: %v\n响应体: %+v", resp.Req1.Code, resp)
|
switch resp.Req1.Code {
|
||||||
return
|
case 1000:
|
||||||
|
return fmt.Errorf(`%v: Token无效或已过期`, resp.Req1.Code)
|
||||||
|
case 2000:
|
||||||
|
return fmt.Errorf(`%v: 该Token不支持刷新`, resp.Req1.Code)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf(`%v: 刷新登录失败`, resp.Req1.Code)
|
||||||
|
}
|
||||||
|
// loger.Warn("刷新登录失败, code: %v\n响应体: %+v", resp.Req1.Code, resp)
|
||||||
|
// return fmt.Errorf("刷新登录失败, code: %v\n响应体: %+v", resp.Req1.Code, resp)
|
||||||
}
|
}
|
||||||
loger.Info(`刷新登录成功`)
|
loger.Info(`刷新登录成功`)
|
||||||
env.Config.Custom.Tx_Uuin = resp.Req1.Data.StrMusicId
|
env.Config.Custom.Tx_Uuin = resp.Req1.Data.StrMusicId
|
||||||
env.Config.Custom.Tx_Ukey = resp.Req1.Data.MusicKey
|
env.Config.Custom.Tx_Ukey = resp.Req1.Data.MusicKey
|
||||||
env.Config.Custom.Tx_Refresh_Interval = resp.Req1.Data.ExpiredAt - 86000 // 提前一天
|
tnow := time.Now()
|
||||||
|
env.Config.Custom.Tx_Refresh_Interval = time.Date(tnow.Year(), tnow.Month(), tnow.Day()+5, 0, 0, 0, 0, tnow.Location()).Unix()
|
||||||
|
// env.Config.Custom.Tx_Refresh_Interval = now + 432000 //(每5天刷新一次) //1209600 - 86000 // 14天提前一天
|
||||||
loger.Debug(`Resp: %+v`, resp)
|
loger.Debug(`Resp: %+v`, resp)
|
||||||
loger.Debug(`Uuin: %v, Ukey: %v`, resp.Req1.Data.StrMusicId, resp.Req1.Data.MusicKey)
|
loger.Debug(`Uuin: %v, Ukey: %v`, resp.Req1.Data.StrMusicId, resp.Req1.Data.MusicKey)
|
||||||
loger.Debug(`ExpiresAt: %v`, resp.Req1.Data.ExpiredAt)
|
loger.Debug(`ExpiresAt: %v, Real: %v`, resp.Req1.Data.ExpiredAt, env.Config.Custom.Tx_Refresh_Interval)
|
||||||
err = env.Cfg.Save(``)
|
err = env.Cfg.Save(``)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
loger.Error(`%s`, err)
|
// loger.Error(`%s`, err)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
if err == nil {
|
||||||
loger.Info(`数据更新成功`) // 已通过相应数据更新uin和qqmusic_key
|
loger.Info(`数据更新成功`) // 已通过相应数据更新uin和qqmusic_key
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
env.Inits.Add(func() {
|
||||||
|
if env.Config.Custom.Tx_Refresh_Enable && env.Config.Custom.Tx_Ukey != `` && env.Config.Custom.Tx_Uuin != `` {
|
||||||
|
env.Tasker.Add(`tx_refresh`, refresh, 86000, true)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
246
src/sources/custom/tx/types.go
Normal file
246
src/sources/custom/tx/types.go
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
package tx
|
||||||
|
|
||||||
|
type musicInfo struct {
|
||||||
|
// Info struct {
|
||||||
|
// Company struct {
|
||||||
|
// Title string `json:"title"`
|
||||||
|
// Type string `json:"type"`
|
||||||
|
// Content []struct {
|
||||||
|
// ID int `json:"id"`
|
||||||
|
// Value string `json:"value"`
|
||||||
|
// Mid string `json:"mid"`
|
||||||
|
// Type int `json:"type"`
|
||||||
|
// ShowType int `json:"show_type"`
|
||||||
|
// IsParent int `json:"is_parent"`
|
||||||
|
// Picurl string `json:"picurl"`
|
||||||
|
// ReadCnt int `json:"read_cnt"`
|
||||||
|
// Author string `json:"author"`
|
||||||
|
// Jumpurl string `json:"jumpurl"`
|
||||||
|
// OriPicurl string `json:"ori_picurl"`
|
||||||
|
// } `json:"content"`
|
||||||
|
// Pos int `json:"pos"`
|
||||||
|
// More int `json:"more"`
|
||||||
|
// Selected string `json:"selected"`
|
||||||
|
// UsePlatform int `json:"use_platform"`
|
||||||
|
// } `json:"company"`
|
||||||
|
// Genre struct {
|
||||||
|
// Title string `json:"title"`
|
||||||
|
// Type string `json:"type"`
|
||||||
|
// Content []struct {
|
||||||
|
// ID int `json:"id"`
|
||||||
|
// Value string `json:"value"`
|
||||||
|
// Mid string `json:"mid"`
|
||||||
|
// Type int `json:"type"`
|
||||||
|
// ShowType int `json:"show_type"`
|
||||||
|
// IsParent int `json:"is_parent"`
|
||||||
|
// Picurl string `json:"picurl"`
|
||||||
|
// ReadCnt int `json:"read_cnt"`
|
||||||
|
// Author string `json:"author"`
|
||||||
|
// Jumpurl string `json:"jumpurl"`
|
||||||
|
// OriPicurl string `json:"ori_picurl"`
|
||||||
|
// } `json:"content"`
|
||||||
|
// Pos int `json:"pos"`
|
||||||
|
// More int `json:"more"`
|
||||||
|
// Selected string `json:"selected"`
|
||||||
|
// UsePlatform int `json:"use_platform"`
|
||||||
|
// } `json:"genre"`
|
||||||
|
// Lan struct {
|
||||||
|
// Title string `json:"title"`
|
||||||
|
// Type string `json:"type"`
|
||||||
|
// Content []struct {
|
||||||
|
// ID int `json:"id"`
|
||||||
|
// Value string `json:"value"`
|
||||||
|
// Mid string `json:"mid"`
|
||||||
|
// Type int `json:"type"`
|
||||||
|
// ShowType int `json:"show_type"`
|
||||||
|
// IsParent int `json:"is_parent"`
|
||||||
|
// Picurl string `json:"picurl"`
|
||||||
|
// ReadCnt int `json:"read_cnt"`
|
||||||
|
// Author string `json:"author"`
|
||||||
|
// Jumpurl string `json:"jumpurl"`
|
||||||
|
// OriPicurl string `json:"ori_picurl"`
|
||||||
|
// } `json:"content"`
|
||||||
|
// Pos int `json:"pos"`
|
||||||
|
// More int `json:"more"`
|
||||||
|
// Selected string `json:"selected"`
|
||||||
|
// UsePlatform int `json:"use_platform"`
|
||||||
|
// } `json:"lan"`
|
||||||
|
// } `json:"info"`
|
||||||
|
// Extras struct {
|
||||||
|
// Name string `json:"name"`
|
||||||
|
// Transname string `json:"transname"`
|
||||||
|
// Subtitle string `json:"subtitle"`
|
||||||
|
// From string `json:"from"`
|
||||||
|
// Wikiurl string `json:"wikiurl"`
|
||||||
|
// } `json:"extras"`
|
||||||
|
TrackInfo struct {
|
||||||
|
// ID int `json:"id"`
|
||||||
|
// Type int `json:"type"`
|
||||||
|
// Mid string `json:"mid"`
|
||||||
|
// Name string `json:"name"`
|
||||||
|
// Title string `json:"title"`
|
||||||
|
// Subtitle string `json:"subtitle"`
|
||||||
|
// Singer []struct {
|
||||||
|
// ID int `json:"id"`
|
||||||
|
// Mid string `json:"mid"`
|
||||||
|
// Name string `json:"name"`
|
||||||
|
// Title string `json:"title"`
|
||||||
|
// Type int `json:"type"`
|
||||||
|
// Uin int `json:"uin"`
|
||||||
|
// } `json:"singer"`
|
||||||
|
// Album struct {
|
||||||
|
// ID int `json:"id"`
|
||||||
|
// Mid string `json:"mid"`
|
||||||
|
// Name string `json:"name"`
|
||||||
|
// Title string `json:"title"`
|
||||||
|
// Subtitle string `json:"subtitle"`
|
||||||
|
// TimePublic string `json:"time_public"`
|
||||||
|
// Pmid string `json:"pmid"`
|
||||||
|
// } `json:"album"`
|
||||||
|
// Mv struct {
|
||||||
|
// ID int `json:"id"`
|
||||||
|
// Vid string `json:"vid"`
|
||||||
|
// Name string `json:"name"`
|
||||||
|
// Title string `json:"title"`
|
||||||
|
// Vt int `json:"vt"`
|
||||||
|
// } `json:"mv"`
|
||||||
|
// Interval int `json:"interval"`
|
||||||
|
// Isonly int `json:"isonly"`
|
||||||
|
// Language int `json:"language"`
|
||||||
|
// Genre int `json:"genre"`
|
||||||
|
// IndexCd int `json:"index_cd"`
|
||||||
|
// IndexAlbum int `json:"index_album"`
|
||||||
|
// TimePublic string `json:"time_public"`
|
||||||
|
// Status int `json:"status"`
|
||||||
|
// Fnote int `json:"fnote"`
|
||||||
|
File struct {
|
||||||
|
MediaMid string `json:"media_mid"`
|
||||||
|
// Size24Aac int `json:"size_24aac"`
|
||||||
|
// Size48Aac int `json:"size_48aac"`
|
||||||
|
// Size96Aac int `json:"size_96aac"`
|
||||||
|
// Size192Ogg int `json:"size_192ogg"`
|
||||||
|
// Size192Aac int `json:"size_192aac"`
|
||||||
|
// Size128Mp3 int `json:"size_128mp3"`
|
||||||
|
// Size320Mp3 int `json:"size_320mp3"`
|
||||||
|
// SizeApe int `json:"size_ape"`
|
||||||
|
// SizeFlac int `json:"size_flac"`
|
||||||
|
// SizeDts int `json:"size_dts"`
|
||||||
|
// SizeTry int `json:"size_try"`
|
||||||
|
// TryBegin int `json:"try_begin"`
|
||||||
|
// TryEnd int `json:"try_end"`
|
||||||
|
// URL string `json:"url"`
|
||||||
|
// SizeHires int `json:"size_hires"`
|
||||||
|
// HiresSample int `json:"hires_sample"`
|
||||||
|
// HiresBitdepth int `json:"hires_bitdepth"`
|
||||||
|
// B30S int `json:"b_30s"`
|
||||||
|
// E30S int `json:"e_30s"`
|
||||||
|
// Size96Ogg int `json:"size_96ogg"`
|
||||||
|
// Size360Ra []interface{} `json:"size_360ra"`
|
||||||
|
// SizeDolby int `json:"size_dolby"`
|
||||||
|
// SizeNew []int `json:"size_new"`
|
||||||
|
} `json:"file"`
|
||||||
|
Pay struct {
|
||||||
|
// PayMonth int `json:"pay_month"`
|
||||||
|
// PriceTrack int `json:"price_track"`
|
||||||
|
// PriceAlbum int `json:"price_album"`
|
||||||
|
PayPlay int `json:"pay_play"`
|
||||||
|
// PayDown int `json:"pay_down"`
|
||||||
|
// PayStatus int `json:"pay_status"`
|
||||||
|
// TimeFree int `json:"time_free"`
|
||||||
|
} `json:"pay"`
|
||||||
|
// Action struct {
|
||||||
|
// Switch int `json:"switch"`
|
||||||
|
// Msgid int `json:"msgid"`
|
||||||
|
// Alert int `json:"alert"`
|
||||||
|
// Icons int `json:"icons"`
|
||||||
|
// Msgshare int `json:"msgshare"`
|
||||||
|
// Msgfav int `json:"msgfav"`
|
||||||
|
// Msgdown int `json:"msgdown"`
|
||||||
|
// Msgpay int `json:"msgpay"`
|
||||||
|
// Switch2 int `json:"switch2"`
|
||||||
|
// Icon2 int `json:"icon2"`
|
||||||
|
// } `json:"action"`
|
||||||
|
// Ksong struct {
|
||||||
|
// ID int `json:"id"`
|
||||||
|
// Mid string `json:"mid"`
|
||||||
|
// } `json:"ksong"`
|
||||||
|
// Volume struct {
|
||||||
|
// Gain float64 `json:"gain"`
|
||||||
|
// Peak float64 `json:"peak"`
|
||||||
|
// Lra float64 `json:"lra"`
|
||||||
|
// } `json:"volume"`
|
||||||
|
// Label string `json:"label"`
|
||||||
|
// URL string `json:"url"`
|
||||||
|
// Bpm int `json:"bpm"`
|
||||||
|
// Version int `json:"version"`
|
||||||
|
// Trace string `json:"trace"`
|
||||||
|
// DataType int `json:"data_type"`
|
||||||
|
// ModifyStamp int `json:"modify_stamp"`
|
||||||
|
// Pingpong string `json:"pingpong"`
|
||||||
|
// Ppurl string `json:"ppurl"`
|
||||||
|
// Tid int `json:"tid"`
|
||||||
|
// Ov int `json:"ov"`
|
||||||
|
// Sa int `json:"sa"`
|
||||||
|
// Es string `json:"es"`
|
||||||
|
Vs []string `json:"vs"`
|
||||||
|
// Vi []int `json:"vi"`
|
||||||
|
// Ktag string `json:"ktag"`
|
||||||
|
// Vf []float64 `json:"vf"`
|
||||||
|
} `json:"track_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type playInfo struct {
|
||||||
|
// Code int `json:"code"`
|
||||||
|
Data struct {
|
||||||
|
// Uin string `json:"uin"`
|
||||||
|
// Retcode int `json:"retcode"`
|
||||||
|
// VerifyType int `json:"verify_type"`
|
||||||
|
// LoginKey string `json:"login_key"`
|
||||||
|
// Msg string `json:"msg"`
|
||||||
|
// Sip []string `json:"sip"`
|
||||||
|
// Thirdip []string `json:"thirdip"`
|
||||||
|
// Testfile2G string `json:"testfile2g"`
|
||||||
|
// Testfilewifi string `json:"testfilewifi"`
|
||||||
|
Midurlinfo []struct {
|
||||||
|
// Songmid string `json:"songmid"`
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
Purl string `json:"purl"`
|
||||||
|
// Errtype string `json:"errtype"`
|
||||||
|
// P2Pfromtag int `json:"p2pfromtag"`
|
||||||
|
// Qmdlfromtag int `json:"qmdlfromtag"`
|
||||||
|
// CommonDownfromtag int `json:"common_downfromtag"`
|
||||||
|
// VipDownfromtag int `json:"vip_downfromtag"`
|
||||||
|
// Pdl int `json:"pdl"`
|
||||||
|
// Premain int `json:"premain"`
|
||||||
|
// Hisdown int `json:"hisdown"`
|
||||||
|
// Hisbuy int `json:"hisbuy"`
|
||||||
|
// UIAlert int `json:"uiAlert"`
|
||||||
|
// Isbuy int `json:"isbuy"`
|
||||||
|
// Pneedbuy int `json:"pneedbuy"`
|
||||||
|
// Pneed int `json:"pneed"`
|
||||||
|
// Isonly int `json:"isonly"`
|
||||||
|
// Onecan int `json:"onecan"`
|
||||||
|
// Result int `json:"result"`
|
||||||
|
// Tips string `json:"tips"`
|
||||||
|
// Opi48Kurl string `json:"opi48kurl"`
|
||||||
|
// Opi96Kurl string `json:"opi96kurl"`
|
||||||
|
// Opi192Kurl string `json:"opi192kurl"`
|
||||||
|
// Opiflackurl string `json:"opiflackurl"`
|
||||||
|
// Opi128Kurl string `json:"opi128kurl"`
|
||||||
|
// Opi192Koggurl string `json:"opi192koggurl"`
|
||||||
|
// Wififromtag string `json:"wififromtag"`
|
||||||
|
// Flowfromtag string `json:"flowfromtag"`
|
||||||
|
// Wifiurl string `json:"wifiurl"`
|
||||||
|
// Flowurl string `json:"flowurl"`
|
||||||
|
// Vkey string `json:"vkey"`
|
||||||
|
// Opi30Surl string `json:"opi30surl"`
|
||||||
|
// Ekey string `json:"ekey"`
|
||||||
|
// AuthSwitch int `json:"auth_switch"`
|
||||||
|
// Subcode int `json:"subcode"`
|
||||||
|
// Opi96Koggurl string `json:"opi96koggurl"`
|
||||||
|
// AuthSwitch2 int `json:"auth_switch2"`
|
||||||
|
} `json:"midurlinfo"`
|
||||||
|
// Servercheck string `json:"servercheck"`
|
||||||
|
// Expiration int `json:"expiration"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
@ -14,28 +14,28 @@ var (
|
|||||||
H string // 专用音质
|
H string // 专用音质
|
||||||
}{
|
}{
|
||||||
sources.Q_128k: {
|
sources.Q_128k: {
|
||||||
E: `.mp3`,
|
E: sources.X_mp3,
|
||||||
H: `M500`,
|
H: `M500`,
|
||||||
},
|
},
|
||||||
sources.Q_320k: {
|
sources.Q_320k: {
|
||||||
E: `.mp3`,
|
E: sources.X_mp3,
|
||||||
H: `M800`,
|
H: `M800`,
|
||||||
},
|
},
|
||||||
sources.Q_flac: {
|
sources.Q_flac: {
|
||||||
E: `.flac`,
|
E: sources.Q_flac,
|
||||||
H: `F000`,
|
H: `F000`,
|
||||||
},
|
},
|
||||||
sources.Q_fl24: {
|
sources.Q_fl24: {
|
||||||
E: `.flac`,
|
E: sources.Q_flac,
|
||||||
H: `RS01`,
|
H: `RS01`,
|
||||||
},
|
},
|
||||||
`dolby`: {
|
sources.Q_dolby: {
|
||||||
E: `.flac`,
|
E: sources.Q_flac,
|
||||||
H: `Q000`,
|
H: `Q000`,
|
||||||
},
|
},
|
||||||
`master`: {
|
sources.Q_master: {
|
||||||
E: `.flac`,
|
E: sources.Q_flac,
|
||||||
H: `AI00`,
|
H: `AI00`, // (~~母带音质大部分都是AI提上去的~~)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
// qualityMapReverse = map[string]string{
|
// qualityMapReverse = map[string]string{
|
||||||
|
@ -1,98 +0,0 @@
|
|||||||
package wy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/aes"
|
|
||||||
"crypto/cipher"
|
|
||||||
"encoding/base64"
|
|
||||||
|
|
||||||
"github.com/ZxwyWebSite/ztool"
|
|
||||||
"github.com/ZxwyWebSite/ztool/x/bytesconv"
|
|
||||||
"github.com/ZxwyWebSite/ztool/x/json"
|
|
||||||
"github.com/ZxwyWebSite/ztool/zcypt"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// __all__ = []string{`weEncrypt`, `linuxEncrypt`, `eEncrypt`}
|
|
||||||
// MODULUS = ztool.Str_FastConcat(
|
|
||||||
// `00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7`,
|
|
||||||
// `b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280`,
|
|
||||||
// `104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932`,
|
|
||||||
// `575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b`,
|
|
||||||
// `3ece0462db0a22b8e7`,
|
|
||||||
// )
|
|
||||||
// PUBKEY = `010001`
|
|
||||||
// NONCE = bytesconv.StringToBytes(`0CoJUm6Qyw8W8jud`)
|
|
||||||
// LINUXKEY = bytesconv.StringToBytes(`rFgB&h#%2?^eDg:Q`)
|
|
||||||
eapiKey = bytesconv.StringToBytes(`e82ckenh8dichen8`)
|
|
||||||
ivKey = bytesconv.StringToBytes(`0102030405060708`)
|
|
||||||
)
|
|
||||||
|
|
||||||
func eapiEncrypt(url, text string) map[string][]string {
|
|
||||||
digest := zcypt.CreateMD5(bytesconv.StringToBytes(ztool.Str_FastConcat(
|
|
||||||
`nobody`, url, `use`, text, `md5forencrypt`,
|
|
||||||
)))
|
|
||||||
data := ztool.Str_FastConcat(
|
|
||||||
url, `-36cd479b6b5-`, text, `-36cd479b6b5-`, digest,
|
|
||||||
)
|
|
||||||
// 注:JSON编码时会自动将[]byte转为string,这里省去一步转换
|
|
||||||
return map[string][]string{
|
|
||||||
`params`: {bytesconv.BytesToString(aesEncrypt(bytesconv.StringToBytes(data), eapiKey, false))},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// crypto.js
|
|
||||||
|
|
||||||
func aesEncrypt(text, key []byte, iv bool) []byte {
|
|
||||||
pad := 16 - len(text)%16
|
|
||||||
text = append(text, bytes.Repeat([]byte{byte(pad)}, pad)...)
|
|
||||||
block, _ := aes.NewCipher(key)
|
|
||||||
// if err != nil {
|
|
||||||
// panic(err)
|
|
||||||
// }
|
|
||||||
var encryptor cipher.BlockMode
|
|
||||||
if iv {
|
|
||||||
encryptor = cipher.NewCBCEncrypter(block, ivKey)
|
|
||||||
} else {
|
|
||||||
encryptor = zcypt.NewECBEncrypter(block)
|
|
||||||
}
|
|
||||||
ciphertext := make([]byte, len(text))
|
|
||||||
encryptor.CryptBlocks(ciphertext, text)
|
|
||||||
if iv {
|
|
||||||
return zcypt.Base64Encode(base64.StdEncoding, ciphertext)
|
|
||||||
}
|
|
||||||
return bytes.ToUpper(zcypt.HexEncode(ciphertext))
|
|
||||||
}
|
|
||||||
|
|
||||||
func eapi(url string, object map[string]any) map[string][]string {
|
|
||||||
text, err := json.Marshal(object)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
message := ztool.Str_FastConcat(
|
|
||||||
`nobody`, url, `use`, bytesconv.BytesToString(text), `md5forencrypt`,
|
|
||||||
)
|
|
||||||
digest := zcypt.CreateMD5(bytesconv.StringToBytes(message))
|
|
||||||
data := bytes.Join(
|
|
||||||
[][]byte{
|
|
||||||
bytesconv.StringToBytes(url),
|
|
||||||
text,
|
|
||||||
bytesconv.StringToBytes(digest),
|
|
||||||
},
|
|
||||||
[]byte{45, 51, 54, 99, 100, 52, 55, 57, 98, 54, 98, 53, 45},
|
|
||||||
)
|
|
||||||
return map[string][]string{
|
|
||||||
`params`: {bytesconv.BytesToString(aesEncrypt(data, eapiKey, false))},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func decrypt(data []byte) (out []byte) {
|
|
||||||
dec, err := zcypt.HexDecode(data)
|
|
||||||
if err == nil {
|
|
||||||
out, err = zcypt.AesDecrypt(dec, eapiKey)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
133
src/sources/custom/wy/modules/core_crypto.go
Normal file
133
src/sources/custom/wy/modules/core_crypto.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
package wy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/pem"
|
||||||
|
"math/rand"
|
||||||
|
_ "unsafe"
|
||||||
|
|
||||||
|
"github.com/ZxwyWebSite/ztool"
|
||||||
|
"github.com/ZxwyWebSite/ztool/x/bytesconv"
|
||||||
|
"github.com/ZxwyWebSite/ztool/x/json"
|
||||||
|
"github.com/ZxwyWebSite/ztool/zcypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// crypto.js
|
||||||
|
|
||||||
|
var (
|
||||||
|
ivKey = bytesconv.StringToBytes(`0102030405060708`)
|
||||||
|
presetKey = bytesconv.StringToBytes(`0CoJUm6Qyw8W8jud`)
|
||||||
|
linuxapiKey = bytesconv.StringToBytes(`rFgB&h#%2?^eDg:Q`)
|
||||||
|
base62 = bytesconv.StringToBytes(`abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`)
|
||||||
|
publicKey = bytesconv.StringToBytes(`-----BEGIN PUBLIC KEY-----
|
||||||
|
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7clFSs6sXqHauqKWqdtLkF2KexO40H1YTX8z2lSgBBOAxLsvaklV8k4cBFK9snQXE9/DDaFt6Rr7iVZMldczhC0JNgTz+SHXT6CBHuX3e9SdB1Ua44oncaTWz7OBGLbCiK45wIDAQAB
|
||||||
|
-----END PUBLIC KEY-----`)
|
||||||
|
eapiKey = bytesconv.StringToBytes(`e82ckenh8dichen8`)
|
||||||
|
)
|
||||||
|
|
||||||
|
func aesEncrypt(text, key []byte, iv bool) []byte {
|
||||||
|
pad := 16 - len(text)%16
|
||||||
|
text = append(text, bytes.Repeat([]byte{byte(pad)}, pad)...)
|
||||||
|
block, _ := aes.NewCipher(key)
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
var encryptor cipher.BlockMode
|
||||||
|
if iv {
|
||||||
|
encryptor = cipher.NewCBCEncrypter(block, ivKey)
|
||||||
|
} else {
|
||||||
|
encryptor = zcypt.NewECBEncrypter(block)
|
||||||
|
}
|
||||||
|
ciphertext := make([]byte, len(text))
|
||||||
|
encryptor.CryptBlocks(ciphertext, text)
|
||||||
|
if iv {
|
||||||
|
return zcypt.Base64Encode(base64.StdEncoding, ciphertext)
|
||||||
|
}
|
||||||
|
return bytes.ToUpper(zcypt.HexEncode(ciphertext))
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:linkname rsaEncryptNone crypto/rsa.encrypt
|
||||||
|
func rsaEncryptNone(*rsa.PublicKey, []byte) ([]byte, error)
|
||||||
|
|
||||||
|
func rsaEncrypt(data []byte) string {
|
||||||
|
pblock, _ := pem.Decode(publicKey)
|
||||||
|
pubKey, _ := x509.ParsePKIXPublicKey(pblock.Bytes)
|
||||||
|
// 注:为实现NONE加密手动导出了标准库里的encrypt方法,若编译不过添加以下代码
|
||||||
|
// /usr/local/go/src/crypto/rsa/rsa.go:478
|
||||||
|
// ```
|
||||||
|
// var Encrypt = encrypt // export
|
||||||
|
// ```
|
||||||
|
// 第二种方式:linkname调用,不用改库 https://www.jianshu.com/p/7b3638b47845
|
||||||
|
encData, err := rsaEncryptNone(pubKey.(*rsa.PublicKey), data)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return zcypt.HexToString(encData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func weapi(object map[string]any) map[string][]string {
|
||||||
|
text, err := json.Marshal(object)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
secretKey := make([]byte, 16)
|
||||||
|
for i := 0; i < 16; i++ {
|
||||||
|
secretKey[i] = base62[rand.Intn(62)]
|
||||||
|
}
|
||||||
|
return map[string][]string{
|
||||||
|
`params`: {bytesconv.BytesToString(aesEncrypt(
|
||||||
|
aesEncrypt(text, presetKey, true),
|
||||||
|
secretKey,
|
||||||
|
true,
|
||||||
|
))},
|
||||||
|
`encSecKey`: {rsaEncrypt(ztool.Sort_ReverseNew(secretKey))},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func linuxapi(object map[string]any) map[string][]string {
|
||||||
|
text, err := json.Marshal(object)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return map[string][]string{
|
||||||
|
`eparams`: {bytesconv.BytesToString(aesEncrypt(text, linuxapiKey, false))},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func eapi(url string, object map[string]any) map[string][]string {
|
||||||
|
text, err := json.Marshal(object)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
message := ztool.Str_FastConcat(
|
||||||
|
`nobody`, url, `use`, bytesconv.BytesToString(text), `md5forencrypt`,
|
||||||
|
)
|
||||||
|
digest := zcypt.CreateMD5(bytesconv.StringToBytes(message))
|
||||||
|
data := bytes.Join(
|
||||||
|
[][]byte{
|
||||||
|
bytesconv.StringToBytes(url),
|
||||||
|
text,
|
||||||
|
bytesconv.StringToBytes(digest),
|
||||||
|
},
|
||||||
|
[]byte{45, 51, 54, 99, 100, 52, 55, 57, 98, 54, 98, 53, 45},
|
||||||
|
)
|
||||||
|
return map[string][]string{
|
||||||
|
`params`: {bytesconv.BytesToString(aesEncrypt(data, eapiKey, false))},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decrypt(data []byte) (out []byte) {
|
||||||
|
dec, err := zcypt.HexDecode(data)
|
||||||
|
if err == nil {
|
||||||
|
out, err = zcypt.AesDecrypt(dec, eapiKey)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
@ -20,7 +20,11 @@ import (
|
|||||||
|
|
||||||
const anonymous_token = `1f5fa7b6a6a9f81a11886e5186fde7fb98e25cf0036d7afd055b980b2261f5464b7f5273fc3921d1262bfec66a19a30c41d8da00c3685f5ace96f0d5a48b6db334d974731083682e3324751bcc9aaf44c3061cd1`
|
const anonymous_token = `1f5fa7b6a6a9f81a11886e5186fde7fb98e25cf0036d7afd055b980b2261f5464b7f5273fc3921d1262bfec66a19a30c41d8da00c3685f5ace96f0d5a48b6db334d974731083682e3324751bcc9aaf44c3061cd1`
|
||||||
|
|
||||||
var wapiReg = regexp.MustCompile(`\w*api`)
|
var (
|
||||||
|
wapiReg = regexp.MustCompile(`\w*api`)
|
||||||
|
csrfReg = regexp.MustCompile(`_csrf=([^(;|$)]+)`)
|
||||||
|
domaReg = regexp.MustCompile(`\s*Domain=[^(;|$)]+;*`)
|
||||||
|
)
|
||||||
|
|
||||||
var userAgentMap = map[string]string{
|
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`,
|
`mobile`: `Mozilla/5.0 (iPhone; CPU iPhone OS 17_2_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1`,
|
||||||
@ -28,24 +32,30 @@ var userAgentMap = map[string]string{
|
|||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// reqCookie struct{}
|
ReqQuery struct {
|
||||||
|
Cookie map[string]string
|
||||||
|
RealIP string
|
||||||
|
Ids string
|
||||||
|
Br string
|
||||||
|
Level string
|
||||||
|
}
|
||||||
reqOptions struct {
|
reqOptions struct {
|
||||||
UA string
|
|
||||||
Headers map[string]string
|
Headers map[string]string
|
||||||
|
UA string
|
||||||
RealIP string
|
RealIP string
|
||||||
IP string
|
IP string
|
||||||
Cookie interface{}
|
|
||||||
Crypto string
|
Crypto string
|
||||||
Url string
|
Url string
|
||||||
|
Cookie interface{}
|
||||||
}
|
}
|
||||||
reqAnswer struct {
|
ReqAnswer struct {
|
||||||
Status int
|
Status int
|
||||||
Body map[string]any
|
Body map[string]any
|
||||||
Cookie []string
|
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 {
|
if options.Headers == nil {
|
||||||
options.Headers = make(map[string]string)
|
options.Headers = make(map[string]string)
|
||||||
}
|
}
|
||||||
@ -96,26 +106,24 @@ func createRequest(method, url string, data map[string]any, options reqOptions)
|
|||||||
var form stdurl.Values
|
var form stdurl.Values
|
||||||
switch options.Crypto {
|
switch options.Crypto {
|
||||||
case `weapi`:
|
case `weapi`:
|
||||||
panic(`not support`)
|
options.Headers[`User-Agent`] = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69`
|
||||||
// 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`])
|
||||||
// reg := regexp.MustCompile(`_csrf=([^(;|$)]+)`)
|
if len(csrfToken) > 1 {
|
||||||
// csrfToken := reg.FindStringSubmatch(options.Headers[`Cookie`])
|
data[`csrf_token`] = csrfToken[1]
|
||||||
// if len(csrfToken) > 1 {
|
} else {
|
||||||
// data[`csrf_token`] = csrfToken[1]
|
data[`csrf_token`] = ``
|
||||||
// } else {
|
}
|
||||||
// data[`csrf_token`] = ``
|
form = weapi(data)
|
||||||
// }
|
// fmt.Println(form.Encode())
|
||||||
// data = weapi(data)
|
url = wapiReg.ReplaceAllString(url, `weapi`)
|
||||||
// url = wapiReg.ReplaceAllString(url, `weapi`)
|
|
||||||
case `linuxapi`:
|
case `linuxapi`:
|
||||||
panic(`not support`)
|
form = linuxapi(map[string]any{
|
||||||
// data = linuxapi(
|
`method`: method,
|
||||||
// method,
|
`url`: wapiReg.ReplaceAllString(url, `weapi`),
|
||||||
// wapiReg.ReplaceAllString(url, `weapi`),
|
`params`: data,
|
||||||
// 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`
|
||||||
// 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`
|
||||||
// url = `https://music.163.com/api/linux/forward`
|
|
||||||
case `eapi`:
|
case `eapi`:
|
||||||
cookie, ok := options.Cookie.(map[string]string)
|
cookie, ok := options.Cookie.(map[string]string)
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -168,7 +176,7 @@ func createRequest(method, url string, data map[string]any, options reqOptions)
|
|||||||
// for k, v := range data {
|
// for k, v := range data {
|
||||||
// values.Add(k, v)
|
// 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,
|
err := ztool.Net_Request(method, url,
|
||||||
strings.NewReader(form.Encode()),
|
strings.NewReader(form.Encode()),
|
||||||
[]ztool.Net_ReqHandlerFunc{
|
[]ztool.Net_ReqHandlerFunc{
|
||||||
@ -178,17 +186,20 @@ func createRequest(method, url string, data map[string]any, options reqOptions)
|
|||||||
func(res *http.Response) error {
|
func(res *http.Response) error {
|
||||||
body, err := io.ReadAll(res.Body)
|
body, err := io.ReadAll(res.Body)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
answer.Cookie = res.Header[`Set-Cookie`]
|
// fmt.Println(`body:`, string(body), "\nstr:", body)
|
||||||
reg := regexp.MustCompile(`\s*Domain=[^(;|$)]+;*`)
|
if len(body) == 0 {
|
||||||
|
err = errors.New(`nil Body`)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
answer.Cookie = res.Header[`Set-Cookie`] //res.Header.Values(`set-cookie`)
|
||||||
for i, v := range answer.Cookie {
|
for i, v := range answer.Cookie {
|
||||||
answer.Cookie[i] = reg.ReplaceAllString(v, ``)
|
answer.Cookie[i] = domaReg.ReplaceAllString(v, ``)
|
||||||
}
|
}
|
||||||
if options.Crypto == `eapi` && body[0] != '{' {
|
if options.Crypto == `eapi` && body[0] != '{' {
|
||||||
err = json.Unmarshal(decrypt(body), &answer.Body)
|
err = json.Unmarshal(decrypt(body), &answer.Body)
|
||||||
} else {
|
} else {
|
||||||
err = json.Unmarshal(body, &answer.Body)
|
err = json.Unmarshal(body, &answer.Body)
|
||||||
}
|
}
|
||||||
if err == nil {
|
|
||||||
if code, ok := answer.Body[`code`].(string); ok {
|
if code, ok := answer.Body[`code`].(string); ok {
|
||||||
answer.Body[`code`], err = strconv.Atoi(code)
|
answer.Body[`code`], err = strconv.Atoi(code)
|
||||||
} else {
|
} else {
|
||||||
@ -212,10 +223,10 @@ func createRequest(method, url string, data map[string]any, options reqOptions)
|
|||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
answer.Status = 502
|
answer.Status = 502
|
||||||
answer.Body = map[string]any{`code`: 502, `msg`: err}
|
answer.Body = map[string]any{`code`: 502, `msg`: err.Error()}
|
||||||
return err
|
// return err
|
||||||
}
|
}
|
||||||
return nil
|
return err // nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
96
src/sources/custom/wy/modules/core_types.go
Normal file
96
src/sources/custom/wy/modules/core_types.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package wy
|
||||||
|
|
||||||
|
type (
|
||||||
|
// 音乐URL
|
||||||
|
PlayInfo struct {
|
||||||
|
Data []struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Br int `json:"br"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
Md5 string `json:"md5"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
Expi int `json:"expi"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Gain float64 `json:"gain"`
|
||||||
|
Peak float64 `json:"peak"`
|
||||||
|
Fee int `json:"fee"`
|
||||||
|
Uf interface{} `json:"uf"`
|
||||||
|
Payed int `json:"payed"`
|
||||||
|
Flag int `json:"flag"`
|
||||||
|
CanExtend bool `json:"canExtend"`
|
||||||
|
FreeTrialInfo *struct {
|
||||||
|
AlgData interface{} `json:"algData"`
|
||||||
|
End int `json:"end"`
|
||||||
|
FragmentType int `json:"fragmentType"`
|
||||||
|
Start int `json:"start"`
|
||||||
|
} `json:"freeTrialInfo"`
|
||||||
|
Level string `json:"level"`
|
||||||
|
EncodeType string `json:"encodeType"`
|
||||||
|
FreeTrialPrivilege struct {
|
||||||
|
ResConsumable bool `json:"resConsumable"`
|
||||||
|
UserConsumable bool `json:"userConsumable"`
|
||||||
|
ListenType int `json:"listenType"`
|
||||||
|
CannotListenReason int `json:"cannotListenReason"`
|
||||||
|
PlayReason interface{} `json:"playReason"`
|
||||||
|
} `json:"freeTrialPrivilege"`
|
||||||
|
FreeTimeTrialPrivilege struct {
|
||||||
|
ResConsumable bool `json:"resConsumable"`
|
||||||
|
UserConsumable bool `json:"userConsumable"`
|
||||||
|
Type int `json:"type"`
|
||||||
|
RemainTime int `json:"remainTime"`
|
||||||
|
} `json:"freeTimeTrialPrivilege"`
|
||||||
|
URLSource int `json:"urlSource"`
|
||||||
|
RightSource int `json:"rightSource"`
|
||||||
|
PodcastCtrp interface{} `json:"podcastCtrp"`
|
||||||
|
EffectTypes interface{} `json:"effectTypes"`
|
||||||
|
Time int `json:"time"`
|
||||||
|
} `json:"data"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
// 音乐是否可用
|
||||||
|
VerifyInfo struct {
|
||||||
|
Code int16 `json:"code"`
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
// 音质数据
|
||||||
|
QualityData struct {
|
||||||
|
Br int `json:"br"` // 比特率 Bit Rate
|
||||||
|
Fid int `json:"fid"` // ?
|
||||||
|
Size int `json:"size"` // 文件大小
|
||||||
|
Vd float64 `json:"vd"` // Volume Delta
|
||||||
|
Sr int `json:"sr"` // 采样率 Sample Rate
|
||||||
|
}
|
||||||
|
// 歌曲音质详情
|
||||||
|
QualityDetail struct {
|
||||||
|
Data struct {
|
||||||
|
SongID int `json:"songId"`
|
||||||
|
H QualityData `json:"h"` // 高质量文件信息
|
||||||
|
M QualityData `json:"m"` // 中质量文件信息
|
||||||
|
L QualityData `json:"l"` // 低质量文件信息
|
||||||
|
Sq QualityData `json:"sq"` // 无损质量文件信息
|
||||||
|
Hr QualityData `json:"hr"` // Hi-Res质量文件信息
|
||||||
|
Db QualityData `json:"db"` // 杜比音质
|
||||||
|
Jm QualityData `json:"jm"` // jymaster(超清母带)
|
||||||
|
Je QualityData `json:"je"` // jyeffect(高清环绕声)
|
||||||
|
Sk QualityData `json:"sk"` // sky(沉浸环绕声)
|
||||||
|
} `json:"data"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Error bool `json:"error"`
|
||||||
|
}
|
||||||
|
// 扫码登录请求
|
||||||
|
QrKey struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
UniKey string `json:"unikey"`
|
||||||
|
}
|
||||||
|
// 扫码登录结果
|
||||||
|
QrCheck struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
AvatarUrl string `json:"avatarUrl"`
|
||||||
|
NickName string `json:"nickname"`
|
||||||
|
}
|
||||||
|
)
|
19
src/sources/custom/wy/modules/login_qr_check.go
Normal file
19
src/sources/custom/wy/modules/login_qr_check.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package wy
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// 二维码检测扫码状态接口
|
||||||
|
func LoginQrCheck(key string) (*ReqAnswer, error) {
|
||||||
|
res, err := createRequest(
|
||||||
|
http.MethodPost,
|
||||||
|
`https://music.163.com/weapi/login/qrcode/client/login`,
|
||||||
|
map[string]any{
|
||||||
|
`key`: key,
|
||||||
|
`type`: 1,
|
||||||
|
},
|
||||||
|
reqOptions{
|
||||||
|
Crypto: `weapi`,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return res, err
|
||||||
|
}
|
6
src/sources/custom/wy/modules/login_qr_create.go
Normal file
6
src/sources/custom/wy/modules/login_qr_create.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package wy
|
||||||
|
|
||||||
|
// 二维码生成接口
|
||||||
|
func LoginQrCreate(key string) string {
|
||||||
|
return `https://music.163.com/login?codekey=` + key
|
||||||
|
}
|
19
src/sources/custom/wy/modules/login_qr_key.go
Normal file
19
src/sources/custom/wy/modules/login_qr_key.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package wy
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// 二维码 key 生成接口
|
||||||
|
func LoginQrKey() (*ReqAnswer, error) {
|
||||||
|
res, err := createRequest(
|
||||||
|
http.MethodPost,
|
||||||
|
`https://music.163.com/weapi/login/qrcode/unikey`,
|
||||||
|
map[string]any{
|
||||||
|
`type`: 1,
|
||||||
|
},
|
||||||
|
reqOptions{
|
||||||
|
Crypto: `weapi`,
|
||||||
|
Cookie: nil,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return res, err
|
||||||
|
}
|
27
src/sources/custom/wy/modules/login_refresh.go
Normal file
27
src/sources/custom/wy/modules/login_refresh.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package wy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 登录刷新
|
||||||
|
func LoginRefresh(query ReqQuery) (*ReqAnswer, error) {
|
||||||
|
res, err := createRequest(
|
||||||
|
http.MethodPost,
|
||||||
|
`https://music.163.com/weapi/login/token/refresh`,
|
||||||
|
map[string]any{},
|
||||||
|
reqOptions{
|
||||||
|
Crypto: `weapi`,
|
||||||
|
UA: `pc`,
|
||||||
|
Cookie: query.Cookie,
|
||||||
|
RealIP: query.RealIP,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if code, ok := res.Body[`code`].(int); ok && err == nil {
|
||||||
|
if code == 200 {
|
||||||
|
res.Body[`cookie`] = strings.Join(res.Cookie, `;`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, err
|
||||||
|
}
|
73
src/sources/custom/wy/modules/song_url.go
Normal file
73
src/sources/custom/wy/modules/song_url.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package wy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ZxwyWebSite/ztool"
|
||||||
|
)
|
||||||
|
|
||||||
|
// type Query_song_url struct {
|
||||||
|
// Cookie map[string]string
|
||||||
|
// Ids string
|
||||||
|
// Br string
|
||||||
|
// RealIP string
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 歌曲链接
|
||||||
|
func SongUrl(query ReqQuery) (*ReqAnswer, error) {
|
||||||
|
if query.Cookie == nil {
|
||||||
|
query.Cookie = make(map[string]string)
|
||||||
|
}
|
||||||
|
query.Cookie[`os`] = `pc`
|
||||||
|
if query.Br == `` {
|
||||||
|
query.Br = `999000`
|
||||||
|
}
|
||||||
|
ids := strings.Split(query.Ids, `,`)
|
||||||
|
// idj, err := json.Marshal(ids)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
data := map[string]any{
|
||||||
|
`ids`: ztool.Str_FastConcat(`["`, strings.Join(ids, `","`), `"]`), //bytesconv.BytesToString(idj), //`["1998644237"]`,
|
||||||
|
`br`: query.Br, //ztool.Str_Select(query.Br, `999000`),
|
||||||
|
}
|
||||||
|
res, err := createRequest(
|
||||||
|
http.MethodPost,
|
||||||
|
`https://interface3.music.163.com/eapi/song/enhance/player/url`,
|
||||||
|
data,
|
||||||
|
reqOptions{
|
||||||
|
Crypto: `eapi`,
|
||||||
|
Cookie: query.Cookie,
|
||||||
|
RealIP: query.RealIP,
|
||||||
|
Url: `/api/song/enhance/player/url`,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
// 根据id排序
|
||||||
|
if length := len(ids); length > 1 && err == nil {
|
||||||
|
indexOf := make(map[string]int, length)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
indexOf[ids[i]] = i
|
||||||
|
}
|
||||||
|
if data, ok := res.Body[`data`].([]interface{}); ok {
|
||||||
|
sort.SliceStable(data, func(a, b int) bool {
|
||||||
|
da, oa := data[a].(map[string]interface{})
|
||||||
|
db, ob := data[b].(map[string]interface{})
|
||||||
|
if oa && ob {
|
||||||
|
ia, ka := da[`id`].(float64)
|
||||||
|
ib, kb := db[`id`].(float64)
|
||||||
|
if ka && kb {
|
||||||
|
ta := strconv.FormatInt(int64(ia), 10)
|
||||||
|
tb := strconv.FormatInt(int64(ib), 10)
|
||||||
|
return indexOf[ta] < indexOf[tb]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
res.Body[`data`] = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, err
|
||||||
|
}
|
37
src/sources/custom/wy/modules/song_url_v1.go
Normal file
37
src/sources/custom/wy/modules/song_url_v1.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package wy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/ZxwyWebSite/ztool"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 歌曲链接 - v1
|
||||||
|
// 此版本不再采用 br 作为音质区分的标准
|
||||||
|
// 而是采用 standard, exhigh, lossless, hires, jyeffect(高清环绕声), sky(沉浸环绕声), jymaster(超清母带) 进行音质判断
|
||||||
|
func SongUrlV1(query ReqQuery) (*ReqAnswer, error) {
|
||||||
|
if query.Cookie == nil {
|
||||||
|
query.Cookie = make(map[string]string)
|
||||||
|
}
|
||||||
|
query.Cookie[`os`] = `android`
|
||||||
|
query.Cookie[`appver`] = `8.10.05`
|
||||||
|
data := map[string]any{
|
||||||
|
`ids`: ztool.Str_FastConcat(`[`, query.Ids, `]`),
|
||||||
|
`level`: query.Level,
|
||||||
|
`encodeType`: `flac`,
|
||||||
|
}
|
||||||
|
if query.Level == `sky` /*|| query.Level == `jysky`*/ {
|
||||||
|
data[`immerseType`] = `c51`
|
||||||
|
}
|
||||||
|
return createRequest(
|
||||||
|
http.MethodPost,
|
||||||
|
`https://interface.music.163.com/eapi/song/enhance/player/url/v1`,
|
||||||
|
data,
|
||||||
|
reqOptions{
|
||||||
|
Crypto: `eapi`,
|
||||||
|
Cookie: query.Cookie,
|
||||||
|
RealIP: query.RealIP,
|
||||||
|
Url: `/api/song/enhance/player/url/v1`,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
@ -1,34 +1,126 @@
|
|||||||
package wy
|
package wy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"lx-source/src/env"
|
"lx-source/src/env"
|
||||||
"lx-source/src/sources"
|
"lx-source/src/sources"
|
||||||
"lx-source/src/sources/builtin"
|
|
||||||
"lx-source/src/sources/custom/utils"
|
"lx-source/src/sources/custom/utils"
|
||||||
|
wm "lx-source/src/sources/custom/wy/modules"
|
||||||
|
"lx-source/src/sources/example"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ZxwyWebSite/ztool"
|
"github.com/ZxwyWebSite/ztool"
|
||||||
"github.com/ZxwyWebSite/ztool/x/cookie"
|
"github.com/ZxwyWebSite/ztool/x/cookie"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Url(songMid, quality string) (ourl, msg string) {
|
var (
|
||||||
|
wy_pool = &sync.Pool{New: func() any { return new(wm.PlayInfo) }}
|
||||||
|
// wv_pool *sync.Pool
|
||||||
|
|
||||||
|
Url func(string, string) (string, string)
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
env.Inits.Add(func() {
|
||||||
|
loger := env.Loger.NewGroup(`WyInit`)
|
||||||
|
switch env.Config.Custom.Wy_Mode {
|
||||||
|
case `0`, `builtin`:
|
||||||
|
loger.Debug(`use builtin`)
|
||||||
|
// if env.Config.Source.MusicIdVerify {
|
||||||
|
// wv_pool = &sync.Pool{New: func() any { return new(verifyInfo) }}
|
||||||
|
// }
|
||||||
|
Url = builtin
|
||||||
|
case `1`, `163api`:
|
||||||
|
if env.Config.Custom.Wy_Api_Cookie == `` {
|
||||||
|
loger.Fatal(`使用163api且Cookie参数为空`)
|
||||||
|
}
|
||||||
|
switch env.Config.Custom.Wy_Api_Type {
|
||||||
|
case `0`, `native`:
|
||||||
|
loger.Debug(`use 163api module`)
|
||||||
|
Url = nmModule
|
||||||
|
case `1`, `remote`:
|
||||||
|
loger.Debug(`use 163api custom`)
|
||||||
|
if env.Config.Custom.Wy_Api_Address == `` {
|
||||||
|
loger.Fatal(`自定义接口地址为空`)
|
||||||
|
}
|
||||||
|
if env.Config.Custom.Wy_Api_Address[len(env.Config.Custom.Wy_Api_Address)-1] != '/' {
|
||||||
|
env.Config.Custom.Wy_Api_Address += "/" // 补全尾部斜杠
|
||||||
|
}
|
||||||
|
loger.Info(`使用自定义接口: %v`, env.Config.Custom.Wy_Api_Address)
|
||||||
|
Url = nmCustom
|
||||||
|
default:
|
||||||
|
loger.Fatal(`未定义的调用方式,请检查配置 [Custom].Wy_Api_Type`)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
loger.Fatal(`未定义的接口模式,请检查配置 [Custom].Wy_Mode`)
|
||||||
|
}
|
||||||
|
loger.Free()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtin(songMid, quality string) (ourl, msg string) {
|
||||||
loger := env.Loger.NewGroup(`Wy`)
|
loger := env.Loger.NewGroup(`Wy`)
|
||||||
rquality, ok := brMap[quality]
|
defer loger.Free()
|
||||||
|
rquality, ok := qualityMap[quality]
|
||||||
if !ok {
|
if !ok {
|
||||||
msg = sources.E_QNotSupport
|
msg = sources.E_QNotSupport
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cookies := cookie.Parse(env.Config.Custom.Wy_Cookie)
|
resp := wy_pool.Get().(*wm.PlayInfo)
|
||||||
answer, err := SongUrl(song_url_query{
|
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{
|
||||||
Cookie: cookie.ToMap(cookies),
|
Cookie: cookie.ToMap(cookies),
|
||||||
Ids: songMid,
|
Ids: songMid,
|
||||||
Br: rquality,
|
// Br: rquality,
|
||||||
|
Level: rquality,
|
||||||
})
|
})
|
||||||
var body builtin.WyApi_Song
|
body := wy_pool.Get().(*wm.PlayInfo)
|
||||||
|
defer wy_pool.Put(body)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = ztool.Val_MapToStruct(answer.Body, &body)
|
err = ztool.Val_MapToStruct(answer.Body, &body)
|
||||||
}
|
}
|
||||||
@ -43,52 +135,48 @@ func Url(songMid, quality string) (ourl, msg string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
data := body.Data[0]
|
data := body.Data[0]
|
||||||
br := strconv.Itoa(data.Br) // 注:由于flac返回br值不固定,暂无法进行比较
|
if data.Code != 200 {
|
||||||
if br != rquality && !ztool.Chk_IsMatch(br, sources.Q_flac, sources.Q_fl24) {
|
msg = `触发风控或专辑单独收费: ` + strconv.Itoa(data.Code)
|
||||||
msg = sources.E_QNotMatch
|
|
||||||
return
|
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)
|
ourl = utils.DelQuery(data.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func PyUrl(songMid, quality string) (ourl, msg string) {
|
func nmCustom(songMid, quality string) (ourl, msg string) {
|
||||||
loger := env.Loger.NewGroup(`Wy`)
|
loger := env.Loger.NewGroup(`Wy`)
|
||||||
|
defer loger.Free()
|
||||||
rquality, ok := qualityMap[quality]
|
rquality, ok := qualityMap[quality]
|
||||||
if !ok {
|
if !ok {
|
||||||
msg = sources.E_QNotSupport
|
msg = sources.E_QNotSupport
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
path := `/api/song/enhance/player/url/v1`
|
body := wy_pool.Get().(*wm.PlayInfo)
|
||||||
requestUrl := `https://interface.music.163.com/eapi/song/enhance/player/url/v1`
|
defer wy_pool.Put(body)
|
||||||
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(
|
err := ztool.Net_Request(
|
||||||
http.MethodPost, requestUrl,
|
http.MethodGet,
|
||||||
strings.NewReader(form.Encode()), //bytes.NewReader(form),
|
ztool.Str_FastConcat(
|
||||||
[]ztool.Net_ReqHandlerFunc{ztool.Net_ReqAddHeader(map[string]string{
|
env.Config.Custom.Wy_Api_Address, `song/url/v1`, `?id=`, songMid, `&level=`, rquality,
|
||||||
`Cookie`: env.Config.Custom.Wy_Cookie,
|
// `×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_ResHandlerFunc{ztool.Net_ResToStruct(&body)},
|
||||||
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 {
|
if err != nil {
|
||||||
loger.Error(`Request: %s`, err)
|
loger.Error(`SongUrl: %s`, err)
|
||||||
msg = sources.ErrHttpReq
|
msg = sources.ErrHttpReq
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -98,10 +186,16 @@ func PyUrl(songMid, quality string) (ourl, msg string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
data := body.Data[0]
|
data := body.Data[0]
|
||||||
if data.Level != rquality {
|
if data.Code != 200 {
|
||||||
msg = sources.E_QNotMatch
|
msg = `触发风控或专辑单独收费: ` + strconv.Itoa(data.Code)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if data.Level != rquality {
|
||||||
|
msg = ztool.Str_FastConcat(`实际音质不匹配: `, rquality, ` <= `, data.Level)
|
||||||
|
if !env.Config.Source.ForceFallback {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
ourl = utils.DelQuery(data.URL)
|
ourl = utils.DelQuery(data.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
83
src/sources/custom/wy/refresh.go
Normal file
83
src/sources/custom/wy/refresh.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package wy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"lx-source/src/env"
|
||||||
|
wy "lx-source/src/sources/custom/wy/modules"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
// "time"
|
||||||
|
|
||||||
|
"github.com/ZxwyWebSite/ztool/logs"
|
||||||
|
"github.com/ZxwyWebSite/ztool/x/cookie"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
刷新登录模块 (来自 NeteaseCloudMusicApi)
|
||||||
|
逻辑:
|
||||||
|
检测返回结果中是否含有"MUSIC_U":
|
||||||
|
如果有则为正常刷新,延时30天
|
||||||
|
否则延时1天
|
||||||
|
注:
|
||||||
|
原代码未提供详细描述,无法确定有效结果判断条件,暂时先这么写
|
||||||
|
2024-02-15:
|
||||||
|
MUSIC_U 改变 则 6天 后 继续执行
|
||||||
|
MUSIC_U 不变 则 1天 后 继续执行
|
||||||
|
原理:
|
||||||
|
听说隔壁某解析群的账号经常使用,Token快一年了也没过期,
|
||||||
|
所以模拟正常使用,每天调用一次刷新接口,
|
||||||
|
证明这个Token还在使用,类似于给它"续期",
|
||||||
|
就像SPlayer客户端一样,Cookie变了就合并,
|
||||||
|
(这是我随便猜的,未经测试仅供参考)
|
||||||
|
*/
|
||||||
|
|
||||||
|
func refresh(loger *logs.Logger, now int64) error {
|
||||||
|
// 前置检测
|
||||||
|
// now := time.Now().Unix() //(执行时间已改为从参数获取)
|
||||||
|
if now < env.Config.Custom.Wy_Refresh_Interval {
|
||||||
|
loger.Debug(`Key未过期,跳过...`)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// 刷新逻辑
|
||||||
|
cookies := cookie.ToMap(cookie.Parse(env.Config.Custom.Wy_Api_Cookie))
|
||||||
|
res, err := wy.LoginRefresh(wy.ReqQuery{
|
||||||
|
Cookie: cookies,
|
||||||
|
})
|
||||||
|
loger.Debug(`Resp: %+v`, res)
|
||||||
|
if err == nil {
|
||||||
|
if out, ok := res.Body[`cookie`].(string); ok {
|
||||||
|
loger.Info(`获取数据成功`)
|
||||||
|
cmap := cookie.ToMap(cookie.Parse(out))
|
||||||
|
// inline call to maps.Copy
|
||||||
|
for k, v := range cmap {
|
||||||
|
cookies[k] = v
|
||||||
|
}
|
||||||
|
env.Config.Custom.Wy_Api_Cookie = cookie.Marshal(cookies)
|
||||||
|
loger.Debug(`Cookie: %#v`, cookies)
|
||||||
|
// if _, ok := cmap[`MUSIC_U`]; ok {
|
||||||
|
// // MUSIC_U 改变 则 6天 后 继续执行
|
||||||
|
// env.Config.Custom.Wy_Refresh_Interval = now + 518400 //2147483647 - 86000
|
||||||
|
// loger.Debug(`MUSIC_U 改变, 6天 后 继续执行`)
|
||||||
|
// } else {
|
||||||
|
// // MUSIC_U 不变 则 1天 后 继续执行
|
||||||
|
// env.Config.Custom.Wy_Refresh_Interval = now + 86000
|
||||||
|
// loger.Debug(`MUSIC_U 不变, 1天 后 继续执行`) //`未发现有效结果,将在下次检测时再次尝试`
|
||||||
|
// }
|
||||||
|
tnow := time.Now()
|
||||||
|
env.Config.Custom.Wy_Refresh_Interval = time.Date(tnow.Year(), tnow.Month(), tnow.Day()+1, 0, 0, 0, 0, tnow.Location()).Unix()
|
||||||
|
// env.Config.Custom.Wy_Refresh_Interval = now + 86000
|
||||||
|
err = env.Cfg.Save(``)
|
||||||
|
if err == nil {
|
||||||
|
loger.Info(`配置更新成功`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
env.Inits.Add(func() {
|
||||||
|
if env.Config.Custom.Wy_Refresh_Enable && env.Config.Custom.Wy_Api_Cookie != `` {
|
||||||
|
env.Tasker.Add(`wy_refresh`, refresh, 86000, true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -1,42 +0,0 @@
|
|||||||
package wy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/ZxwyWebSite/ztool"
|
|
||||||
)
|
|
||||||
|
|
||||||
type song_url_query struct {
|
|
||||||
Cookie map[string]string
|
|
||||||
Ids string
|
|
||||||
Br string
|
|
||||||
RealIP string
|
|
||||||
}
|
|
||||||
|
|
||||||
func SongUrl(query song_url_query) (*reqAnswer, error) {
|
|
||||||
query.Cookie[`os`] = `pc`
|
|
||||||
if query.Br == `` {
|
|
||||||
query.Br = `999000`
|
|
||||||
}
|
|
||||||
ids := strings.Split(query.Ids, `,`)
|
|
||||||
// idj, err := json.Marshal(ids)
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
data := map[string]any{
|
|
||||||
`ids`: ztool.Str_FastConcat(`["`, strings.Join(ids, `","`), `"]`), //bytesconv.BytesToString(idj), //`["1998644237"]`,
|
|
||||||
`br`: query.Br, //ztool.Str_Select(query.Br, `999000`),
|
|
||||||
}
|
|
||||||
return createRequest(
|
|
||||||
http.MethodPost,
|
|
||||||
`https://interface3.music.163.com/eapi/song/enhance/player/url`,
|
|
||||||
data,
|
|
||||||
reqOptions{
|
|
||||||
Crypto: `eapi`,
|
|
||||||
Cookie: query.Cookie,
|
|
||||||
RealIP: query.RealIP,
|
|
||||||
Url: `/api/song/enhance/player/url`,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
@ -3,20 +3,21 @@ package wy
|
|||||||
import "lx-source/src/sources"
|
import "lx-source/src/sources"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
brMap = map[string]string{
|
// brMap = map[string]string{
|
||||||
sources.Q_128k: `128000`,
|
// sources.Q_128k: `128000`,
|
||||||
sources.Q_320k: `320000`,
|
// sources.Q_320k: `320000`,
|
||||||
sources.Q_flac: `1000000`, //`743625`,`915752`
|
// sources.Q_flac: `1000000`, //`743625`,`915752`
|
||||||
sources.Q_fl24: `2000000`, //`1453955`,`1683323`
|
// sources.Q_fl24: `2000000`, //`1453955`,`1683323`
|
||||||
}
|
// }
|
||||||
qualityMap = map[string]string{
|
qualityMap = map[string]string{
|
||||||
sources.Q_128k: `standard`,
|
sources.Q_128k: `standard`,
|
||||||
sources.Q_320k: `exhigh`,
|
sources.Q_320k: `exhigh`,
|
||||||
sources.Q_flac: `lossless`,
|
sources.Q_flac: `lossless`,
|
||||||
sources.Q_fl24: `hires`,
|
sources.Q_fl24: `hires`,
|
||||||
`dolby`: `jyeffect`,
|
|
||||||
`sky`: `jysky`,
|
sources.Q_dolby: `jyeffect`,
|
||||||
`master`: `jymaster`,
|
sources.Q_sky: sources.Q_sky,
|
||||||
|
sources.Q_master: `jymaster`,
|
||||||
}
|
}
|
||||||
// 优化:返回音质与查询音质相同,完全可以直接比较,不用多一步Reverse
|
// 优化:返回音质与查询音质相同,完全可以直接比较,不用多一步Reverse
|
||||||
// qualityMapReverse = map[string]string{
|
// qualityMapReverse = map[string]string{
|
||||||
|
38
src/sources/example/data.go
Normal file
38
src/sources/example/data.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package example
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
|
||||||
|
"github.com/ZxwyWebSite/ztool"
|
||||||
|
"github.com/ZxwyWebSite/ztool/zcypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Api_wy string
|
||||||
|
Api_mg string
|
||||||
|
|
||||||
|
Vef_wy string
|
||||||
|
|
||||||
|
Header_wy map[string]string
|
||||||
|
Header_mg map[string]string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// InitBuiltInData
|
||||||
|
var initdata = struct {
|
||||||
|
Api_Wy *string
|
||||||
|
Api_Mg *string
|
||||||
|
Vef_Wy *string
|
||||||
|
Header_Wy *map[string]string
|
||||||
|
Header_Mg *map[string]string
|
||||||
|
}{
|
||||||
|
Api_Wy: &Api_wy,
|
||||||
|
Api_Mg: &Api_mg,
|
||||||
|
Vef_Wy: &Vef_wy,
|
||||||
|
Header_Wy: &Header_wy,
|
||||||
|
Header_Mg: &Header_mg,
|
||||||
|
}
|
||||||
|
data := []byte{0x53, 0x6e, 0x38, 0x44, 0x41, 0x51, 0x4c, 0x2f, 0x67, 0x41, 0x41, 0x42, 0x42, 0x51, 0x45, 0x47, 0x51, 0x58, 0x42, 0x70, 0x58, 0x31, 0x64, 0x35, 0x41, 0x51, 0x77, 0x41, 0x41, 0x51, 0x5a, 0x42, 0x63, 0x47, 0x6c, 0x66, 0x54, 0x57, 0x63, 0x42, 0x44, 0x41, 0x41, 0x42, 0x42, 0x6c, 0x5a, 0x6c, 0x5a, 0x6c, 0x39, 0x58, 0x65, 0x51, 0x45, 0x4d, 0x41, 0x41, 0x45, 0x4a, 0x53, 0x47, 0x56, 0x68, 0x5a, 0x47, 0x56, 0x79, 0x58, 0x31, 0x64, 0x35, 0x41, 0x66, 0x2b, 0x43, 0x41, 0x41, 0x45, 0x4a, 0x53, 0x47, 0x56, 0x68, 0x5a, 0x47, 0x56, 0x79, 0x58, 0x30, 0x31, 0x6e, 0x41, 0x66, 0x2b, 0x43, 0x41, 0x41, 0x41, 0x41, 0x49, 0x66, 0x2b, 0x42, 0x42, 0x41, 0x45, 0x42, 0x45, 0x57, 0x31, 0x68, 0x63, 0x46, 0x74, 0x7a, 0x64, 0x48, 0x4a, 0x70, 0x62, 0x6d, 0x64, 0x64, 0x63, 0x33, 0x52, 0x79, 0x61, 0x57, 0x35, 0x6e, 0x41, 0x66, 0x2b, 0x43, 0x41, 0x41, 0x45, 0x4d, 0x41, 0x51, 0x77, 0x41, 0x41, 0x50, 0x34, 0x42, 0x72, 0x76, 0x2b, 0x41, 0x41, 0x53, 0x52, 0x6a, 0x63, 0x32, 0x30, 0x75, 0x63, 0x32, 0x46, 0x35, 0x63, 0x58, 0x6f, 0x75, 0x59, 0x32, 0x39, 0x74, 0x4c, 0x32, 0x46, 0x77, 0x61, 0x53, 0x38, 0x2f, 0x64, 0x48, 0x6c, 0x77, 0x5a, 0x54, 0x31, 0x68, 0x63, 0x47, 0x6c, 0x54, 0x62, 0x32, 0x35, 0x6e, 0x56, 0x58, 0x4a, 0x73, 0x56, 0x6a, 0x45, 0x42, 0x4e, 0x6d, 0x30, 0x75, 0x62, 0x58, 0x56, 0x7a, 0x61, 0x57, 0x4d, 0x75, 0x62, 0x57, 0x6c, 0x6e, 0x64, 0x53, 0x35, 0x6a, 0x62, 0x69, 0x39, 0x74, 0x61, 0x57, 0x64, 0x31, 0x62, 0x58, 0x56, 0x7a, 0x61, 0x57, 0x4d, 0x76, 0x61, 0x44, 0x55, 0x76, 0x63, 0x47, 0x78, 0x68, 0x65, 0x53, 0x39, 0x68, 0x64, 0x58, 0x52, 0x6f, 0x4c, 0x32, 0x64, 0x6c, 0x64, 0x46, 0x4e, 0x76, 0x62, 0x6d, 0x64, 0x51, 0x62, 0x47, 0x46, 0x35, 0x53, 0x57, 0x35, 0x6d, 0x62, 0x77, 0x45, 0x6c, 0x59, 0x33, 0x4e, 0x74, 0x4c, 0x6e, 0x4e, 0x68, 0x65, 0x58, 0x46, 0x36, 0x4c, 0x6d, 0x4e, 0x76, 0x62, 0x53, 0x39, 0x68, 0x63, 0x47, 0x6b, 0x76, 0x50, 0x33, 0x52, 0x35, 0x63, 0x47, 0x55, 0x39, 0x59, 0x33, 0x4e, 0x74, 0x51, 0x32, 0x68, 0x6c, 0x59, 0x57, 0x74, 0x4e, 0x64, 0x58, 0x4e, 0x70, 0x59, 0x77, 0x45, 0x43, 0x43, 0x6c, 0x56, 0x7a, 0x5a, 0x58, 0x49, 0x74, 0x51, 0x57, 0x64, 0x6c, 0x62, 0x6e, 0x52, 0x65, 0x54, 0x57, 0x39, 0x36, 0x61, 0x57, 0x78, 0x73, 0x59, 0x53, 0x38, 0x31, 0x4c, 0x6a, 0x41, 0x67, 0x4b, 0x46, 0x64, 0x70, 0x62, 0x6d, 0x52, 0x76, 0x64, 0x33, 0x4d, 0x67, 0x54, 0x6c, 0x51, 0x67, 0x4e, 0x69, 0x34, 0x78, 0x4f, 0x79, 0x42, 0x58, 0x54, 0x31, 0x63, 0x32, 0x4e, 0x43, 0x6b, 0x67, 0x51, 0x58, 0x42, 0x77, 0x62, 0x47, 0x56, 0x58, 0x5a, 0x57, 0x4a, 0x4c, 0x61, 0x58, 0x51, 0x76, 0x4e, 0x54, 0x4d, 0x33, 0x4c, 0x6a, 0x4d, 0x32, 0x49, 0x43, 0x68, 0x4c, 0x53, 0x46, 0x52, 0x4e, 0x54, 0x43, 0x77, 0x67, 0x62, 0x47, 0x6c, 0x72, 0x5a, 0x53, 0x42, 0x48, 0x5a, 0x57, 0x4e, 0x72, 0x62, 0x79, 0x6b, 0x67, 0x51, 0x32, 0x68, 0x79, 0x62, 0x32, 0x31, 0x6c, 0x4c, 0x7a, 0x55, 0x77, 0x4c, 0x6a, 0x41, 0x75, 0x4d, 0x6a, 0x59, 0x32, 0x4d, 0x53, 0x34, 0x34, 0x4e, 0x78, 0x42, 0x59, 0x4c, 0x56, 0x4a, 0x6c, 0x63, 0x58, 0x56, 0x6c, 0x63, 0x33, 0x52, 0x6c, 0x5a, 0x43, 0x31, 0x58, 0x61, 0x58, 0x52, 0x6f, 0x44, 0x6c, 0x68, 0x4e, 0x54, 0x45, 0x68, 0x30, 0x64, 0x48, 0x42, 0x53, 0x5a, 0x58, 0x46, 0x31, 0x5a, 0x58, 0x4e, 0x30, 0x41, 0x51, 0x51, 0x48, 0x55, 0x6d, 0x56, 0x6d, 0x5a, 0x58, 0x4a, 0x6c, 0x63, 0x68, 0x74, 0x6f, 0x64, 0x48, 0x52, 0x77, 0x63, 0x7a, 0x6f, 0x76, 0x4c, 0x32, 0x30, 0x75, 0x62, 0x58, 0x56, 0x7a, 0x61, 0x57, 0x4d, 0x75, 0x62, 0x57, 0x6c, 0x6e, 0x64, 0x53, 0x35, 0x6a, 0x62, 0x69, 0x39, 0x32, 0x4e, 0x43, 0x38, 0x43, 0x51, 0x6e, 0x6b, 0x67, 0x4d, 0x44, 0x52, 0x6d, 0x4f, 0x44, 0x45, 0x30, 0x4e, 0x6a, 0x46, 0x68, 0x4f, 0x54, 0x68, 0x6a, 0x4e, 0x32, 0x46, 0x6d, 0x4e, 0x54, 0x55, 0x33, 0x5a, 0x6d, 0x56, 0x68, 0x4d, 0x32, 0x4e, 0x6d, 0x4d, 0x6a, 0x68, 0x6a, 0x4e, 0x47, 0x56, 0x68, 0x4d, 0x54, 0x55, 0x48, 0x59, 0x32, 0x68, 0x68, 0x62, 0x6d, 0x35, 0x6c, 0x62, 0x41, 0x63, 0x77, 0x4d, 0x54, 0x51, 0x77, 0x4d, 0x44, 0x42, 0x45, 0x42, 0x6b, 0x4e, 0x76, 0x62, 0x32, 0x74, 0x70, 0x5a, 0x54, 0x68, 0x54, 0x52, 0x56, 0x4e, 0x54, 0x53, 0x55, 0x39, 0x4f, 0x50, 0x56, 0x70, 0x55, 0x53, 0x58, 0x64, 0x50, 0x52, 0x47, 0x74, 0x35, 0x54, 0x55, 0x52, 0x52, 0x64, 0x45, 0x39, 0x55, 0x52, 0x54, 0x46, 0x4f, 0x55, 0x7a, 0x41, 0x77, 0x54, 0x55, 0x52, 0x6f, 0x62, 0x45, 0x78, 0x55, 0x61, 0x47, 0x68, 0x4e, 0x56, 0x30, 0x56, 0x30, 0x54, 0x57, 0x70, 0x52, 0x4d, 0x45, 0x34, 0x79, 0x57, 0x54, 0x4a, 0x4e, 0x65, 0x6d, 0x73, 0x79, 0x54, 0x31, 0x52, 0x42, 0x65, 0x67, 0x41, 0x3d}
|
||||||
|
dec, _ := zcypt.Base64Decode(base64.StdEncoding, data)
|
||||||
|
ztool.Val_GobDecode(dec, &initdata)
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
package sources
|
package sources
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"lx-source/src/caches"
|
// "lx-source/src/caches"
|
||||||
)
|
// )
|
||||||
|
|
||||||
// var Loger = env.Loger.NewGroup(`Sources`) // JieXiApis
|
// var Loger = env.Loger.NewGroup(`Sources`) // JieXiApis
|
||||||
|
|
||||||
@ -13,6 +13,14 @@ const (
|
|||||||
Q_320k = `320k`
|
Q_320k = `320k`
|
||||||
Q_flac = `flac`
|
Q_flac = `flac`
|
||||||
Q_fl24 = `flac24bit`
|
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_wy = `wy` // 小芸
|
||||||
S_mg = `mg` // 小蜜
|
S_mg = `mg` // 小蜜
|
||||||
@ -29,6 +37,26 @@ const (
|
|||||||
ErrHttpReq = `无法连接解析接口`
|
ErrHttpReq = `无法连接解析接口`
|
||||||
ErrNoLink = `无法获取试听链接`
|
ErrNoLink = `无法获取试听链接`
|
||||||
ErrDisable = `该音乐源已被禁用`
|
ErrDisable = `该音乐源已被禁用`
|
||||||
|
// 缓存时间
|
||||||
|
C_tx = 80400 // 不知道tx为什么要取一个这么不对劲的数字当过期时长
|
||||||
|
C_kg = 24 * 60 * 60 // 24 hours
|
||||||
|
C_kw = 60 * 60 // 60 minutes
|
||||||
|
C_wy = 20 * 60 // 20 minutes
|
||||||
|
C_mg = C_kg // 永久直链但最多缓存1天
|
||||||
|
C_lx = 12 * 60 * 60 // 本地默认缓存12小时
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
I_wy = iota
|
||||||
|
I_mg
|
||||||
|
I_kw
|
||||||
|
I_kg
|
||||||
|
I_tx
|
||||||
|
I_lx
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
S_al = []string{S_wy, S_mg, S_kw, S_kg, S_tx, S_lx} // 全部平台
|
||||||
)
|
)
|
||||||
|
|
||||||
// 源查询接口
|
// 源查询接口
|
||||||
@ -43,24 +71,58 @@ const (
|
|||||||
验证部分放到GetLink里
|
验证部分放到GetLink里
|
||||||
|
|
||||||
*/
|
*/
|
||||||
type Source interface {
|
// type Source interface {
|
||||||
Verify(*caches.Query) (string, bool) // 验证是否可用 <查询参数> <rquery,ok>
|
// Verify(*caches.Query) (string, bool) // 验证是否可用 <查询参数> <rquery,ok>
|
||||||
GetLink(*caches.Query) (string, string) // 查询获取链接 <查询参数> <链接,信息>
|
// GetLink(*caches.Query) (string, string) // 查询获取链接 <查询参数> <链接,信息>
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 默认空接口
|
// 默认空接口
|
||||||
type NullSource struct{}
|
// type NullSource struct{}
|
||||||
|
|
||||||
func (*NullSource) Verify(*caches.Query) (string, bool) { return ``, false }
|
// func (*NullSource) Verify(*caches.Query) (string, bool) { return ``, false }
|
||||||
func (*NullSource) GetLink(*caches.Query) (string, string) { return ``, `NullSource` }
|
// func (*NullSource) GetLink(*caches.Query) (string, string) { return ``, `NullSource` }
|
||||||
|
|
||||||
var UseSource Source = &NullSource{} // = &builtin.Source{}
|
// var UseSource Source = &NullSource{} // = &builtin.Source{}
|
||||||
|
|
||||||
// 统一错误
|
// 统一错误
|
||||||
// type Error struct {
|
// type (
|
||||||
// msg string
|
// ErrDef struct {
|
||||||
|
// Typ string
|
||||||
|
// Msg string
|
||||||
|
// }
|
||||||
|
// ErrQul struct {
|
||||||
|
// Need string
|
||||||
|
// Real string
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
|
||||||
|
// func (e *ErrDef) Error() string {
|
||||||
|
// return ztool.Str_FastConcat(e.Typ, `: `, e.Msg)
|
||||||
|
// }
|
||||||
|
// func (e *ErrQul) Error() string {
|
||||||
|
// return ztool.Str_FastConcat(`实际音质不匹配: `, e.Need, ` <= `, e.Real)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// func (e *Error) Error() string {
|
// 验证失败(Verify Failed)
|
||||||
// return ztool.Str_FastConcat(e.msg)
|
// func ErrVerify(msg string) error {
|
||||||
|
// return &ErrDef{
|
||||||
|
// Typ: Err_Verify,
|
||||||
|
// Msg: msg,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 实际音质不匹配
|
||||||
|
// func ErrQuality(need, real string) error {
|
||||||
|
// return &ErrQul{
|
||||||
|
// Need: need,
|
||||||
|
// Real: real,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 无返回数据(No Data)
|
||||||
|
// func ErrNoData() error {
|
||||||
|
// return &ErrDef{
|
||||||
|
// Typ: `No Data`,
|
||||||
|
// Msg: ``,
|
||||||
|
// }
|
||||||
// }
|
// }
|
||||||
|
48
src/sources/types.go
Normal file
48
src/sources/types.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
//go:build extapp
|
||||||
|
|
||||||
|
package sources
|
||||||
|
|
||||||
|
// MusicFree 数据结构
|
||||||
|
type (
|
||||||
|
// 其他
|
||||||
|
IExtra map[string]interface{}
|
||||||
|
// 音乐
|
||||||
|
IMusicItem struct {
|
||||||
|
Artist string `json:"artist"` // 作者
|
||||||
|
Title string `json:"title"` // 歌曲标题
|
||||||
|
Duration int `json:"duration,omitempty"` // 时长(s)
|
||||||
|
Album string `json:"album,omitempty"` // 专辑名
|
||||||
|
Artwork string `json:"artwork,omitempty"` // 专辑封面图
|
||||||
|
Url string `json:"url,omitempty"` // 默认音源
|
||||||
|
Lrc string `json:"lrc,omitempty"` // 歌词URL
|
||||||
|
RawLrc string `json:"rawLrc,omitempty"` // 歌词文本(lrc格式 带时间戳)
|
||||||
|
Other IExtra `json:"extra,omitempty"` // 其他
|
||||||
|
}
|
||||||
|
// 歌单
|
||||||
|
IMusicSheetItem struct {
|
||||||
|
Artwork string `json:"artwork,omitempty"` // 封面图
|
||||||
|
Title string `json:"title"` // 标题
|
||||||
|
Description string `json:"description,omitempty"` // 描述
|
||||||
|
WorksNum int `json:"worksNum,omitempty"` // 作品总数
|
||||||
|
PlayCount int `json:"playCount,omitempty"` // 播放次数
|
||||||
|
MusicList []IMusicItem `json:"musicList,omitempty"` // 播放列表
|
||||||
|
CreateAt int64 `json:"createAt,omitempty"` // 歌单创建日期
|
||||||
|
Artist string `json:"artist,omitempty"` // 歌单作者
|
||||||
|
Other IExtra `json:"extra,omitempty"` // 其他
|
||||||
|
}
|
||||||
|
// 专辑
|
||||||
|
IAlbumItem IMusicSheetItem
|
||||||
|
// 作者
|
||||||
|
IArtistItem struct {
|
||||||
|
Platform string `json:"platform,omitempty"` // 插件名
|
||||||
|
ID interface{} `json:"id"` // 唯一id
|
||||||
|
Name string `json:"name"` // 姓名
|
||||||
|
Fans int `json:"fans,omitempty"` // 粉丝数
|
||||||
|
Description string `json:"description,omitempty"` // 简介
|
||||||
|
Avatar string `json:"avatar,omitempty"` // 头像
|
||||||
|
WorksNum int `json:"worksNum,omitempty"` // 作品数目
|
||||||
|
MusicList []IMusicItem `json:"musicList,omitempty"` // 作者的音乐列表
|
||||||
|
AlbumList []IAlbumItem `json:"albumList,omitempty"` // 作者的专辑列表
|
||||||
|
Other IExtra `json:"extra,omitempty"` // 其他
|
||||||
|
}
|
||||||
|
)
|
202
update.md
202
update.md
@ -1,5 +1,207 @@
|
|||||||
## Lx-Source/更新日志
|
## 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)
|
#### \# 2024-01-18 v1.0.2-b12-d1 (dev)
|
||||||
+ 对部分功能实现方式进行优化,去除qualityMapReverse依赖
|
+ 对部分功能实现方式进行优化,去除qualityMapReverse依赖
|
||||||
+ 由于wy修改api验证方式,python版逻辑已不可用,现参考NeteaseCloudMusicApi项目进行修改
|
+ 由于wy修改api验证方式,python版逻辑已不可用,现参考NeteaseCloudMusicApi项目进行修改
|
||||||
|
Loading…
x
Reference in New Issue
Block a user