完成网络音乐、工具页面与安装器体验升级

This commit is contained in:
2026-08-18 13:54:33 +08:00
parent 337390f53e
commit 149c28082d
1082 changed files with 26442 additions and 162838 deletions
+1
View File
@@ -0,0 +1 @@
06560e3e053bda1ab830750db6f645bab703f824
+339
View File
@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
+316
View File
@@ -0,0 +1,316 @@
<br />
<p align="center">
<img src="https://github.com/iAJue/MoeKoeMusic/raw/main/images/hero.png" alt="hero" width="250" height="250">
<h2 align="center" style="font-weight: 600">MoeKoe Music</h2>
<p align="center">
<a href="https://github.com/MoeKoeMusic/MoeKoeMusic/releases/latest"><img src="https://img.shields.io/github/v/release/MoeKoeMusic/MoeKoeMusic?style=flat-square" /></a>
<a href="https://github.com/MoeKoeMusic/MoeKoeMusic/stargazers"><img src="https://img.shields.io/github/stars/MoeKoeMusic/MoeKoeMusic?style=flat-square" /></a>
<a href="https://github.com/MoeKoeMusic/MoeKoeMusic/releases"><img src="https://img.shields.io/github/downloads/MoeKoeMusic/MoeKoeMusic/total?style=flat-square" /></a>
<a href="https://github.com/MoeKoeMusic/MoeKoeMusic/blob/main/LICENSE"><img src="https://img.shields.io/github/license/MoeKoeMusic/MoeKoeMusic?style=flat-square" /></a>
<a href="https://github.com/iAJue"><img src="https://img.shields.io/badge/%F0%9F%8E%89_Create_by_iAJue-with_Love_%E2%9D%A4-pink?style=flat-square" /></a>
</p>
<p align="center">
一款开源简洁高颜值的酷狗第三方客户端
<br />
<a href="https://github.com/iAJue/MoeKoeMusic/" target="blank"><strong>🌎 GitHub仓库</strong></a>&nbsp;&nbsp;|&nbsp;&nbsp;
<a href="https://github.com/iAJue/MoeKoeMusic/releases" target="blank"><strong>📦️ 下载安装包</strong></a>&nbsp;&nbsp;|&nbsp;&nbsp;
<a href="https://MoeJue.cn" target="blank"><strong>💬 访问博客</strong></a>&nbsp;&nbsp;|&nbsp;&nbsp;
<a href="https://Music.MoeKoe.cn" target="blank"><strong>🏠 项目主页</strong></a>
</p>
<p align="center">
<a href="https://github.com/iAJue/MoeKoeMusic/README.md" target="blank"><strong>🇨🇳 简体中文</strong></a>&nbsp;&nbsp;|&nbsp;&nbsp;
<a href="https://github.com/iAJue/MoeKoeMusic/blob/main/docs/README_tw.md" target="blank"><strong>🇨🇳 繁体中文</strong></a>&nbsp;&nbsp;|&nbsp;&nbsp;
<a href="https://github.com/iAJue/MoeKoeMusic/blob/main/docs/README_ja.md" target="blank"><strong>🇯🇵 日本語</strong></a>&nbsp;&nbsp;|&nbsp;&nbsp;
<a href="https://github.com/iAJue/MoeKoeMusic/blob/main/docs/README_en.md" target="blank"><strong>🇺🇸 English</strong></a>&nbsp;&nbsp;|&nbsp;&nbsp;
<a href="https://github.com/iAJue/MoeKoeMusic/blob/main/docs/README_ko.md" target="blank"><strong>🇰🇷 한국어</strong></a>&nbsp;&nbsp;|&nbsp;&nbsp;
<a href="https://github.com/iAJue/MoeKoeMusic/blob/main/docs/README_ru.md" target="blank"><strong>🇷🇺 Русский</strong></a>
<br />
<br />
</p>
</p>
![images](https://github.com/iAJue/MoeKoeMusic/raw/main/images/1.png)
## ❤️ 前言
早在10年前后的样子,那会在用网页版QQ的时候我就已经开始使用酷狗音乐了(也是十来年的老粉了),所以这些年收藏的歌曲全部都在上面.后来我也尝试开始使用网易云或QQ音乐,也尝试把酷狗的歌单导入进去,但是效果都不尽人意.我听的大多是日漫OP,好多歌曲都没办法找到.
兜兜转转最后还是回到酷狗,但是在Mac端的酷狗,时常可能会出现不能播放的情况,虽说界面没什么功能,但也挺好的.在网友的安利下,我现在一直是在酷狗的[概念版](https://t1.kugou.com/d2tBza3CSV2)上听歌,并且是市面上为数不多能免费听VIP歌曲的音乐播放软件了,力推.
我在我的个人介绍页面说我特别喜欢听歌,尤其是日漫OP.怎么证明呢?(之前我网页版歌单也年久失修了)那就自己开发一个音乐播放器.
## ✨ 特性
- ✅ 使用 Vue.js 全家桶开发
- 🔴 酷狗账号登录(扫码/手机/账号登录)
- 📃 支持歌词显示
- 📻 每日推荐歌曲
- 🔕 无任何社交功能
- 🔗 官方服务器直连
- ✔️ 每日自动领取VIP,登录就是VIP
- 🎨 主题色切换
- 👋 启动问候语
- ⚙️ 多平台支持
- 🧩 超级插件系统
- 🧭 布局个性化设置
- 🥚 隐藏功能与彩蛋系统
- 🚀 新手引导系统
- ✨ 更多特性由你来探索
## 📢 Todo List
- [x] 📺 支持 MV 播放
- [x] 🌚 Light/Dark Mode 自动切换
- [x] 👆 支持 Touch Bar
- [x] 🖥️ 支持 PWA
- [x] ⌨️ 全局快捷键
- [x] 🤟 多语言支持
- [x] 📻 桌面歌词
- [x] ⚙️ 系统架构优化
- [x] 🎶 歌曲、歌单/收藏、取消
[查看更新日志](http://music.moekoe.cn/changelog.html)
## 📦️ 安装
### 1. 客户端安装
访问本项目的 [Releases](https://github.com/iAJue/MoeKoeMusic/releases) 页面下载安装包。
### 2. WEB端安装(docker
* 注意:部署后请开放服务器对应端口才可使用,或者使用反向代理实现域名访问。
1. 方式一:快速启动(推荐)
```
git clone https://github.com/iAJue/MoeKoeMusic.git
cd MoeKoeMusic
git submodule update --init --recursive
docker compose up -d &
```
2. ~~方式二:使用docker-compose一键安装 (镜像暂未上传官方)~~
```
docker run -d --name MoeKoeMusic -p 8080:8080 -p 6521:6521 -e PORT=6521 -e platform=lite iajue/moekoe-music:latest
```
3. 方式三:宝塔容器编排
* 远程镜像,版本可能会落后于官方
```
version: '3.3'
services:
moekoe-music:
# 镜像地址
image: registry.cn-wulanchabu.aliyuncs.com/youngxj/moekoe-music:latest
container_name: moekoe-music # 容器名
restart: unless-stopped # 自动重启
build:
context: .
dockerfile: Dockerfile
environment:
- PORT=6521
- platform=lite
ports: # 端口映射
- "8080:8080" # 前端服务
- "6521:6521" # 接口服务
```
复制内容上面的内容,粘贴到宝塔面板的容器编排里面,编排名称为MoeKoeMusic,点击部署即可。
### 3. 一键部署
[![使用 EdgeOne Pages 部署](https://cdnstatic.tencentcs.com/edgeone/pages/deploy.svg)](https://edgeone.ai/pages/new?template=https://github.com/iAJue/moekoemusic&install-command=npm%20install&output-directory=dist&root-directory=.%2F&build-command=npm%20run%20build&env=VITE_APP_API_URL)
需在环境变量(VITE_APP_API_URL)中填写自己的API地址
## ⚙️ 开发
1. 克隆本仓库
```sh
git clone --recurse-submodules https://github.com/iAJue/MoeKoeMusic.git
```
2. 进入目录并安装依赖
```sh
cd MoeKoeMusic
npm run install-all
```
3. 启动开发者模式
```sh
npm run dev
```
4. 打包项目
```sh
npm run build
```
5. 编译项目
- Windows:
```sh
npm run electron:build:win [默认 NSIS 安装包]
```
- Linux:
```sh
npm run electron:build:linux [默认 AppImage 格式]
```
- macOS:
```sh
npm run electron:build:macos [默认 macOS 双架构]
```
更多命令请查看 `package.json` 文件 `scripts`
## 👷‍♂️ 编译客户端
如果在 Release 页面没有找到适合你的设备的安装包的话,你可以根据下面的步骤来打包自己的客户端。
1. 安装 [Node.js](https://nodejs.org/en/),并确保 `Node.js` 版本 >= 18.0.0。
2. 使用 `git clone https://github.com/iAJue/MoeKoeMusic.git` 克隆本仓库到本地。
3. 使用 `npm install` 安装项目依赖。
4. 编译API服务端
- Windows:
```sh
npm run build:api:win
```
- Linux:
```sh
npm run build:api:linux
```
- macOS:
```sh
npm run build:api:macos
```
5. 选择下列的命令来打包适合的你的安装包,打包出来的文件在 `/dist_electron` 目录下。了解更多信息可访问 [electron-builder 文档](https://www.electron.build/cli)
#### 1. 打包 macOS 平台
- 通用的 macOS 包(Intel 和 Apple Silicon 双架构):
```
npm run electron:build -- --mac --universal
```
- 仅 Intel 架构:
```
npm run electron:build -- --mac --x64
```
- 仅 Apple Silicon 架构:
```
npm run electron:build -- --mac --arm64
```
#### 2. 打包 Windows 平台
- 默认 NSIS 安装包(适合大多数 Windows 用户):
```
npm run electron:build -- --win
```
- 为 Windows 创建 EXE 文件和 Squirrel 安装包:
```
npm run electron:build -- --win --ia32 --x64 --arm64 --target squirrel
```
- --ia32 为 32 位 Windows 架构。
- --x64 为 64 位 Windows 架构。
- --arm64 为 ARM Windows 架构(Surface 等设备)。
- 为 Windows 生成便携式的 EXE 文件(免安装):
```
npm run electron:build -- --win --portable
```
#### 3. 打包 Linux 平台
- 默认 AppImage 格式(适用于大多数 Linux 发行版):
```
npm run electron:build -- --linux
```
- snap(适用于 Ubuntu 和支持 snap 的发行版):
```
npm run electron:build -- --linux --target snap
```
- deb(适用于 Debian/Ubuntu 系列):
```
npm run electron:build -- --linux --target deb
```
- rpm(适用于 Red Hat/Fedora 系列):
```
npm run electron:build -- --linux --target rpm
```
- ARM64架构(ARM v8+):
```
npm run build:api:linux-arm64 //编译API
npm run electron:build:linux-arm64 //编译主程序
```
#### 4. 打包所有平台
如果需要同时生成 Windows、macOS 和 Linux 的安装包,可以使用以下命令:
```
npm run electron:build -- -mwl
```
#### 5. 自定义编译设置
您可以根据需要添加其他选项来进一步自定义打包,例如指定 x64 和 arm64 架构,或选择不同的目标格式。
## ⭐ 支持项目
如果您觉得这个项目对您有帮助,欢迎给我们一个 Star!您的支持是我们持续改进的动力。
[![GitHub stars](https://img.shields.io/github/stars/iAJue/MoeKoeMusic.svg?style=social&label=Star)](https://github.com/iAJue/MoeKoeMusic)
## ✅ 反馈
如有任何问题或建议,欢迎提交 issue 或 pull request。
## ⚠️ 免责声明
0. 本程序是酷狗第三方客户端,并非酷狗官方,需要更完善的功能请下载官方客户端体验.
1. 本项目仅供学习使用,请尊重版权,请勿利用此项目从事商业行为及非法用途!
2. 使用本项目的过程中可能会产生版权数据。对于这些版权数据,本项目不拥有它们的所有权。为了避免侵权,使用者务必在 24 小时内清除使用本项目的过程中所产生的版权数据。
3. 由于使用本项目产生的包括由于本协议或由于使用或无法使用本项目而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。
1. 禁止在违反当地法律法规的情况下使用本项目。对于使用者在明知或不知当地法律法规不允许的情况下使用本项目所造成的任何违法违规行为由使用者承担,本项目不承担由此造成的任何直接、间接、特殊、偶然或结果性责任。
2. 音乐平台不易,请尊重版权,支持正版。
3. 本项目仅用于对技术可行性的探索及研究,不接受任何商业(包括但不限于广告等)合作及捐赠。
4. 如果官方音乐平台觉得本项目不妥,可联系本项目更改或移除。
## 📜 开源许可
本项目仅供个人学习研究使用,禁止用于商业及非法用途。
基于 [GNU General Public License v2.0 (GPL-2.0)](https://github.com/iAJue/MoeKoeMusic/blob/main/LICENSE) 许可进行开源。
## 👍 灵感来源
API 源代码来自 [MakcRe/KuGouMusicApi](https://github.com/MakcRe/KuGouMusicApi)
- [Apple Music](https://music.apple.com)
- [YouTube Music](https://music.youtube.com)
- [YesPlayMusic](https://github.com/qier222/YesPlayMusic)
- [酷狗音乐](https://kugou.com/)
## 🖼️ 截图
![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/2.png)
![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/3.png)
![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/4.png)
![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/5.png)
![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/6.png)
![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/7.png)
![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/8.png)
![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/9.png)
![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/10.png)
![image](https://github.com/iAJue/MoeKoeMusic/raw/main/images/11.png)
## 🗓️ Star History
[![Star History Chart](https://api.star-history.com/chart?repos=MoeKoeMusic/MoeKoeMusic&type=date&legend=top-left&sealed_token=unIw8k8CbengL1CW5XVoW-psSsTQHjp7X6Hchrr9WiEh4baeFZXunI95J9V8nnY8XTWrT8UuvRBXScevzXLRlhTID0-_V200iw9TFChclu1-28maCH3NutInvRl6nQ1jDMXpInpefFixeHYDr8BCn9fYqiNtZAoK30Vi46s7QPQQU80mJstgjgeqWjXf)](https://www.star-history.com/#iAJue/MoeKoeMusic&Date)
+1
View File
@@ -0,0 +1 @@
4581660209c9cdd07669e8bd5bcbe136b7d91e02
+20
View File
@@ -0,0 +1,20 @@
# YMhut Box changes
Imported from `MoeKoeMusic/MoeKoeMusic` commit
`4581660209c9cdd07669e8bd5bcbe136b7d91e02`. The `api` directory is the
repository's `MakcRe/KuGouMusicApi` submodule at commit
`06560e3e053bda1ab830750db6f645bab703f824`.
YMhut Box adds an internal Windows sidecar entry point and these local-only
controls:
- loopback-only random-port startup and a versioned JSON readiness message;
- a per-process authentication token and parent-process watchdog;
- authenticated health and graceful shutdown routes;
- a KuGou-host-only compatibility bridge used by the existing signed C# adapter;
- a two-minute allowlist cache limited to anonymous, public GET routes;
- relocatable production dependencies staged with pnpm's hoisted linker;
- quiet logging that does not print query strings, credentials, or response bodies.
The original API source remains under `api/`. Original project notices are in
`MOEKOE_README.md`, `MOEKOE_LICENSE`, `api/README.md`, and `api/LICENSE`.
+13
View File
@@ -0,0 +1,13 @@
/**
!/module
!/plugins
!/public
!/static
!/util
!/app.js
!/server.js
!/package.json
!/package-lock.json
!/pnpm-lock.yaml
!/index.js
!/main.js
+21
View File
@@ -0,0 +1,21 @@
## 配置设备平台
### lite: 酷狗概念版, 默认为手机版
platform=''
## 服务端口 (可选)
PORT=''
## HTTP 代理地址 (可选)
KUGOU_API_PROXY=''
## 设备id(可选,建议固定 )
## KUGOU_API_GUID 建议为 uuidv4
## KUGOU_API_DEV 建议长度为 10 位的字符串
## MAC地址
KUGOU_API_GUID=''
KUGOU_API_DEV=''
KUGOU_API_MAC=''
## webglhash 指纹,如:9655615838832711846
## 如果想使用正式的,可以运行 public/sid_edt_generator.html 来获取固定
KUGOU_API_WEBGL=''
+148
View File
@@ -0,0 +1,148 @@
# 发行版本部署
## 多端部署
name: Build Release
## 权限配置
permissions:
contents: write
on:
push:
tags:
- v*
jobs:
# window 端打包
build-windows:
name: Build for Windows
runs-on: windows-latest
timeout-minutes: 30
steps:
# 检出 Git 仓库
- name: Check out Git repository
uses: actions/checkout@v4
# 安装 Node.js
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: '18.x'
# 安装项目依赖
- name: Install Dependencies
run: npm install
# 构建
- name: Build App for Windows
run: npm run pkgwin
shell: bash
# 上传构建产物
- name: Upload Windows artifact
uses: actions/upload-artifact@v4
with:
name: app-win
if-no-files-found: ignore
path: |
bin/*.exe
#创建 GitHub Release
- name: Create Release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/v')
with:
token: ${{ secrets.GITHUB_TOKEN}}
draft: false
prerelease: false
files: bin/*.exe
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN}}
# mac 端打包
build-masos:
name: Build for MacOS
runs-on: macos-latest
timeout-minutes: 30
steps:
# 检出 Git 仓库
- name: Check out Git repository
uses: actions/checkout@v4
# 安装 Node.js
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: '18.x'
# 安装项目依赖
- name: Install Dependencies
run: npm install
# 构建
- name: Build App for Macos
run: npm run pkgmacos
shell: bash
# 上传构建产物
- name: Upload Macos artifact
uses: actions/upload-artifact@v4
with:
name: app-macos
if-no-files-found: ignore
path: |
bin/*
#创建 GitHub Release
- name: Create Release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/v')
with:
token: ${{ secrets.GITHUB_TOKEN}}
draft: false
prerelease: false
files: bin/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN}}
# Linux 端打包
build-linux:
name: Build for Linux
runs-on: ubuntu-22.04
timeout-minutes: 30
steps:
# 检出 Git 仓库
- name: Check out Git repository
uses: actions/checkout@v4
# 安装 Node.js
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: '18.x'
# 更新 Ubuntu 软件源
- name: Ubuntu Update with sudo
run: sudo apt-get update
# 安装 RPM 和 Pacman 插件
- name: Install RPM & Pacman (on Ubuntu)
run: |
sudo apt-get install --no-install-recommends -y rpm &&
sudo apt-get install --no-install-recommends -y libarchive-tools &&
sudo apt-get install --no-install-recommends -y libopenjp2-tools
# 安装项目依赖
- name: Install Dependencies
run: npm install
# 构建
- name: Build App for Linux
run: npm run pkglinux
shell: bash
# 上传构建产物
- name: Upload Linux artifact
uses: actions/upload-artifact@v4
with:
name: app-linux
if-no-files-found: ignore
path: |
bin/*
#创建 GitHub Release
- name: Create Release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/v')
with:
token: ${{ secrets.GITHUB_TOKEN}}
draft: false
prerelease: false
files: bin/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN}}
+27
View File
@@ -0,0 +1,27 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
test
bin/*
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.env
+19
View File
@@ -0,0 +1,19 @@
{
"arrowParens": "always",
"bracketSameLine": false,
"bracketSpacing": true,
"embeddedLanguageFormatting": "auto",
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"jsxSingleQuote": false,
"printWidth": 150,
"proseWrap": "always",
"quoteProps": "preserve",
"requirePragma": false,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"useTabs": false,
"vueIndentScriptAndStyle": false
}
+184
View File
@@ -0,0 +1,184 @@
# CLAUDE.md
> KuGouMusicApi 项目指南 — 酷狗音乐 NodeJS 版 API
## 项目概述
本项目是酷狗音乐的非官方 NodeJS API 服务,通过伪造请求头和调用官方 API 实现酷狗音乐的全部功能。支持两种运行模式:HTTP 服务模式(Express)和编程式调用模式(作为库引入)。
- **作者**: Lines(基于 MakcRe 的项目)
- **版本**: 1.5.1
- **Node 要求**: >= 12
- **License**: MIT
## 技术栈
- **运行时**: Node.js
- **HTTP 框架**: Express 4.x
- **HTTP 客户端**: Axios
- **加密库**: crypto-jsAES/MD5)、node-forgeRSA
- **压缩库**: pakozlib 解压,用于 KRC 歌词解码)
- **大整数库**: big-integer(用于 MID 计算)
- **开发工具**: nodemon、prettier、esbuild、pkg
## 项目结构
```
KuGouMusicApi/
├── app.js # CLI 入口(shebang),调用 server.startService()
├── index.js # 开发入口,require('./app')
├── main.js # 编程式 API 入口(作为库使用时)
├── server.js # Express 服务器核心(CORS、Cookie、路由注册)
├── module/ # API 模块目录(约 160 个接口)
│ ├── search.js # 搜索
│ ├── login.js # 登录
│ ├── login_cellphone.js # 手机登录
│ ├── song_url.js # 歌曲 URL
│ ├── playlist_detail.js # 歌单详情
│ └── ... # 其他接口
├── util/ # 工具库
│ ├── index.js # 统一导出入口
│ ├── config.json # 平台配置(appid、clientver 等)
│ ├── crypto.js # 加密函数(AES、RSA、MD5、SHA1
│ ├── helper.js # 签名函数(signature、sign、signKey
│ ├── request.js # HTTP 请求封装(签名、代理、响应处理)
│ ├── util.js # 通用工具(随机数、Cookie、歌词解码、MID、WebGL 指纹)
│ ├── runtime.js # 运行时配置(CLI 参数、代理解析)
│ ├── generate_simulate.js # 行为指纹模拟生成(AES+RSA 加密)
│ ├── apicache.js # API 响应缓存中间件
│ └── memory-cache.js # 内存缓存实现
├── public/ # 前端页面
│ ├── index.html # 接口文档首页
│ ├── login_captcha.html # 登录页(依赖 WASM
│ ├── login_captcha_simulate.html # 登录页(纯 JS 模拟,不依赖 WASM)
│ ├── sid_edt_generator.html # SID/EDT 生成工具页
│ └── verify-pkg/ # WASM 验证码包
├── docs/ # 接口文档
├── .env.example # 环境变量配置示例
├── Dockerfile # Docker 部署配置
├── vercel.json # Vercel 部署配置
└── package.json
```
## 核心架构
### 请求流程
```
客户端请求 → Express 中间件(CORS → Cookie 解析 → 平台标识注入 → 缓存)
→ 路由处理器(参数合并 → Cookie 格式化 → Authorization 头解析)
→ API 模块函数(构建请求参数 → 生成签名 → 调用 createRequest
→ createRequest(注入设备标识 → 签名 → 代理 → 发送请求 → 响应处理)
→ 酷狗服务器
```
### 签名机制
所有 API 请求都需要签名验证,支持三种签名类型:
- **Android 签名**(默认): `MD5(盐值 + 排序参数 + 请求体 + 盐值)`
- **Web 签名**: `MD5(盐值 + 排序参数 + 盐值)`
- **Register 签名**: `MD5("1014" + 排序值 + "1014")`
区分标准版和概念版(lite),使用不同的盐值和 appid。
### 行为指纹(SID/EDT
酷狗服务端会检测请求的行为指纹,需要生成模拟数据:
- **SID**: RSA-OAEP 加密的 AES 密钥(Base64
- **EDT**: AES-128-CBC 加密的行为数据(Base64
- 行为数据包括:鼠标轨迹(贝塞尔曲线)、滚动事件、窗口事件、WebGL 指纹
### 双平台支持
通过 `.env` 中的 `platform` 环境变量切换:
- **标准版**(默认): `appid=1005`, `clientver=20489`
- **概念版 lite**: `appid=3116`, `clientver=11440`
## 常用命令
```bash
# 安装依赖
npm install
# 开发模式(热重载)
npm run dev
# 生产模式
npm start
# 自定义端口
PORT=4000 npm run dev
# 使用代理
KUGOU_API_PROXY=http://127.0.0.1:7890 npm run dev
# CLI 参数方式设置代理
node app.js --proxy=http://127.0.0.1:7890 --platform=lite
```
## 环境变量
| 变量 | 说明 | 默认值 |
|------|------|--------|
| `platform` | 平台类型:`lite`(概念版)或空(标准版) | 空 |
| `PORT` | 服务端口 | 3000 |
| `HOST` | 监听地址 | 空(所有地址) |
| `KUGOU_API_PROXY` | HTTP 代理地址 | 空 |
| `KUGOU_API_GUID` | 设备 GUID(建议 UUID v4 | 自动生成 |
| `KUGOU_API_DEV` | 开发设备标识(10位大写字符串) | 自动生成 |
| `KUGOU_API_MAC` | 设备 MAC 地址 | 02:00:00:00:00:00 |
| `KUGOU_API_WEBGL` | WebGL 指纹哈希 | 自动生成随机值 |
## 开发规范
### 添加新接口
1.`module/` 目录下创建新的 `.js` 文件
2. 文件名即为路由路径(`_` 替换为 `/`),如 `user_detail.js``/user/detail`
3. 模块导出一个函数:`(params, useAxios) => Promise`
- `params`: 合并后的请求参数(含 cookie、query、body
- `useAxios`: 请求工厂函数,调用 `createRequest` 发送请求
```javascript
// module/example.js
module.exports = (params, useAxios) => {
return new Promise((resolve, reject) => {
useAxios({
baseURL: 'https://gateway.kugou.com',
url: '/v1/example',
method: 'GET',
params: { keyword: params.keyword },
cookie: params.cookie,
}).then(resolve).catch(reject);
});
};
```
### 签名选择
- 大多数接口使用 `android` 签名(默认)
- Web 相关接口使用 `web` 签名
- 设备注册接口使用 `register` 签名
### Cookie 传递
Cookie 通过以下方式传递:
- URL 查询参数: `?cookie=token%3Dxxx%3Buserid%3Dxxx`
- 请求体: `{ cookie: "token=xxx;userid=xxx" }`
- Authorization 头: `Authorization: token=xxx;userid=xxx`
服务端会自动将 Cookie 字符串转换为 JSON 对象并合并。
## 代码风格
- 使用 Prettier 格式化(配置见 `.prettierrc.json`
- 2 空格缩进
- 单引号
- 无分号(Prettier 默认)
## 部署
- **Docker**: 使用项目根目录的 `Dockerfile`
- **Vercel**: 使用 `vercel.json` 配置
- **pkg 打包**: `npm run pkgwin` / `npm run pkglinux` / `npm run pkgmacos`
- **esbuild 打包**: `npm run pkgjs`
+28
View File
@@ -0,0 +1,28 @@
FROM node:lts-alpine
RUN apk add --no-cache tini
RUN corepack enable
RUN npm config set registry https://registry.npmmirror.com && npm install -g pnpm --force
ENV NODE_ENV=production
WORKDIR /app
RUN chown node:node /app
COPY --chown=node:node package.json pnpm-lock.yaml ./
USER node
#RUN pnpm install --prod --frozen-lockfile --ignore-scripts false
RUN npm install --production --ignore-scripts=false
COPY --chown=node:node . ./
EXPOSE 3000
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "app.js"]
+14
View File
@@ -0,0 +1,14 @@
MIT License
Copyright (c) 2023 MakcRe
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
+259
View File
@@ -0,0 +1,259 @@
# KuGouMusic API
酷狗音乐 NodeJS 版 API
[![](https://img.shields.io/badge/Author-MakcRe-blueviolet?style=for-the-badge '作者')](https://github.com/MakcRe)
![](https://img.shields.io/badge/dynamic/json?label=GitHub%20Followers&style=for-the-badge&query=%24.data.totalSubs&url=https%3A%2F%2Fapi.spencerwoo.com%2Fsubstats%2F%3Fsource%3Dgithub%26queryKey%3DMakcRe&labelColor=282c34&color=181717&logo=github&longCache=true '关注数量')
![](https://img.shields.io/github/stars/MakcRe/KuGouMusicApi.svg?style=for-the-badge&label=Star 'Star数量')
![](https://img.shields.io/github/forks/MakcRe/KuGouMusicApi.svg?style=for-the-badge&label=Fork 'Fork数量')
![](https://img.shields.io/github/issues/MakcRe/KuGouMusicApi.svg?style=for-the-badge&label=Issues 'Issues数量')
![](https://img.shields.io/github/contributors/MakcRe/KuGouMusicApi?style=for-the-badge '贡献者')
![](https://img.shields.io/github/repo-size/MakcRe/KuGouMusicApi?style=for-the-badge&label=files&color=cf8ef4&labelColor=373e4dl '文件大小')
![](https://img.shields.io/github/languages/code-size/MakcRe/KuGouMusicApi?color=blueviolet&style=for-the-badge '代码大小')
[//]: # '<br>'
![](https://img.shields.io/github/package-json/v/MakcRe/KuGouMusicApi?longCache=true&style=for-the-badge)
![](https://img.shields.io/badge/Node-12+-green.svg?longCache=true&style=for-the-badge)
![](https://img.shields.io/badge/License-mit-blue.svg?longCache=true&style=for-the-badge)
## 灵感来自
[Binaryify/NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi)
## 环境要求
需要 NodeJS 12+ 环境
## 工作原理
跨站请求伪造 (CSRF), 伪造请求头 , 调用官方 API
## 免责声明
> 1. 本项目仅供学习使用,请尊重版权,请勿利用此项目从事商业行为及非法用途!
> 2. 使用本项目的过程中可能会产生版权数据。对于这些版权数据,本项目不拥有它们的所有权。为了避免侵权,使用者务必在 24 小时内清除使用本项目的过程中所产
> 生的版权数据。
> 3. 由于使用本项目产生的包括由于本协议或由于使用或无法使用本项目而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停
> 工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。
> 4. **禁止在违反当地法律法规的情况下使用本项目。** 对于使用者在明知或不知当地法律法规不允许的情况下使用本项目所造成的任何违法违规行为由使用者承担,本
> 项目不承担由此造成的任何直接、间接、特殊、偶然或结果性责任。
> 5. 音乐平台不易,请尊重版权,支持正版。
> 6. 本项目仅用于对技术可行性的探索及研究,不接受任何商业(包括但不限于广告等)合作及捐赠。
> 7. 如果官方音乐平台觉得本项目不妥,可联系本项目更改或移除。
### 安装
```shell
$ git clone git@github.com:MakcRe/KuGouMusicApi.git
$ cd KuGouMusicApi
$ npm install
```
### 使用接口为概念版
```
$ 复制 .env.example 为 .env,并且把里面的 `platform=''` 改为 `platform=lite`
$ 注意不同版本的平台的 token 是不通用的。
```
### 运行
```shell
$ npm run dev
```
服务器启动默认端口为 3000, 若不想使用 3000 端口 , 可使用以下命令 : Mac/Linux
```shell
$ PORT=4000 npm run dev
```
windows 下使用 git-bash 或者 cmder 等终端执行以下命令 :
```shell
$ set PORT=4000 && npm run dev
```
windows 下使用 PowerShell 等终端执行以下命令 :
```shell
$ $Env:PORT=4000; npm run dev
```
服务器启动默认 host 为 localhost,如果需要更改, 可使用以下命令 : Mac/Linux
```shell
$ HOST=127.0.0.1 npm run dev
```
windows 下使用 git-bash 或者 cmder 等终端执行以下命令 :
```shell
$ set HOST=127.0.0.1 && npm run dev
```
windows 下使用 PowerShell 等终端执行以下命令 :
```shell
$ $Env:HOST=127.0.0.1; npm run dev
```
### 代理配置
如需通过 HTTP 代理请求酷狗接口,可在启动前设置 `KUGOU_API_PROXY` 环境变量,例如:
```shell
$ $Env:KUGOU_API_PROXY='http://127.0.0.1:7890'; npm run dev
```
也可以在启动命令后追加代理参数:
```shell
$ node app.js --proxy=http://127.0.0.1:7890
```
## Vercel 部署
### 操作方法
1. fork 此项目
2. 在 Vercel 官网点击 `New Project`
3. 点击 `Import Git Repository` 并选择你 fork 的此项目并点击 `import`
4. 点击 `PERSONAL ACCOUNT``select`
5. 直接点 `Continue`
6. 若需要部署版本为概念版(不需要该步骤可以跳过),在 `Environment Variables` 添加 `key``platform``Value (Will Be Encrypted)``lite` 然后点击
`Add`
7. `PROJECT NAME`自己填,`FRAMEWORK PRESET``Other` 然后直接点 `Deploy` 接着等部署完成即可
## 功能特性
1. 登录
2. 刷新登录
3. 发送验证码
4. dfid 获取
5. 获取用户额外信息
6. 获取用户 vip 信息
7. 获取用户歌单
8. 获取用户关注歌手
9. 获取用户听歌历史排行
10. 获取用户最近听歌历史
11. 获取继续播放信息(对应手机版首页显示继续播放入口)
12. 收藏歌单/新建歌单
13. 取消收藏歌单/删除歌单
14. 对歌单添加歌曲
15. 对歌单删除歌曲
16. 新碟上架
17. 专辑信息
18. 专辑详情
19. 专辑音乐列表
20. 获取音乐 URL
21. 获取歌曲高潮部分
22. 搜索
23. 默认搜索关键词
24. 综合搜索
25. 热搜列表
26. 搜索建议
27. 歌词搜索
28. 获取歌词
29. 歌单分类
30. 歌单
31. 主题歌单
32. 音效歌单
33. 获取歌单详情
34. 获取歌单所有歌曲
35. 获取歌单所有歌曲(新版)
36. 相似歌单
37. 获取主题歌单所有歌曲
38. 获取主题音乐
39. 获取主题音乐详情
40. 歌曲推荐
41. 获取歌手和专辑图片
42. 获取歌手图片
43. 获取音乐相关信息
44. 获取更多音乐版本
45. 获取音乐伴奏信息
46. 获取音乐 k 歌数量
47. 获取音乐详情
48. 获取音乐专辑/歌手信息
49. 私人 FM(对应手机和 pc 端的猜你喜欢)
50. banner
51. 乐库 banner
52. 乐库电台
53. 乐库
54. 电台 - 推荐
55. 电台
56. 电台 - 图片
57. 电台 - 音乐列表
58. 编辑精选
59. 编辑精选数据
60. 编辑精选歌单
61. 编辑精选专区
62. 编辑精选专区详情
63. 领取 VIP(需要登陆,该接口为测试接口)
64. 获取歌手列表
65. 获取歌手详情
66. 获取歌手专辑
67. 获取歌手单曲
68. 获取歌手 MV
69. 关注歌手
70. 取消关注歌手
71. 获取关注歌手新歌
72. 获取视频 url
73. 获取歌曲 MV
74. 获取视频相关信息
75. 获取视频详情
76. 新歌速递
77. 场景音乐列表
78. 场景音乐详情
79. 获取场景音乐讨论区
80. 获取场景音乐模块 Tag
81. 获取场景音乐歌单列表
82. 获取场景音乐视频列表
83. 获取场景音乐音乐列表
84. 每日推荐
85. 历史推荐
86. 风格推荐
87. 排行列表
88. 排行榜推荐列表
89. 排行榜往期列表
90. 排行榜信息
91. 排行榜歌曲列表
92. 歌曲评论
93. 歌曲评论-根据分类返回
94. 歌曲评论-根据热词返回
95. 楼层评论
96. 歌单评论
97. 专辑评论
98. 歌曲曲谱
99. 曲谱详情
100. 推荐曲谱
101. 曲谱合集
102. 曲谱合集详情
103. 提交听歌历史
104. 获取服务器时间
105. 刷刷
106. AI 推荐
107. 频道 - 获取用户所有频道
108. 频道 - 详情
109. 频道 - 频道安利
110. 频道 - 相似频道
111. 频道 - 订阅
112. 频道 - 音乐故事
113. 频道 - 音乐故事详情
114. 动态 - 最常访问
115. 获取用户公开的音乐
116. 听书 - 每日推荐
117. 听书 - 排行榜推荐
118. 听书 - VIP 推荐
119. 听书 - 每周推荐
120. 听书 - 专辑详情
121. 听书 - 专辑音乐列表
122. 歌曲详情 - 歌曲成绩单
123. 歌曲详情 - 歌曲成绩单详情
124. 获取已购单曲
125. 获取已购专辑
## License
[The MIT License (MIT)](https://github.com/MakcRe/KuGouMusicApi/blob/main/LICENSE)
+8
View File
@@ -0,0 +1,8 @@
#!/usr/bin/env node
async function start() {
require('./util/runtime').applyCliOverrides();
await require('./server').startService();
}
start().catch(console.error);
View File
File diff suppressed because it is too large Load Diff
+12
View File
@@ -0,0 +1,12 @@
# 酷狗音乐 API
> 酷狗音乐 NodeJS 版 API
- 全部接口已升级到最新
- 具备登录接口
- 更完善的文档
[GitHub](https://github.com/MakcRe/KuGouMusicApi)
[Get Started](#kugoumusic-api)
![color](#fff)
+23
View File
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>酷狗音乐 NodeJS 版 API</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="Description" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css" />
</head>
<body>
<div id="app"></div>
<script>
window.$docsify = {
name: '酷狗音乐 API',
repo: 'https://github.com/MakcRe/KuGouMusicApi',
coverpage: true,
};
</script>
<!-- Docsify v4 -->
<script src="//cdn.jsdelivr.net/npm/docsify@4"></script>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
require('./app');
File diff suppressed because it is too large Load Diff
+126
View File
@@ -0,0 +1,126 @@
/**
* @fileoverview KuGouMusicApi 程序化 API 入口模块
*
* 本模块是项目的编程式调用入口(区别于 index.js 的 HTTP 服务入口),
* 负责:
* 1. 扫描 `module/` 目录下所有 API 模块文件并动态加载
* 2. 为每个模块创建统一的包装函数,自动处理 Cookie 格式转换
* 3. 将所有 API 函数与服务器工具、请求工具合并后统一导出
*
* 导出结构为扁平对象,模块文件名(去 .js 后缀)即为 API 函数名。
* 例如:`module/search.js` → `api.search(params)`
*
* @module app
* @requires node:fs
* @requires path
* @requires ./util - 工具函数(cookieToJson 等)
* @requires ./server - 服务器管理(startService、getModulesDefinitions
* @requires ./util/request - 底层 HTTP 请求工具(createRequest
*
* @example
* // 作为库使用(编程式调用,不启动 HTTP 服务)
* const api = require('./app');
*
* // 手机登录
* const loginRes = await api.login_cellphone({
* mobile: '13800138000',
* code: '123456'
* });
*
* // 搜索音乐(携带认证 Cookie)
* const searchRes = await api.search({
* keywords: '海阔天空',
* cookie: `token=${loginRes.body.token};userid=${loginRes.body.userid}`
* });
*/
const fs = require('node:fs');
const path = require('path');
const { cookieToJson } = require('./util');
/**
* 动态注册的 API 函数集合
*
* 键为模块文件名(去 .js 后缀),值为对应的包装函数。
* 例如:`{ search: [Function], login_cellphone: [Function], ... }`
*
* @type {Record<string, (data?: Record<string, any>) => Promise<any>>}
*/
let obj = {};
/**
* ============================================================
* 动态扫描并加载 module/ 目录下的所有 API 模块
* ============================================================
*
* 扫描流程:
* 1. 使用 `fs.readdirSync` 同步读取 `module/` 目录下所有文件
* 2. 对文件列表进行倒序排列(与 server.js 中的路由注册顺序保持一致)
* 3. 过滤出以 `.js` 结尾的文件
* 4. 对每个模块文件:
* a. 通过 `require` 加载模块,获取模块处理函数
* b. 从文件名提取 API 函数名(去掉 `.js` 后缀,如 `search.js` → `search`
* c. 创建包装函数并注册到 `obj` 对象中
*
* 每个包装函数的作用:
* - 将调用者传入的 cookie 字符串自动转换为 JSON 对象
* - 合并调用参数与默认 cookie
* - 调用原始模块处理函数,同时注入请求工厂函数(lazy 加载 createRequest
*/
fs.readdirSync(path.join(__dirname, 'module'))
.reverse()
.forEach((file) => {
// 跳过非 .js 文件(如 .json、.map、目录等)
if (!file.endsWith('.js')) return;
// 加载模块,获取其导出的处理函数
let fileModule = require(path.join(__dirname, 'module', file));
// 提取 API 函数名:取文件名中第一个 `.` 之前的部分
// 例如:`search.js` → `search``login_cellphone.js` → `login_cellphone`
let fn = file.split('.').shift() || '';
/**
* 为当前模块创建包装函数
*
* @param {Record<string, any>} [data={}] - 调用者传入的请求参数
* @returns {Promise<any>} 模块处理函数的返回结果
*/
obj[fn] = (data = {}) => {
// 自动将 cookie 字符串转换为 JSON 对象
// 调用者可以传入 cookie 字符串(如 "token=xxx;userid=xxx")或对象格式
if (typeof data.cookie === 'string') data.cookie = cookieToJson(data.cookie);
// 调用原始模块处理函数,传入两个参数:
// 1. 合并后的参数对象(确保 cookie 始终为对象格式,默认为空对象)
// 2. 请求工厂函数:延迟加载 createRequest,避免循环依赖
return fileModule({ ...data, cookie: data.cookie ? data.cookie : {} }, (...args) => {
// 延迟加载:仅在实际需要发起请求时才 require createRequest
// 这样可以避免模块加载阶段的循环依赖问题
const { createRequest } = require('./util/request');
return createRequest(...args);
});
};
});
/**
* ============================================================
* 统一导出
* ============================================================
*
* 最终导出一个扁平对象,包含三部分内容:
*
* 1. `...require('./server')` — 服务器管理工具
* - `startService()`: 启动 HTTP 服务
* - `getModulesDefinitions()`: 动态扫描模块定义
*
* 2. `...require('./util/request')` — 底层请求工具
* - `createRequest()`: 创建 HTTP 请求
*
* 3. `...obj` — 所有 API 模块函数
* - 每个 module/*.js 文件对应一个同名函数
* - 如 `search`、`login_cellphone`、`song_url` 等
*
* @type {Record<string, any> & import("./server")}
*/
module.exports = { ...require('./server'), ...require('./util/request'), ...obj };
+34
View File
@@ -0,0 +1,34 @@
const { appid, clientver, signParamsKey, cryptoMd5 } = require('../util');
module.exports = (params, useAxios) => {
const userid = params?.userid || params?.cookie?.userid || 0;
const mid = params?.cookie?.KUGOU_API_MID; // 可以自定义
const clienttime = Date.now();
const recommend_source = (params?.album_audio_id || '').split(',').map((s) => ({ ID: Number(s) }));
const dataMap = {
platform: 'ios',
clientver,
clienttime: clienttime,
userid,
client_playlist: [],
source_type: 2,
playlist_ver: 2,
area_code: 1,
appid,
key: signParamsKey(clienttime.toString()),
mid,
recommend_source,
};
return useAxios({
url: '/recommend',
data: dataMap,
method: 'post',
encryptType: 'android',
cookie: params?.cookie || {},
headers: { 'x-router': 'songlistairec.kugou.com' },
clearDefaultParams: true,
});
};
+34
View File
@@ -0,0 +1,34 @@
const { clientver, appid, cryptoMd5, signParamsKey } = require('../util');
module.exports = (params, useAxios) => {
const dateTime = Date.now();
const data = (params?.album_id || '').split(',').map((s) => ({ album_id: s, album_name: '', author_name: '' }));
const dfid = params?.cookie?.dfid || params?.dfid || '-';
const userid = params?.cookie?.userid || params?.userid || 0;
const token = params?.cookie?.token || params?.token || 0;
const dataMap = {
appid,
clienttime: dateTime,
clientver,
data,
dfid,
fields: params?.fields || '',
key: signParamsKey(dateTime),
mid: params?.cookie?.KUGOU_API_MID,
};
if (token) dataMap['token'] = token;
if (userid) dataMap['userid'] = userid;
return useAxios({
baseURL: 'http://kmr.service.kugou.com',
url: '/v1/album',
method: 'POST',
data: dataMap,
encryptType: 'android',
cookie: params?.cookie || {},
headers: { 'x-router': 'kmr.service.kugou.com', 'Content-Type': 'application/json' },
});
};
+16
View File
@@ -0,0 +1,16 @@
// 专辑详情
module.exports = (params, useAxios) => {
const data = {
data: [{ album_id: params.id }],
is_buy: params?.is_buy || 0,
fields: 'album_id,album_name,publish_date,sizable_cover,intro,language,is_publish,heat,type,quality,authors,exclusive,author_name,trans_param',
};
return useAxios({
url: '/kmr/v2/albums',
method: 'POST',
data,
encryptType: 'android',
cookie: params?.cookie || {},
headers: { 'x-router': 'openapi.kugou.com', 'kg-tid': '255' },
});
};
+9
View File
@@ -0,0 +1,9 @@
// 唱片店
module.exports = (params, useAxios) => {
return useAxios({
url: '/zhuanjidata/v3/album_shop_v2/get_classify_data',
method: 'GET',
encryptType: 'android',
cookie: params?.cookie || {},
});
};
+18
View File
@@ -0,0 +1,18 @@
// 专辑音乐列表
module.exports = (params, useAxios) => {
const dataMap = {
album_id: params.id,
is_buy: params?.is_buy || '',
page: params?.page || 1,
pagesize: params?.pagesize || 30,
};
return useAxios({
url: '/v1/album_audio/lite',
method: 'POST',
data: dataMap,
encryptType: 'android',
cookie: params?.cookie || {},
headers: { 'x-router': 'openapi.kugou.com', 'kg-tid': '255' },
});
}
+21
View File
@@ -0,0 +1,21 @@
// 获取歌手专辑
module.exports = (params, useAxios) => {
const dataMap = {
author_id: params.id,
pagesize: params?.pagesize || 30,
page: params?.page || 1,
sort: params?.sort === 'hot' ? 3 : 1, // 3:最热,1:最新
category: 1,
area_code: 'all',
};
return useAxios({
url: '/kmr/v1/author/albums',
method: 'POST',
data: dataMap,
encryptType: 'android',
cookie: params?.cookie || {},
headers: { 'x-router': 'openapi.kugou.com', 'kg-tid': 36 },
});
};
+29
View File
@@ -0,0 +1,29 @@
const { cryptoMd5, signParamsKey, clientver, appid } = require('../util');
// 获取歌手单曲
module.exports = (params, useAxios) => {
const clienttime = Math.floor(new Date().getTime() / 1000);
const mid = params?.cookie?.KUGOU_API_MID;
const dataMap = {
appid,
clientver,
mid,
clienttime,
key: signParamsKey(clienttime),
author_id: params.id,
pagesize: params?.pagesize || 30,
page: params?.page || 1,
sort: params?.sort === 'hot' ? 1 : 2, // 1:最热,2:最新
area_code: 'all',
};
return useAxios({
baseURL: 'https://openapi.kugou.com',
url: '/kmr/v1/audio_group/author',
method: 'POST',
data: dataMap,
encryptType: 'android',
cookie: params?.cookie || {},
headers: { 'x-router': 'openapi.kugou.com', 'kg-tid': 220 },
});
};
+11
View File
@@ -0,0 +1,11 @@
// 歌手详情
module.exports = (params, useAxios) => {
return useAxios({
url: '/kmr/v3/author',
method: 'POST',
data: { author_id: params.id },
encryptType: 'android',
cookie: params?.cookie || {},
headers: { 'x-router': 'openapi.kugou.com', 'kg-tid': 36 },
});
};
+31
View File
@@ -0,0 +1,31 @@
const { cryptoAesEncrypt, rsaEncrypt2 } = require('../util');
// 关注歌手
module.exports = (params, useAxios) => {
const singerid = Number(params.id);
const token = params?.token || params.cookie?.token || '';
const userid = Number(params?.userid || params?.cookie?.userid || 0);
const clienttime = Math.floor(Date.now() / 1000);
const encrypt = cryptoAesEncrypt({ singerid, token });
const paramsMap = {
plat: 0,
userid,
singerid,
source: 7,
p: rsaEncrypt2({ clienttime, key: encrypt.key }),
params: encrypt.str
}
return useAxios({
url: '/followservice/v3/follow_singer',
method: 'post',
data: paramsMap,
params: {clienttime},
encryptType: 'android',
cookie: params?.cookie || {},
});
};
@@ -0,0 +1,21 @@
// 获取关注歌手新歌
module.exports = (params, useAxios) => {
const last_album_id = params.last_album_id || 0;
const page_size = params.pagesize || 30;
const opt_sort = params.opt_sort === 2 ? 2 : 1;
const paramsMap = {
last_album_id,
page_size,
opt_sort,
};
return useAxios({
url: '/feed/v1/follow/newsong_album_list',
method: 'post',
data: { last_album_id },
params: paramsMap,
encryptType: 'android',
cookie: params?.cookie || {},
});
};
+11
View File
@@ -0,0 +1,11 @@
// 歌手荣誉详情
module.exports = (params, useAxios) => {
return useAxios({
baseURL: 'http://h5activity.kugou.com',
url: '/v1/query_singer_honour_detail',
method: 'POST',
params: { singer_id: params.id, pagesize: params?.pagesize || 30, page: params?.page || 1 },
encryptType: 'android',
cookie: params?.cookie || {},
});
};
+18
View File
@@ -0,0 +1,18 @@
// 歌手列表
module.exports = (params, useAxios) => {
const paramsMap = {
musician: Number(params.musician || 0),
sextype: params.sextypes || 0,
showtype: 2,
type: params.type || 0,
hotsize: Number(params.hotsize || 30)
};
return useAxios({
url: '/ocean/v6/singer/list',
method: 'GET',
params: paramsMap,
encryptType: 'android',
cookie: params?.cookie || {},
});
};
+28
View File
@@ -0,0 +1,28 @@
const { cryptoAesEncrypt, rsaEncrypt2 } = require('../util');
// 取消关注歌手
module.exports = (params, useAxios) => {
const singerid = params.id;
const token = params?.token || params.cookie?.token || '';
const userid = params?.userid || params?.cookie?.userid || 0;
const clienttime = Math.floor(Date.now() / 1000);
const encrypt = cryptoAesEncrypt({ singerid, token });
const paramsMap = {
plat: 0,
userid,
singerid,
source: 7,
p: rsaEncrypt2({ clienttime, key: encrypt.key }),
params: encrypt.str
}
return useAxios({
url: '/followservice/v3/unfollow_singer',
method: 'post',
data: paramsMap,
encryptType: 'android',
cookie: params?.cookie || {},
});
};
+22
View File
@@ -0,0 +1,22 @@
// 获取歌手mv
const tag_idx = { official: 18, live: 20, fan: 23, artist: 42419, all: '' };
module.exports = (params, useAxios) => {
const paramsMap = {
author_id: params.id,
is_fanmade: '',
tag_idx: tag_idx[params?.tag || 'all'] || '', // 18:官方版本,20:现场版本,23:饭制版本,42419:歌手发布
pagesize: params.pagesize || 30,
page: params.page || 1,
};
return useAxios({
baseURL: 'https://openapicdn.kugou.com',
url: '/kmr/v1/author/videos',
method: 'GET',
params: paramsMap,
encryptType: 'android',
cookie: params?.cookie || {},
});
};
+32
View File
@@ -0,0 +1,32 @@
const { signParamsKey, appid, clientver } = require('../util');
module.exports = (params, useAxios) => {
const dateTime = Date.now();
const data = (params?.hash || '').split(',').map((s) => ({ hash: s, audio_id: 0 }));
const dfid = params?.cookie?.dfid || params?.dfid || '-';
const userid = params?.cookie?.userid || params?.userid || 0;
const token = params?.cookie?.token || params?.token || 0;
const dataMap = {
appid,
clienttime: dateTime,
clientver,
data,
dfid,
key: signParamsKey(dateTime),
mid: params?.cookie?.KUGOU_API_MID,
};
if (token) dataMap['token'] = token;
if (userid) dataMap['userid'] = userid;
return useAxios({
baseURL: 'http://kmr.service.kugou.com',
url: '/v1/audio/audio',
method: 'POST',
data: dataMap,
encryptType: 'android',
cookie: params?.cookie || {},
headers: { 'x-router': 'kmr.service.kugou.com', 'Content-Type': 'application/json' },
});
};
@@ -0,0 +1,34 @@
const { cryptoMd5, appid } = require('../util');
module.exports = (params, useAxios) => {
const dataMap = {
isteen: 0,
mixId: Number(params.mixId) || 0,
usemkv: 1,
platform: 2,
fileName: params.fileName || '',
hash: params.hash,
version: 12375,
appid,
}
const str = '*s&iN#G70*';
const paramsString = Object.keys(dataMap)
.sort()
.map((key) => `${key}=${typeof dataMap[key] === 'object' ? JSON.stringify(dataMap[key]) : dataMap[key]}`)
.join('&');
dataMap['sign'] = cryptoMd5(`${paramsString}${str}`).substring(8, 24);
return useAxios({
baseURL: 'https://nsongacsing.kugou.com',
url: '/sing7/accompanywan/json/v2/cdn/optimal_matching_accompany_2_listen.do',
params: dataMap,
method: 'get',
encryptType: 'android',
cookie: params?.cookie || {},
clearDefaultParams: true,
notSignature: true,
});
};
+34
View File
@@ -0,0 +1,34 @@
const { cryptoMd5, appid } = require('../util');
module.exports = (params, useAxios) => {
const dataMap = {
isteen: 0,
songId: Number(params.songId),
usemkv: 1,
platform: 2,
singerName: params.singerName,
songHash: params.songHash,
version: 12375,
appid,
}
const str = '*s&iN#G70*';
const paramsString = Object.keys(dataMap)
.sort()
.map((key) => `${key}=${typeof dataMap[key] === 'object' ? JSON.stringify(dataMap[key]) : dataMap[key]}`)
.join('&');
dataMap['sign'] = cryptoMd5(`${paramsString}${str}`).substring(8, 24);
return useAxios({
baseURL: 'https://acsing.service.kugou.com',
url: '/sing7/listenguide/json/v2/cdn/listenguide/get_total_opus_num_v02.do',
params: dataMap,
method: 'get',
encryptType: 'android',
cookie: params?.cookie || {},
clearDefaultParams: true,
notSignature: true,
});
};
+20
View File
@@ -0,0 +1,20 @@
// 听歌识曲
module.exports = (params, useAxios) => {
const paramsMap = {
fpid: Date.now(),
area_code: 1,
include_unpublish: 1,
useid: params?.userid || params?.cookie?.userid || 0,
multi_result: 1,
};
return useAxios({
url: '/fingerprint.service/v1/music_trackid_mulit',
encryptType: 'android',
method: 'POST',
data: params.data,
params: paramsMap,
cookie: params?.cookie || {},
headers: { 'content-type': 'application/octet-stream', 'user-agent': 'KuGou/11490 (Android)' },
});
};
+46
View File
@@ -0,0 +1,46 @@
const { cryptoMd5 } = require('../util');
const sortType = { all: 1, hot: 2, new: 3 };
// https://listkmrp3cdnretry.kugou.com/v3/album_audio/related
module.exports = (params, useAxios) => {
const show_detail = Number(params.show_detail) === 0;
let dataMap = {
album_audio_id: Number(params.album_audio_id),
appid: 1005,
area_code: 1,
clientver: 12329,
};
if (!show_detail) {
dataMap = {
...dataMap,
page: params.page || 1,
pagesize: params.pagesize || 30,
show_input: 1,
show_type: params.show_type || 0,
sort: sortType[params.sort] || 1,
type: params.type || 0,
};
}
dataMap['version'] = 1;
const str = 'OIlwieks28dk2k092lksi2UIkp';
const paramsString = Object.keys(dataMap)
.sort()
.map((key) => `${key}=${typeof dataMap[key] === 'object' ? JSON.stringify(dataMap[key]) : dataMap[key]}`)
.join('');
dataMap['signature'] = cryptoMd5(`${str}${paramsString}${str}`);
return useAxios({
baseURL: 'https://listkmrp3cdnretry.kugou.com',
url: !show_detail ? '/v3/album_audio/related' : '/v2/audio_related/total',
params: dataMap,
method: 'get',
encryptType: 'android',
cookie: params?.cookie || {},
clearDefaultParams: true,
});
};
+49
View File
@@ -0,0 +1,49 @@
const { appid, clientver, signParamsKey } = require('../util');
module.exports = (params, useAxios) => {
const userid = params?.cookie?.userid || params?.userid || 0;
const vip_type = params?.cookie?.vip_type || params?.vipType || 0;
const dateTime = Date.now();
const personal_recommend = {
userid,
appid,
playlist_ver: 2,
clienttime: dateTime,
mid: params?.cookie?.KUGOU_API_MID,
new_sync_point: dateTime,
module_id: 1,
action: 'login',
vip_type,
vip_flags: 3,
recommend_source_locked: 0,
song_pool_id: Number(params?.song_pool_id || 0),
callerid: 0,
m_type: 1,
kguid: userid,
platform: 'ios',
area_code: 1,
fakem: 'ca981cfc583a4c37f28d2d49000013c16a0a',
clientver: 11850,
mode: params?.mode || 'normal',
active_swtich: 'on',
key: signParamsKey(dateTime),
};
const dataMap = {
behaviors: [],
abtest: {
abtest: { shuashua: { commentcard: 2 } },
},
personal_recommend_params: personal_recommend,
};
return useAxios({
url: '/genesisapi/v1/newepoch_song_rec/feed',
data: dataMap,
params: { sort_type: 1, platform: 'ios', page: 1, content_ver: 4, clientver: 11850 },
method: 'post',
encryptType: 'android',
cookie: params?.cookie || {},
});
};
+17
View File
@@ -0,0 +1,17 @@
// 手机验证码发送
module.exports = (params, useAxios) => {
const dataMap = {
businessid: 5,
mobile: `${params?.mobile}`,
plat: 3,
};
return useAxios({
baseURL: 'http://login.user.kugou.com',
url: '/v7/send_mobile_code',
method: 'POST',
data: dataMap,
encryptType: 'android',
cookie: {mid: params?.cookie?.KUGOU_API_MID},
});
};
+21
View File
@@ -0,0 +1,21 @@
// 歌曲评论
module.exports = (params, useAxios) => {
const paramsMap = {
childrenid: params.id,
need_show_image: 1,
p: params.page || 1,
pagesize: params.pagesize || 30,
show_classify: params.show_classify || 1,
show_hotword_list: params.show_hotword_list || 1,
code: '94f1792ced1df89aa68a7939eaf2efca',
}
return useAxios({
url: '/m.comment.service/v1/cmtlist',
encryptType: 'android',
method: 'POST',
params: paramsMap,
cookie: params?.cookie || {},
});
};
+23
View File
@@ -0,0 +1,23 @@
// 歌曲评论数
module.exports = (params, useAxios) => {
const paramsMap = {
r: 'comments/getcommentsnum',
code: 'fc4be23b4e972707f36b8a828a93ba8a',
};
if (params && params.hash) {
paramsMap.hash = params.hash;
} else if (params && params.special_id) {
paramsMap.childrenid = params.special_id;
}
return useAxios({
url: '/index.php',
encryptType: 'web',
method: 'GET',
params: paramsMap,
cookie: params?.cookie || {},
headers: { 'x-router': 'sum.comment.service.kugou.com' },
});
};
+59
View File
@@ -0,0 +1,59 @@
// 楼层评论
const songCode = 'fc4be23b4e972707f36b8a828a93ba8a';
const playlistCode = 'ca53b96fe5a1d9c22d71c8f522ef7c4f';
const albumCode = '94f1792ced1df89aa68a7939eaf2efca';
const isNonEmpty = (value) => {
if (value == null) return false;
const text = `${value}`.trim();
return text !== '' && text !== 'null' && text !== 'undefined';
};
const resolveFloorCommentRequestConfig = (params = {}) => {
const resourceType = `${params.resource_type || params.resourceType || ''}`.toLowerCase();
const code = isNonEmpty(params.code)
? `${params.code}`
: resourceType === 'playlist'
? playlistCode
: resourceType === 'album'
? albumCode
: songCode;
const useServiceEndpoint =
resourceType === 'playlist' ||
resourceType === 'album' ||
code === playlistCode ||
code === albumCode;
const paramsMap = {
childrenid: params.special_id,
need_show_image: 1,
p: params.page || 1,
pagesize: params.pagesize || 30,
show_classify: params.show_classify ?? 1,
show_hotword_list: params.show_hotword_list ?? 1,
code,
tid: params.tid,
};
if (isNonEmpty(params.mixsongid)) {
paramsMap.mixsongid = params.mixsongid;
}
return {
url: useServiceEndpoint
? '/m.comment.service/v1/hot_replylist'
: '/mcomment/v1/hot_replylist',
encryptType: 'android',
method: 'POST',
params: paramsMap,
cookie: params?.cookie || {},
};
};
const commentFloor = (params, useAxios) => {
return useAxios(resolveFloorCommentRequestConfig(params));
};
module.exports = commentFloor;
module.exports.resolveFloorCommentRequestConfig = resolveFloorCommentRequestConfig;
+22
View File
@@ -0,0 +1,22 @@
// 歌曲评论
module.exports = (params, useAxios) => {
const paramsMap = {
mixsongid: params.mixsongid,
need_show_image: 1,
p: params.page || 1,
pagesize: params.pagesize || 30,
show_classify: params.show_classify || 1,
show_hotword_list: params.show_hotword_list || 1,
extdata: '0',
code: 'fc4be23b4e972707f36b8a828a93ba8a'
}
return useAxios({
url: '/mcomment/v1/cmtlist',
encryptType: 'android',
method: 'POST',
params: paramsMap,
cookie: params?.cookie || {},
});
};
@@ -0,0 +1,22 @@
// 歌曲评论-根据分类获取评论
module.exports = (params, useAxios) => {
const paramsMap = {
mixsongid: params.mixsongid,
need_show_image: 1,
page: params.page || 1,
pagesize: params.pagesize || 30,
type_id: params.type_id,
extdata: '0',
code: 'fc4be23b4e972707f36b8a828a93ba8a',
sort_method: Number(params.sort) === 2 ? params.sort : 1
}
return useAxios({
url: '/mcomment/v1/cmt_classify_list',
encryptType: 'android',
method: 'POST',
params: paramsMap,
cookie: params?.cookie || {},
});
};
@@ -0,0 +1,21 @@
// 歌曲评论-更具热词获取评论
module.exports = (params, useAxios) => {
const paramsMap = {
mixsongid: params.mixsongid,
need_show_image: 1,
p: params.page || 1,
pagesize: params.pagesize || 30,
hot_word: params.hot_word,
extdata: '0',
code: 'fc4be23b4e972707f36b8a828a93ba8a',
}
return useAxios({
url: '/mcomment/v1/get_hot_word',
encryptType: 'android',
method: 'POST',
params: paramsMap,
cookie: params?.cookie || {},
});
};
+23
View File
@@ -0,0 +1,23 @@
// 歌曲评论
module.exports = (params, useAxios) => {
const paramsMap = {
childrenid: params.id,
need_show_image: 1,
p: params.page || 1,
pagesize: params.pagesize || 30,
show_classify: params.show_classify || 1,
show_hotword_list: params.show_hotword_list || 1,
code: 'ca53b96fe5a1d9c22d71c8f522ef7c4f',
content_type: 0,
tag: 5
}
return useAxios({
url: '/m.comment.service/v1/cmtlist',
encryptType: 'android',
method: 'POST',
params: paramsMap,
cookie: params?.cookie || {},
});
};
+13
View File
@@ -0,0 +1,13 @@
module.exports = (params, useAxios) => {
return useAxios({
baseURL: 'https://acsing.service.kugou.com',
url: '/sing7/relation/json/v3/friend_rec_by_using_song_list',
encryptType: 'android',
method: 'POST',
data: {list: [{user_id: 853927886, mixsong_ids: [290083753,251724346,571554587,250126644,208831644,40328518,250504076,581706850,318347675,585258401,288481998,407414475,28239430,280584633,291957521,64556644,243149863,488725103,32114153,39951172,29019580,40397606,327507651,32029382,32218359,340353127,276448762,177071956,100031397,249251602]}]},
params: { channel: 130, isteen: 0, platform: 2, usemkv: 1 },
cookie: params?.cookie || {},
headers: { pid: 126556797 },
});
};
+20
View File
@@ -0,0 +1,20 @@
// mode list ,song
module.exports = (params, useAxios) => {
const paramsMap = {
mode: params.mode || 'list',
platform: params.platform || 'ios'
};
if (params.history_name) paramsMap['history_name'] = params.history_name;
if (params.date) paramsMap['date'] = params.date;
return useAxios({
url: '/everyday/api/v1/get_history',
encryptType: 'android',
method: 'POST',
params: paramsMap,
cookie: params?.cookie || {},
headers: { 'x-router': 'everydayrec.service.kugou.com' },
});
};
@@ -0,0 +1,10 @@
module.exports = (params, useAxios) => {
return useAxios({
url: '/everyday_song_recommend',
encryptType: 'android',
method: 'POST',
params: { platform: params.platform || 'ios' },
cookie: params?.cookie || {},
headers: { 'x-router': 'everydayrec.service.kugou.com' },
});
};
@@ -0,0 +1,14 @@
module.exports = (params, useAxios) => {
const dataMap = {
platform: params.platform || 'ios'
};
return useAxios({
url: '/everydayrec.service/everyday_style_recommend',
encryptType: 'android',
method: 'POST',
data: {},
params: { tagids: params.tagids ?? ''},
cookie: params?.cookie || {},
});
};
+9
View File
@@ -0,0 +1,9 @@
module.exports = (params, useAxios) => {
return useAxios({
url: `/count/v1/audio/mget_collect`,
method: 'GET',
encryptType: 'android',
cookie: params?.cookie || {},
params: { mixsongids: params.mixsongids },
});
};
+25
View File
@@ -0,0 +1,25 @@
const { appid, clientver, cryptoMd5, signParamsKey } = require('../util');
module.exports = (params, useAxios) => {
const dateTime = Date.now();
const userid = params?.cookie?.userid || params?.userid || 0;
const dataMap = {
kguid: userid,
clienttime: dateTime,
mid: params?.cookie?.KUGOU_API_MID,
platform: 'android',
clientver,
uid: userid,
get_tracker: 1,
key: signParamsKey(dateTime),
appid,
};
return useAxios({
url: '/v1/class_fm_song',
encryptType: 'android',
method: 'POST',
data: dataMap,
cookie: params?.cookie || {},
headers: { 'x-router': 'fm.service.kugou.com' },
});
};
+30
View File
@@ -0,0 +1,30 @@
const { appid, clientver, signParamsKey } = require('../util');
module.exports = (params, useAxios) => {
const dateTime = Date.now();
const dfid = params?.cookie?.dfid || params?.dfid || '-';
const userid = params?.cookie?.userid || params?.userid;
const token = params?.cookie?.token || params?.token;
const fmData = (params?.fmid || '').split(',').map((s) => ({ fields: 'imgUrl100,imgUrl50', fmid: s, fmtype: 2 }));
const dataMap = {
appid,
clienttime: dateTime,
clientver,
data: fmData,
dfid,
key: signParamsKey(dateTime),
mid: params?.cookie?.KUGOU_API_MID,
};
if (userid) dataMap['userid'] = userid;
if (token) dataMap['token'] = token;
return useAxios({
url: '/v1/fm_info',
encryptType: 'android',
method: 'POST',
data: dataMap,
cookie: params?.cookie || {},
headers: { 'x-router': 'fm.service.kugou.com', 'Content-Type': 'application/json' },
});
};
+26
View File
@@ -0,0 +1,26 @@
const { appid, clientver, signParamsKey } = require('../util');
module.exports = (params, useAxios) => {
const dateTime = Date.now();
const dfid = params?.cookie?.dfid || params?.dfid || '-';
const dataMap = {
appid,
clientver,
clienttime: dateTime,
mid: params?.cookie?.KUGOU_API_MID,
key: signParamsKey(dateTime),
rcmdsongcount: 1,
level: 0,
area_code: 1,
get_tracker: 1,
uid: 0,
};
return useAxios({
url: '/v1/rcmd_list',
encryptType: 'android',
method: 'POST',
data: dataMap,
cookie: params?.cookie || {},
headers: { 'x-router': 'fm.service.kugou.com' },
});
};
+41
View File
@@ -0,0 +1,41 @@
const { appid, clientver, signParamsKey } = require('../util');
module.exports = (params, useAxios) => {
const dateTime = Date.now();
const userid = params?.cookie?.userid || params?.userid;
const fmData = (params?.fmid || '').split(',').map((s) => ({
fmid: s,
fmtype: params?.type || 2,
offset: params?.offset || -1,
size: params?.size || 20,
singername: s.singername || '',
}));
// fmType 生成
(params?.fmtype || '').split(',').forEach((s, l) => (fmData[l].fmtype = s || fmData[l].fmtype));
(params?.fmoffset || '').split(',').forEach((s, l) => (fmData[l].offset = s || fmData[l].offset));
(params?.fmsize || '').split(',').forEach((s, l) => (fmData[l].size = s || fmData[l].size));
const dataMap = {
appid,
area_code: 1,
clienttime: dateTime,
clientver,
data: fmData,
get_tracker: 1,
key: signParamsKey(dateTime),
mid: params?.cookie?.KUGOU_API_MID,
uid: userid,
};
return useAxios({
url: '/v1/app_song_list_offset',
encryptType: 'android',
method: 'POST',
data: dataMap,
cookie: params?.cookie || {},
headers: { 'x-router': 'fm.service.kugou.com', 'Content-Type': 'application/json' },
});
};
+24
View File
@@ -0,0 +1,24 @@
// 获取音效详情
const { signParamsKey } = require('../util');
module.exports = (params, useAxios) => {
const dataMap = {
model_id: Number(params.model_id || 0),
req_src: 'collection',
earphone_vip: 1,
sound_ver: 2,
key: signParamsKey(Date.now()),
page: params.page || 1,
pagesize: params.pagesize || 30,
};
return useAxios({
baseURL: 'http://mobileservice.kugou.com',
url: '/api/v5/earphone/get_model_info',
encryptType: 'android',
method: 'GET',
params: dataMap,
cookie: params?.cookie || {},
});
};
+26
View File
@@ -0,0 +1,26 @@
// 获取社区音效
module.exports = (params, useAxios) => {
const dataMap = {
super_vip: 1,
sound_ver: 2,
page: params.page || 1,
pagesize: params.pagesize || 30,
apiver: 3,
classify: '2,3',
plat: 2,
privilege: 1,
sort: 2
};
return useAxios({
url: '/ocean/v6/sound/list',
encryptType: 'android',
method: 'GET',
params: dataMap,
cookie: params?.cookie || {},
});
};
+22
View File
@@ -0,0 +1,22 @@
// 获取验证码数据
module.exports = (params, useAxios) => {
const userid = Number(params?.userid || params?.cookie?.userid || '0');
const dataMap = {
eventid: params?.eventid,
userid,
platid: params?.platid || 2,
rtype: 1,
wasm: 1,
i: '',
sid: '',
edt: '',
};
return useAxios({
url: '/verifyservice/v3/get_verify_info',
encryptType: 'android',
method: 'POST',
data: dataMap,
cookie: params?.cookie || {},
});
};
+43
View File
@@ -0,0 +1,43 @@
const { signatureAndroidParams, appid, clientver } = require('../util');
module.exports = (params, useAxios) => {
const data = (params?.hash || '').split(',').map((s) => ({ album_id: 0, hash: s, album_audio_id: 0 }));
(params?.album_id || '').split(',').forEach((s, index) => {
if (index <= data.length - 1) {
data[index]['album_id'] = s || 0;
}
});
(params?.album_audio_id || '').split(',').forEach((s, index) => {
if (index <= data.length - 1) {
data[index]['album_audio_id'] = s || 0;
}
});
const paramsMap = {
album_image_type: '-3',
appid,
clientver,
author_image_type: '3,4,5',
count: params?.count || 5,
data,
isCdn: 1,
publish_time: 1,
};
const query = Object.keys(paramsMap)
.sort()
.map((s) => `${s}=${encodeURIComponent(typeof paramsMap[s] === 'object' ? JSON.stringify(paramsMap[s]) : paramsMap[s])}`);
const signature = signatureAndroidParams(paramsMap);
return useAxios({
baseURL: 'https://expendablekmr.kugou.com',
url: `/container/v2/image?${query.join('&')}`,
method: 'GET',
encryptType: 'android',
params: { signature },
cookie: params?.cookie || {},
notSign: true,
clearDefaultParams: true,
});
};
+47
View File
@@ -0,0 +1,47 @@
const { signatureAndroidParams, appid, clientver } = require('../util');
module.exports = (params, useAxios) => {
const data = (params?.hash || '').split(',').map((s) => ({ audio_id: 0, hash: s, album_audio_id: 0, filename: '' }));
(params?.audio_id || '').split(',').forEach((s, index) => {
if (index <= data.length - 1) {
data[index]['audio_id'] = s || 0;
}
});
(params?.album_audio_id || '').split(',').forEach((s, index) => {
if (index <= data.length - 1) {
data[index]['album_audio_id'] = s || 0;
}
});
(params?.filename || '').split(',').forEach((s, index) => {
if (index <= data.length - 1) {
data[index]['filename'] = s;
}
});
const paramsMap = {
appid,
clientver,
count: params?.count || 5,
data,
isCdn: 1,
publish_time: 1,
show_authors: 1,
};
const query = Object.keys(paramsMap)
.sort()
.map((s) => `${s}=${encodeURIComponent(typeof paramsMap[s] === 'object' ? JSON.stringify(paramsMap[s]) : paramsMap[s])}`);
const signature = signatureAndroidParams(paramsMap);
return useAxios({
baseURL: 'https://expendablekmr.kugou.com',
url: `/v2/author_image/audio?${query.join('&')}`,
method: 'GET',
encryptType: 'android',
params: { signature },
cookie: params?.cookie || {},
notSign: true,
clearDefaultParams: true,
});
};
+21
View File
@@ -0,0 +1,21 @@
// 根据 ip id 获取相对应的 歌曲/专辑/视频/歌手
module.exports = (params, useAxios) => {
const dataMap = {
is_publish: 1,
ip_id: params?.id,
sort: 3,
page: params?.page || 1,
pagesize: params?.pagesize || 30,
query: 1,
};
const type = ['audios', 'albums', 'videos', 'author_list'].includes(params?.type) ? params.type : 'audios';
return useAxios({
url: `/openapi/v1/ip/${type}`,
encryptType: 'android',
method: 'POST',
data: dataMap,
cookie: params?.cookie || {},
});
};
+17
View File
@@ -0,0 +1,17 @@
// 获取ip详情
module.exports = (params, useAxios) => {
const data = (params?.id || '').split(',').map((s) => ({ ip_id: s }));
const dataMap = {
data,
is_publish: 1,
};
return useAxios({
url: '/openapi/v1/ip',
encryptType: 'android',
method: 'POST',
data: dataMap,
cookie: params?.cookie || {},
});
};
+16
View File
@@ -0,0 +1,16 @@
// 根据 ip 获取相对于歌单
module.exports = (params, useAxios) => {
const dataMap = {
ip: params?.id,
page: params?.page || 1,
pagesize: params?.pagesize || 30,
};
return useAxios({
url: '/ocean/v6/pubsongs/list_info_for_ip',
encryptType: 'android',
method: 'POST',
params: dataMap,
cookie: params?.cookie || {},
});
};
+31
View File
@@ -0,0 +1,31 @@
// ip 专区
module.exports = (params, useAxios) => {
return new Promise((resolve, reject) => {
useAxios({
url: `/v1/zone/index`,
encryptType: 'android',
method: 'GET',
cookie: params?.cookie || {},
headers: { 'x-router': 'yuekucategory.kugou.com' },
})
.then((resp) => {
if (resp.body.status == 1) {
if (resp.body.data?.list) {
const list = Array.isArray(resp.body.data.list) ? resp.body.data?.list : [];
for (let index = 0; index < list.length; index += 1) {
if (list[index].special_link) {
const urls = new URLSearchParams(list[index].special_link);
if (urls.has('path')) {
const pathUrls = new URLSearchParams(urls.get('path') || '');
list[index]['ip_id'] = Number(pathUrls.get('ip_id') || '');
}
}
}
resp.body.data.list = list;
}
}
resolve(resp);
})
.catch((e) => reject(e));
});
};
+16
View File
@@ -0,0 +1,16 @@
// 获取今日推荐信息,有可能为空
module.exports = (params, useAxios) => {
const dataMap = {
id: params?.id,
share: 0,
};
return useAxios({
url: '/v1/zone/home',
encryptType: 'android',
method: 'GET',
params: dataMap,
cookie: params?.cookie || {},
headers: { 'x-router': 'yuekucategory.kugou.com' },
});
};
+20
View File
@@ -0,0 +1,20 @@
const { appid, clientver } = require('../util');
// 根据 album_audio_id/MixSongID 获取歌曲 相对应的 mv
module.exports = (params, useAxios) => {
const resource = (params?.album_audio_id || '').split(',').map((s) => ({ album_audio_id: s }));
const paramsMap = {
data: resource,
fields: params.fields || '',
};
return useAxios({
url: '/kmr/v1/audio/mv',
method: 'POST',
data: paramsMap,
encryptType: 'android',
cookie: params?.cookie || {},
headers: {'x-router': 'openapi.kugou.com', 'KG-TID': 38},
});
};
+18
View File
@@ -0,0 +1,18 @@
// 根据 album_audio_id/MixSongID 获取歌曲 相对应的 歌手/专辑/歌曲信息
module.exports = (params, useAxios) => {
const resource = (params?.album_audio_id || '').split(',').map((s) => ({ entity_id: Number(s) }));
const paramsMap = {
data: resource,
fields: params.fields || 'base',
};
return useAxios({
url: '/kmr/v2/audio',
method: 'POST',
data: paramsMap,
encryptType: 'android',
cookie: params?.cookie || {},
headers: {'x-router': 'openapi.kugou.com', 'KG-TID': 238},
});
};
@@ -0,0 +1,21 @@
// 获取继续播放信息
module.exports = (params, useAxios) => {
const token = params?.token || params.cookie?.token || '';
const userid = Number(params?.userid || params?.cookie?.userid || 0);
const paramsMap = {
area_code: '1',
sources: ['pc', 'mobile', 'tv', 'car'],
userid,
ret_info: 1,
token,
pagesize: Number(params.pagesize || 30)
}
return useAxios({
url: '/playque/devque/v1/get_latest_songs',
encryptType: 'android',
method: 'POST',
data: paramsMap,
cookie: params?.cookie || {},
});
};
+51
View File
@@ -0,0 +1,51 @@
const { cryptoAesDecrypt, cryptoAesEncrypt, cryptoRSAEncrypt } = require('../util');
module.exports = (params, useAxios) => {
const dateNow = Date.now();
const encrypt = cryptoAesEncrypt({ pwd: params?.password || '', code: params?.code || '', clienttime_ms: dateNow });
const dataMap = {
plat: 1,
support_multi: 1,
clienttime_ms: dateNow,
t1: '562a6f12a6e803453647d16a08f5f0c2ff7eee692cba2ab74cc4c8ab47fc467561a7c6b586ce7dc46a63613b246737c03a1dc8f8d162d8ce1d2c71893d19f1d4b797685a4c6d3d81341cbde65e488c4829a9b4d42ef2df470eb102979fa5adcdd9b4eecfea8b909ff7599abeb49867640f10c3c70fc444effca9d15db44a9a6c907731e2bb0f22cd9b3536380169995693e5f0e2424e3378097d3813186e3fe96bbe7023808a0981b4e2b6135a76faac',
t2: '31c4daf4cf480169ccea1cb7d4a209295865a9d2b788510301694db229b87807469ea0d41b4d4b9173c2151da7294aeebfc9738df154bbdf11a4e117bb5dff6a3af8ce5ce333e681c1f29a44038f27567d58992eb81283e080778ac77db1400fdf49b7cf7e26be2e5af4da7830cc3be4',
t3: 'MCwwLDAsMCwwLDAsMCwwLDA=',
username: params?.username,
params: encrypt.str,
pk: cryptoRSAEncrypt({ 'clienttime_ms': dateNow, key: encrypt.key }).toUpperCase(),
};
return new Promise((resolve, reject) => {
useAxios({
url: '/v9/login_by_pwd',
method: 'POST',
data: dataMap,
encryptType: 'android',
cookie: params?.cookie || {},
headers: { 'x-router': 'login.user.kugou.com' },
})
.then((res) => {
const { body } = res;
if (body?.status && body?.status === 1) {
if (body?.data?.secu_params) {
const getToken = cryptoAesDecrypt(body.data.secu_params, encrypt.key);
if (typeof getToken === 'object') {
res.body.data = { ...body.data, ...getToken };
Object.keys(getToken).forEach((key) => res.cookie.push(`${key}=${getToken[key]}`));
} else {
res.body.data['token'] = getToken;
res.cookie.push(`token=${getToken}`);
}
res.cookie.push(`userid=${res.body.data?.userid || 0}`);
res.cookie.push(`vip_type=${res.body.data?.vip_type || 0}`);
res.cookie.push(`vip_token=${res.body.data?.vip_token || ''}`);
resolve(res);
return;
}
}
resolve(res);
})
.catch((e) => reject(e));
});
};
+76
View File
@@ -0,0 +1,76 @@
// 手机登录
const { cryptoAesDecrypt, cryptoAesEncrypt, cryptoRSAEncrypt, signParamsKey, isLite, randomString } = require('../util');
let liteT2Key = 'fd14b35e3f81af3817a20ae7adae7020';
let liteT2Iv = '17a20ae7adae7020';
let liteT1Key = '5e4ef500e9597fe004bd09a46d8add98';
let liteT1Iv = '04bd09a46d8add98';
module.exports = (params, useAxios) => {
const dateTime = Date.now();
const encrypt = cryptoAesEncrypt({ mobile: params?.mobile || '', code: params?.code || '' });
const mobile = params?.mobile && `${params.mobile.toString().substring(0, 2)}*****${params.mobile.toString().substring(10, 11)}`;
const dfid = params?.cookie?.dfid ?? randomString(24);
const t2 = cryptoAesEncrypt(
`${params.cookie?.KUGOU_API_GUID}|0f607264fc6318a92b9e13c65db7cd3c|${params.cookie?.KUGOU_API_MAC}|${params.cookie?.KUGOU_API_DEV}|${dateTime}`,
{ key: liteT2Key, iv: liteT2Iv }
);
const t1 = cryptoAesEncrypt(`|${dateTime}`, { key: liteT1Key, iv: liteT1Iv });
let dataMap = {
plat: 1,
support_multi: 1,
t1: isLite ? t1 : 0,
t2: isLite ? t2 : 0,
clienttime_ms: dateTime,
mobile,
key: signParamsKey(dateTime),
pk: cryptoRSAEncrypt({ 'clienttime_ms': dateTime, key: encrypt.key }).toUpperCase(),
params: encrypt.str,
};
if (params?.userid) dataMap['userid'] = params.userid;
if (isLite) {
dataMap['dfid'] = dfid;
dataMap['dev'] = params.cookie?.KUGOU_API_DEV;
dataMap['gitversion'] = '5f0b7c4';
} else {
dataMap['t3'] = 'MCwwLDAsMCwwLDAsMCwwLDA=';
}
return new Promise((resolve, reject) => {
useAxios({
baseURL: 'https://loginserviceretry.kugou.com',
url: `/v7/login_by_verifycode`,
method: 'POST',
data: dataMap,
encryptType: 'android',
headers: {
'support-calm': '1',
'User-Agent': 'Android16-1070-11440-130-0-LOGIN-wifi',
},
cookie: params?.cookie || {},
})
.then((res) => {
const { body } = res;
if (body?.status && body?.status === 1) {
if (body?.data?.secu_params) {
const getToken = cryptoAesDecrypt(body.data.secu_params, encrypt.key);
if (typeof getToken === 'object') {
res.body.data = { ...body.data, ...getToken };
Object.keys(getToken).forEach((key) => res.cookie.push(`${key}=${getToken[key]}`));
} else {
res.body.data['token'] = getToken;
}
}
res.cookie.push(`t1=${res.body.data['t1']}`);
res.cookie.push(`token=${res.body.data['token']}`);
res.cookie.push(`userid=${res.body.data?.userid || 0}`);
res.cookie.push(`vip_type=${res.body.data?.vip_type || 0}`);
res.cookie.push(`vip_token=${res.body.data?.vip_token || ''}`);
}
resolve(res);
})
.catch((e) => reject(e));
});
};
+22
View File
@@ -0,0 +1,22 @@
const { cryptoAesEncrypt, cryptoRSAEncrypt } = require('../util');
module.exports = (params, useAxios) => {
const clienttime_ms = parseInt(new Date().getTime());
const encrypt = cryptoAesEncrypt({ token: params.token || params.cookie?.token });
const dataMap = {
plat: 1,
userid: params.userid || params.cookie?.userid || 0,
clienttime_ms,
pk: cryptoRSAEncrypt({ clienttime_ms, key: encrypt.key }).toUpperCase(),
params: encrypt.str,
};
return useAxios({
baseURL: 'https://userinfoservice.kugou.com',
url: '/v2/get_dev',
encryptType: 'android',
method: 'POST',
data: dataMap,
cookie: params?.cookie || {},
});
};
+34
View File
@@ -0,0 +1,34 @@
//登出选定设备
const { cryptoAesEncrypt, cryptoRSAEncrypt, appid, clientver, srcappid,signParamsKey } = require('../util');
module.exports = (params, useAxios) => {
const clienttime_ms = parseInt(new Date().getTime());
const encrypt = cryptoAesEncrypt({ token: params.token || params.cookie?.token });
const mid = calculateMid(params.mid)
const dateTime = Date.now();
const dataMap = {
appid,
clientver,
clienttime: clienttime_ms,
mid: mid,
uuid,
dfid,
plat: 1,
userid,
token: encrypt,
t_mid :guid,
t : dateTime,
t_appid: 3116,
t_clientver: 10597,
srcappid,
signature: signParamsKey(dateTime),
};
return useAxios({
url: '/loginservice/v1/dev_logout',
encryptType: 'android',
method: 'GET',
data: dataMap,
cookie: params?.cookie,
headers: { 'Host':'gateway.kugou.com'}
});
};
+84
View File
@@ -0,0 +1,84 @@
// 开放平台登录
const axios = require('axios');
const { wx_appid, wx_secret, cryptoAesDecrypt, cryptoAesEncrypt, cryptoRSAEncrypt, wx_lite_appid, wx_lite_secret, isLite } = require('../util');
const appid = isLite ? wx_lite_appid : wx_appid;
const secret = isLite ? wx_lite_secret : wx_secret;
let liteT2Key = 'fd14b35e3f81af3817a20ae7adae7020';
let liteT2Iv = '17a20ae7adae7020';
let liteT1Key = '5e4ef500e9597fe004bd09a46d8add98';
let liteT1Iv = '04bd09a46d8add98';
const assetsToken = (code) => {
return axios({
url: 'https://api.weixin.qq.com/sns/oauth2/access_token',
method: 'POST',
params: { secret, appid, code, grant_type: 'authorization_code' },
});
};
module.exports = (params, useAxios) => {
const answer = { status: 500, body: {}, cookie: [] };
return new Promise(async (resolve, reject) => {
try {
const assetsTokenResp = await assetsToken(params?.code || '');
if (assetsTokenResp.data?.access_token && assetsTokenResp.data?.openid) {
const dateNow = Date.now();
const encrypt = cryptoAesEncrypt({ access_token: assetsTokenResp.data.access_token });
const pk = cryptoRSAEncrypt({ 'clienttime_ms': dateNow, key: encrypt.key }).toUpperCase();
const t2 = cryptoAesEncrypt(
`${params.cookie?.KUGOU_API_GUID}|0f607264fc6318a92b9e13c65db7cd3c|${params.cookie?.KUGOU_API_MAC}|${params.cookie?.KUGOU_API_DEV}|${dateNow}`,
{ key: liteT2Key, iv: liteT2Iv }
);
const t1 = cryptoAesEncrypt(`|${dateNow}`, { key: liteT1Key, iv: liteT1Iv });
const dataMap = {
dev: params.cookie?.KUGOU_API_DEV,
force_login: 1,
partnerid: 36,
clienttime_ms: dateNow,
t1: isLite ? t1 : 0,
t2: isLite ? t2 : 0,
t3: 'MCwwLDAsMCwwLDAsMCwwLDA=',
openid: assetsTokenResp.data.openid,
params: encrypt.str,
pk,
};
const response = await useAxios({
url: `/v6/login_by_openplat`,
method: 'POST',
data: dataMap,
cookie: params?.cookie,
encryptType: 'android',
headers: { 'x-router': 'login.user.kugou.com' },
});
if (response.body?.status === 1) {
const getToken = cryptoAesDecrypt(response.body.data?.secu_params, encrypt.key);
if (typeof getToken === 'object') {
response.body.data = { ...response.body.data, ...getToken };
Object.keys(getToken).forEach((key) => response.cookie.push(`${key}=${getToken[key]}`));
} else {
response.body.data['token'] = getToken;
response.cookie.push(`token=${getToken}`);
}
response.cookie.push(`t1=${response.body.data?.t1 ?? ''}`);
response.cookie.push(`userid=${response.body.data?.userid || 0}`);
response.cookie.push(`vip_type=${response.body.data?.vip_type || 0}`);
response.cookie.push(`vip_token=${response.body.data?.vip_token || ''}`);
}
resolve(response);
} else {
answer.status = 502;
answer.body = { status: 0, msg: assetsTokenResp.data };
reject(answer);
}
} catch (error) {
answer.status = 502;
answer.body = { status: 0, msg: error };
reject(answer);
}
});
};
+22
View File
@@ -0,0 +1,22 @@
const { srcappid, appid } = require('../util');
// 酷狗二维码状态检测
// 0 为二维码过期,1 为等待扫码,2 为待确认,4 为授权登录成功(4 状态码下会返回 token)
module.exports = (params, useAxios) => {
return new Promise((resolve, reject) => {
useAxios({
baseURL: 'https://login-user.kugou.com',
url: '/v2/get_userinfo_qrcode',
method: 'GET',
params: { plat: 4, appid, srcappid, qrcode: params?.key },
encryptType: 'web',
cookie: params?.cookie || {},
}).then(resp => {
if (resp.body?.data?.status == 4) {
resp.cookie.push(`token=${resp.body?.data?.token}`);
resp.cookie.push(`userid=${resp.body?.data?.userid}`);
}
resolve(resp);
}).catch(e => reject(e));
});
};
+19
View File
@@ -0,0 +1,19 @@
const qrcode = require('qrcode');
// 酷狗二维码生成
module.exports = (params, useAxios) => {
return new Promise(async (resolve) => {
const url = `https://h5.kugou.com/apps/loginQRCode/html/index.html?qrcode=${params.key}`
return resolve({
code: 200,
status: 200,
body: {
code: 200,
data: {
url: url,
base64: params?.qrimg ? await qrcode.toDataURL(url) : '',
},
},
})
})
}
+19
View File
@@ -0,0 +1,19 @@
const { srcappid, appid } = require('../util');
// 二维码 key 生成接口
module.exports = (params, useAxios) => {
return useAxios({
baseURL: 'https://login-user.kugou.com',
url: '/v2/qrcode',
method: 'GET',
params: {
appid: params?.type === 'web' ? 1014 : 1001,
type: 1,
plat: 4,
qrcode_txt: `https://h5.kugou.com/apps/loginQRCode/html/index.html?appid=${appid}&`,
srcappid,
},
encryptType: 'web',
cookie: params?.cookie || {},
});
};
+79
View File
@@ -0,0 +1,79 @@
// 刷新登录
const { cryptoAesDecrypt, cryptoAesEncrypt, cryptoRSAEncrypt, isLite } = require('../util');
const key = '90b8382a1bb4ccdcf063102053fd75b8';
const iv = 'f063102053fd75b8';
const liteKey = 'c24f74ca2820225badc01946dba4fdf7';
const liteIv = 'adc01946dba4fdf7';
let liteT2Key = 'fd14b35e3f81af3817a20ae7adae7020';
let liteT2Iv = '17a20ae7adae7020';
let liteT1Key = '5e4ef500e9597fe004bd09a46d8add98';
let liteT1Iv = '04bd09a46d8add98';
module.exports = (params, useAxios) => {
const dateNow = Date.now();
const token = params?.token || params?.cookie?.token || '';
const userid = params?.userid || params?.cookie?.userid || '0';
const encrypt = cryptoAesEncrypt({ clienttime: Math.floor(dateNow / 1000), token }, { key: isLite ? liteKey : key, iv: isLite ? liteIv : iv });
const encryptParams = cryptoAesEncrypt({});
const pk = cryptoRSAEncrypt({ clienttime_ms: dateNow, key: encryptParams.key });
const t2 = cryptoAesEncrypt(
`${params.cookie?.KUGOU_API_GUID}|0f607264fc6318a92b9e13c65db7cd3c|${params.cookie?.KUGOU_API_MAC}|${params.cookie?.KUGOU_API_DEV}|${dateNow}`,
{ key: liteT2Key, iv: liteT2Iv }
);
const t1 = cryptoAesEncrypt(params.cookie?.t1 ? `${params.cookie?.t1}|${dateNow}` : `|${dateNow}`, { key: liteT1Key, iv: liteT1Iv });
const dataMap = {
dfid: params?.cookie?.dfid || '-',
p3: encrypt,
plat: 1,
t1: isLite ? t1 : 0,
t2: isLite ? t2 : 0,
t3: 'MCwwLDAsMCwwLDAsMCwwLDA=',
pk,
params: encryptParams.str,
userid,
clienttime_ms: dateNow,
};
if (isLite) {
dataMap['dev'] = params.cookie?.KUGOU_API_DEV;
}
return new Promise((resolve, reject) => {
useAxios({
baseURL: 'http://login.user.kugou.com',
url: `/v5/login_by_token`,
method: 'POST',
data: dataMap,
cookie: params?.cookie,
encryptType: 'android',
})
.then((res) => {
const { body } = res;
if (body?.status && body?.status === 1) {
if (body?.data?.secu_params) {
const getToken = cryptoAesDecrypt(body.data.secu_params, encryptParams.key);
if (typeof getToken === 'object') {
res.body.data = { ...body.data, ...getToken };
Object.keys(getToken).forEach((key) => res.cookie.push(`${key}=${getToken[key]}`));
} else {
res.body.data['token'] = getToken;
}
}
res.cookie.push(`t1=${res.body.data['t1']}`);
res.cookie.push(`token=${res.body.data['token']}`);
res.cookie.push(`userid=${res.body.data?.userid || 0}`);
res.cookie.push(`vip_type=${res.body.data?.vip_type || 0}`);
res.cookie.push(`vip_token=${res.body.data?.vip_token || ''}`);
}
resolve(res);
})
.catch((e) => reject(e));
});
};
+20
View File
@@ -0,0 +1,20 @@
const axios = require('axios');
module.exports = (params, useAxios) => {
const answer = { status: 500, body: {}, cookie: [] };
return new Promise(async (resolve, reject) => {
try {
const resp = await axios({ url: `https://long.open.weixin.qq.com/connect/l/qrconnect?f=json&uuid=${params?.uuid || ''}` });
answer.status = 200;
answer.body = resp.data;
resolve(answer);
} catch (err) {
answer.status = 502;
answer.body = { status: 0, msg: err };
reject(answer);
}
});
}
+61
View File
@@ -0,0 +1,61 @@
const axios = require('axios');
const { wx_appid, wx_secret, cryptoMd5, cryptoSha1, randomString, wx_lite_appid, wx_lite_secret, isLite } = require('../util');
const appid = isLite ? wx_lite_appid : wx_appid;
const secret = isLite ? wx_lite_secret : wx_secret;
const accessToken = () => {
return axios({ url: 'https://api.weixin.qq.com/cgi-bin/token', params: { appid, secret, grant_type: 'client_credential' } });
};
/**
*
* @param {string} accessToken
* @returns
*/
const ticket = (accessToken) => axios({ url: 'https://api.weixin.qq.com/cgi-bin/ticket/getticket', params: { access_token: accessToken, type: 2 } });
module.exports = (params, useAxios) => {
const answer = { status: 500, body: {}, cookie: [] };
return new Promise(async (resolve, reject) => {
try {
const accessTokenResp = await accessToken();
if (accessTokenResp.data?.access_token) {
const ticketResp = await ticket(accessTokenResp.data.access_token);
if (ticketResp.data?.errcode === 0) {
const ticket = ticketResp.data.ticket;
const timestamp = Date.now();
const noncestr = cryptoMd5(randomString());
const signaturePrams = `appid=${appid}&noncestr=${noncestr}&sdk_ticket=${ticket}&timestamp=${timestamp}`;
const signature = cryptoSha1(signaturePrams);
const params = { appid: appid, noncestr, timestamp, scope: 'snsapi_userinfo', signature };
const connect = await axios({ url: 'https://open.weixin.qq.com/connect/sdk/qrconnect', params });
if (connect.data?.errcode === 0) {
answer.status = 200;
connect.data.qrcode['qrcodeurl'] = `https://open.weixin.qq.com/connect/confirm?uuid=${connect.data.uuid}`;
answer.body = connect.data;
resolve(answer);
} else {
answer.status = 502;
answer.body = { status: 0, msg: connect.data };
reject(answer);
}
} else {
answer.status = 502;
answer.body = { status: 0, msg: ticketResp.data };
reject(answer);
}
} else {
answer.status = 502;
answer.body = { status: 0, msg: accessTokenResp.data };
reject(answer);
}
} catch (e) {
answer.status = 502;
answer.body = { status: 0, msg: e };
reject(answer);
}
});
};
@@ -0,0 +1,16 @@
module.exports = (params, useAxios) => {
return useAxios({
url: `/longaudio/v2/album_audios`,
method: 'post',
encryptType: 'android',
data: {
album_id: params.album_id,
area_code: 1,
tagid: 0,
page: params.page || 1,
pagesize: params.pagesize || 30,
},
cookie: params?.cookie || {},
headers: { 'x-router': 'openapi.kugou.com', 'KG-TID': '78' },
});
};
@@ -0,0 +1,15 @@
module.exports = (params, useAxios) => {
const data = (params.album_id || '').split(',').map((s) => ({ album_id: s }));
return useAxios({
url: `/openapi/v2/broadcast`,
method: 'post',
encryptType: 'android',
data: {
data,
show_album_tag: 1,
fields: "album_name,album_id,category,authors,sizable_cover,intro,author_name,trans_param,album_tag,mix_intro,full_intro,is_publish"
},
cookie: params?.cookie || {},
headers: {'KG-TID': '78'}
});
};
@@ -0,0 +1,9 @@
module.exports = (params, useAxios) => {
return useAxios({
url: `/longaudio/v1/home_new/daily_recommend`,
method: 'post',
encryptType: 'android',
params: { module_id: 1, size: params.pagesize || 30, page: params.page || 1 },
cookie: params?.cookie || {},
});
};
@@ -0,0 +1,9 @@
module.exports = (params, useAxios) => {
return useAxios({
url: `/longaudio/v1/home_new/rank_card_recommend`,
method: 'get',
encryptType: 'android',
params: {platform: 'ios'},
cookie: params?.cookie || {},
});
};
@@ -0,0 +1,10 @@
module.exports = (params, useAxios) => {
return useAxios({
url: `/longaudio/v1/home_new/vip_select_recommend`,
method: 'post',
encryptType: 'android',
data: {album_playlist: []},
params: {position: '2', clientver: 12329},
cookie: params?.cookie || {},
});
};
@@ -0,0 +1,10 @@
module.exports = (params, useAxios) => {
return useAxios({
url: `/longaudio/v1/home_new/week_new_albums_recommend`,
method: 'post',
encryptType: 'android',
data: {album_playlist: []},
params: {clientver: 12329},
cookie: params?.cookie || {},
});
};
+35
View File
@@ -0,0 +1,35 @@
// 歌词获取
const { decodeLyrics } = require('../util');
module.exports = (params, useAxios) => {
const dataMap = {
ver: 1,
client: params?.client || 'android',
id: params?.id,
accesskey: params?.accesskey,
fmt: params.fmt || 'krc',
charset: 'utf8',
};
return new Promise((resolve, reject) => {
useAxios({
baseURL: 'https://lyrics.kugou.com',
url: '/download',
method: 'GET',
params: dataMap,
cookie: params?.cookie || {},
encryptType: 'android',
})
.then((res) => {
if (params?.decode) {
if (res.body?.content) {
res.body['decodeContent'] = params?.fmt == 'lrc' || Number(res.body?.contenttype) !== 0 ? Buffer.from(res.body?.content, 'base64').toString() : decodeLyrics(res.body.content);
resolve(res);
return;
}
}
resolve(res);
})
.catch((e) => reject(e));
});
};
+17
View File
@@ -0,0 +1,17 @@
// 电台 banner
module.exports = (params, useAxios) => {
const userid = params?.cookie?.userid || params?.userid || 0;
const dataMap = {
isvip: 0,
userid,
vipType: 0,
};
return useAxios({
baseURL: 'https://adservice.kugou.com',
url: '/v3/pc_diantai',
data: dataMap,
method: 'post',
encryptType: 'android',
cookie: params?.cookie || {},
});
};
+46
View File
@@ -0,0 +1,46 @@
const { signParamsKey, appid, clientver } = require('../util');
module.exports = (params, useAxios) => {
const userid = params?.cookie?.userid || params?.userid || 0;
const token = params?.cookie?.token || params?.token || 0;
const vip_type = params?.cookie?.vip_type || params?.vipType || 0;
const dateTime = Date.now();
const dataMap = {
appid,
clienttime: dateTime,
mid: params?.cookie?.KUGOU_API_MID,
action: params?.action || 'play',
recommend_source_locked: 0,
song_pool_id: Number(params?.song_pool_id || 0),
callerid: 0,
m_type: 1,
platform: params?.platform || 'ios',
area_code: 1,
remain_songcnt: Number(params?.remain_songcnt || 0),
clientver,
is_overplay: params?.is_overplay ? 1 : 0,
mode: params?.mode || 'normal',
fakem: 'ca981cfc583a4c37f28d2d49000013c16a0a',
key: signParamsKey(dateTime),
};
if (userid) {
dataMap['userid'] = userid;
dataMap['kguid'] = userid;
}
if (token) dataMap['token'] = token;
if (vip_type) dataMap['vip_type'] = vip_type;
if (params?.hash) dataMap['hash'] = params.hash;
if (params?.songid) dataMap['songid'] = params.songid;
if (params?.playtime) dataMap['playtime'] = params.playtime;
return useAxios({
url: '/v2/personal_recommend',
data: dataMap,
method: 'post',
encryptType: 'android',
cookie: params?.cookie || {},
headers: { 'x-router': 'persnfm.service.kugou.com' },
});
};
@@ -0,0 +1,17 @@
// 提交听歌历史
module.exports = (params, useAxios) => {
const userid = params?.userid || params?.cookie?.userid || 0;
const token = params?.token || params?.cookie?.token || '';
const songs = [{ mxid: Number(params.mxid), op: 1, ot: Number(params.time || Math.floor(Date.now() / 1000)), pc: Number(params.pc || 1) }];
const dataMap = { songs, token, userid };
return useAxios({
url: '/playhistory/v1/upload_songs',
data: dataMap,
encryptType: 'android',
method: 'POST',
params: { plat: 3 },
cookie: params?.cookie || {},
});
};
+33
View File
@@ -0,0 +1,33 @@
// 收藏歌单
module.exports = (params, useAxios) => {
const userid = params?.userid || params?.cookie?.userid || 0;
const token = params?.token || params.cookie?.token || '';
const clienttime = Math.floor(Date.now() / 1000);
const dataMap = {
userid,
token,
total_ver: 0,
name: params.name,
type: params.type || 0,
source: params.source === 0 ? 0 : params.source || 1,
is_pri: 0,
list_create_userid: params.list_create_userid,
list_create_listid: params.list_create_listid,
list_create_gid: params.list_create_gid || '',
from_shupinmv: 0,
};
if (params.type === 0) {
dataMap['is_pri'] = params.is_pri || 0;
}
return useAxios({
url: '/cloudlist.service/v5/add_list',
data: dataMap,
params: params.type === 0 ? { last_time: clienttime, last_area: 'gztx', userid, token } : {},
method: 'post',
encryptType: 'android',
cookie: params?.cookie || {},
});
};
+52
View File
@@ -0,0 +1,52 @@
const { playlistAesEncrypt, playlistAesDecrypt, rsaEncrypt2, signParamsKey, clientver, appid } = require('../util');
// 取消收藏歌单
module.exports = (params, useAxios) => {
const answer = { status: 500, body: {}, cookie: [] };
return new Promise(async (resolve) => {
try {
const userid = params?.userid || params?.cookie?.userid || 0;
const token = params?.token || params.cookie?.token || '';
const clienttime = Math.floor(Date.now() / 1000);
const dataMap = {
listid: Number(params.listid),
total_ver: 0,
type: 1,
};
const aesEncrypt = playlistAesEncrypt(dataMap);
const p = rsaEncrypt2({ aes: aesEncrypt.key, uid: userid, token }).toUpperCase();
const paramsMap = {
clienttime,
key: signParamsKey(clienttime.toString()),
last_area: 'gztx',
clientver,
appid,
last_time: clienttime,
p,
};
const respone = await useAxios({
url: '/v2/delete_list',
params: paramsMap,
data: aesEncrypt.str,
method: 'post',
encryptType: 'android',
headers: { 'x-router': 'cloudlist.service.kugou.com' },
responseType: 'arraybuffer',
cookie: params?.cookie || {},
});
respone.body = playlistAesDecrypt({ str: respone.body.toString('base64'), key: aesEncrypt.key });
resolve(respone);
} catch (error) {
console.log(error);
answer.body = error;
resolve(answer);
}
});
};
+22
View File
@@ -0,0 +1,22 @@
// 获取歌单详情
module.exports = (params, useAxios) => {
const data = (params?.ids || '').split(',').map(s => ({'global_collection_id': s }));
const dataMap = {
data,
userid: params?.userid || params?.cookie?.userid || 0,
token: params?.token || params?.cookie?.token || ''
};
return useAxios({
url: '/v3/get_list_info',
method: 'POST',
encryptType: 'android',
data: dataMap,
cookie: params?.cookie || {},
headers: {'x-router': 'pubsongs.kugou.com'}
})
}
+16
View File
@@ -0,0 +1,16 @@
// 获取音效歌单
module.exports = (params, useAxios) => {
const dataMap = {
page: params?.page || 1,
pagesize: params?.pagesize || 30,
};
return useAxios({
url: '/pubsongs/v1/get_sound_effect_list',
method: 'POST',
encryptType: 'android',
data: dataMap,
cookie: params?.cookie || {},
});
};
+26
View File
@@ -0,0 +1,26 @@
const { appid, clientver, signParamsKey } = require('../util');
module.exports = (params, useAxios) => {
const data = (params?.ids || '').split(',').map((s) => ({ 'global_collection_id': s }));
const clienttime = Date.now();
const dataMap = {
appid,
clientver,
clienttime,
key: signParamsKey(clienttime),
userid: params?.userid || params?.cookie?.userid || 0,
ugc: 1,
show_list: 1,
need_songs: 1,
data,
};
return useAxios({
url: '/pubsongs/v1/kmr_get_similar_lists',
method: 'POST',
encryptType: 'android',
data: dataMap,
cookie: params?.cookie || {},
});
};
+17
View File
@@ -0,0 +1,17 @@
// 获取歌单分类
module.exports = (params, useAxios) => {
const dataMap = {
tag_type: 'collection',
tag_id: 0,
source: 3,
};
return useAxios({
url: '/pubsongs/v1/get_tags_by_type',
method: 'POST',
encryptType: 'android',
data: dataMap,
cookie: params?.cookie || {},
});
};

Some files were not shown because too many files have changed in this diff Show More