Compare commits

...

No commits in common. 'd13c1129dec7c62db0fb2041a4fd06b10dba93a6' and '6d8850520470f6196eb83710b1e841f6f7d55df8' have entirely different histories.

@ -0,0 +1,16 @@
node_modules
npm-debug.log
Dockerfile*
docker-compose*
.dockerignore
.git
.github
.gitignore
README.md
LICENSE
.vscode
dist
dist_electron
build
images
script

@ -0,0 +1,8 @@
root = true
[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = false

7
.gitattributes vendored

@ -0,0 +1,7 @@
* text eol=lf
# Denote all files that are truly binary and should not be modified.
*.png binary
*.jpg binary
*.mp3 binary
*.icns binary
*.gif binary

@ -0,0 +1,21 @@
---
name: 反馈问题或请求新功能
about: bug & feature
title: ''
labels: ''
assignees: ''
---
# 尽量每个 issue 只提一个 bug 或新功能
### 提新 issue 前请确认 👉
- 没人提过这个 issue[这里看所有 issue](https://github.com/qier222/YesPlayMusic/issues)
- 项目的 Todo 里没有与你 issue 相关的内容([这里看 Todo](https://github.com/qier222/YesPlayMusic/projects/1)
### 反馈 bug 需要的信息
- 用的是网页版还是客户端
- 浏览器名称或电脑操作系统
- 控制台 Console 页面的截图(按 F12 可打开控制台)

@ -0,0 +1,116 @@
name: Release
env:
YARN_INSTALL_NOPT: yarn add --ignore-platform --ignore-optional
on:
push:
branches:
- master
tags:
- v*
workflow_dispatch:
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, windows-latest, ubuntu-18.04]
steps:
- name: Check out Git repository
uses: actions/checkout@v3
with:
submodules: "recursive"
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v3
with:
node-version: 16
cache: 'yarn'
- name: Install RPM & Pacman (on Ubuntu)
if: runner.os == 'Linux'
run: |
sudo apt-get update &&
sudo apt-get install --no-install-recommends -y rpm &&
sudo apt-get install --no-install-recommends -y bsdtar &&
sudo apt-get install --no-install-recommends -y libopenjp2-tools
- name: Install Snapcraft (on Ubuntu)
uses: samuelmeuli/action-snapcraft@v1
if: startsWith(matrix.os, 'ubuntu')
with:
snapcraft_token: ${{ secrets.snapcraft_token }}
- id: get_unm_version
name: Get the installed UNM version
run: |
yarn --ignore-optional
unm_version=$(node -e "console.log(require('./node_modules/@unblockneteasemusic/rust-napi/package.json').version)")
echo "::set-output name=unmver::${unm_version}"
shell: bash
- name: Install UNM dependencies for Windows
if: runner.os == 'Windows'
run: |
${{ env.YARN_INSTALL_NOPT }} \
@unblockneteasemusic/rust-napi-win32-x64-msvc@${{steps.get_unm_version.outputs.unmver}}
shell: bash
- name: Install UNM dependencies for macOS
if: runner.os == 'macOS'
run: |
${{ env.YARN_INSTALL_NOPT }} \
@unblockneteasemusic/rust-napi-darwin-x64@${{steps.get_unm_version.outputs.unmver}} \
@unblockneteasemusic/rust-napi-darwin-arm64@${{steps.get_unm_version.outputs.unmver}} \
dmg-license
shell: bash
- name: Install UNM dependencies for Linux
if: runner.os == 'Linux'
run: |
${{ env.YARN_INSTALL_NOPT }} \
@unblockneteasemusic/rust-napi-linux-x64-gnu@${{steps.get_unm_version.outputs.unmver}} \
@unblockneteasemusic/rust-napi-linux-arm64-gnu@${{steps.get_unm_version.outputs.unmver}} \
@unblockneteasemusic/rust-napi-linux-arm-gnueabihf@${{steps.get_unm_version.outputs.unmver}}
shell: bash
- name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1.6.0
env:
VUE_APP_ELECTRON_API_URL: /api
VUE_APP_ELECTRON_API_URL_DEV: /api
# VUE_APP_ELECTRON_API_URL_DEV: http://127.0.0.1:10754
VUE_APP_LASTFM_API_KEY: 09c55292403d961aa517ff7f5e8a3d9c
VUE_APP_LASTFM_API_SHARED_SECRET: 307c9fda32b3904e53654baff215cb67
with:
# GitHub token, automatically provided to the action
# (No need to define this secret in the repo settings)
github_token: ${{ secrets.github_token }}
# If the commit is tagged with a version (e.g. "v1.0.0"),
# release the app after building
release: ${{ startsWith(github.ref, 'refs/tags/v') }}
use_vue_cli: true
- uses: actions/upload-artifact@v3
with:
name: YesPlayMusic-mac
path: dist_electron/*-universal.dmg
if-no-files-found: ignore
- uses: actions/upload-artifact@v3
with:
name: YesPlayMusic-win
path: dist_electron/*Setup*.exe
if-no-files-found: ignore
- uses: actions/upload-artifact@v3
with:
name: YesPlayMusic-linux
path: dist_electron/*.AppImage
if-no-files-found: ignore

34
.gitignore vendored

@ -0,0 +1,34 @@
.DS_Store
node_modules
/dist
# local env files
.env
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.vercel
#Electron-builder output
/dist_electron
NeteaseCloudMusicApi-master
NeteaseCloudMusicApi-master.zip
# Local Netlify folder
.netlify
vercel.json

@ -0,0 +1 @@
14

@ -0,0 +1,3 @@
build
coverage
dist

@ -0,0 +1,12 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"jsxSingleQuote": true,
"arrowParens": "avoid",
"endOfLine": "lf",
"bracketSpacing": true,
"htmlWhitespaceSensitivity": "strict"
}

@ -0,0 +1,22 @@
FROM node:16.13.1-alpine as build
ENV VUE_APP_NETEASE_API_URL=/api
WORKDIR /app
RUN apk add --no-cache python3 make g++ git
COPY package.json yarn.lock ./
RUN yarn install
COPY . .
RUN yarn build
FROM nginx:1.20.2-alpine as app
COPY --from=build /app/package.json /usr/local/lib/
RUN apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/v3.14/main libuv \
&& apk add --no-cache --update-cache --repository http://dl-cdn.alpinelinux.org/alpine/v3.14/main nodejs npm \
&& npm i -g $(awk -F \" '{if($2=="NeteaseCloudMusicApi") print $2"@"$4}' /usr/local/lib/package.json) \
&& rm -f /usr/local/lib/package.json
COPY --from=build /app/docker/nginx.conf.example /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist /usr/share/nginx/html
CMD nginx ; exec npx NeteaseCloudMusicApi

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020-2023 qier222
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.

@ -0,0 +1,255 @@
<br />
<p align="center">
<a href="https://music.qier222.com" target="blank">
<img src="images/logo.png" alt="Logo" width="156" height="156">
</a>
<h2 align="center" style="font-weight: 600">YesPlayMusic</h2>
<p align="center">
高颜值的第三方网易云播放器
<br />
<a href="https://music.qier222.com" target="blank"><strong>🌎 访问DEMO</strong></a>&nbsp;&nbsp;|&nbsp;&nbsp;
<a href="#%EF%B8%8F-安装" target="blank"><strong>📦️ 下载安装包</strong></a>&nbsp;&nbsp;|&nbsp;&nbsp;
<a href="https://t.me/yesplaymusic" target="blank"><strong>💬 加入交流群</strong></a>
<br />
<br />
</p>
</p>
[![Library][library-screenshot]](https://music.qier222.com)
## ✨ 特性
- ✅ 使用 Vue.js 全家桶开发
- 🔴 网易云账号登录(扫码/手机/邮箱登录)
- 📺 支持 MV 播放
- 📃 支持歌词显示
- 📻 支持私人 FM / 每日推荐歌曲
- 🚫🤝 无任何社交功能
- 🌎️ 海外用户可直接播放(需要登录网易云账号)
- 🔐 支持 [UnblockNeteaseMusic](https://github.com/UnblockNeteaseMusic/server#音源清单),自动使用[各类音源](https://github.com/UnblockNeteaseMusic/server#音源清单)替换变灰歌曲链接 (网页版不支持)
- 「各类音源」指默认启用的音源。
- YouTube 音源需自行安装 `yt-dlp`
- ✔️ 每日自动签到(手机端和电脑端同时签到)
- 🌚 Light/Dark Mode 自动切换
- 👆 支持 Touch Bar
- 🖥️ 支持 PWA可在 Chrome/Edge 里点击地址栏右边的 安装到电脑
- 🟥 支持 Last.fm Scrobble
- ☁️ 支持音乐云盘
- ⌨️ 自定义快捷键和全局快捷键
- 🎧 支持 Mpris
- 🛠 更多特性开发中
## 📦️ 安装
Electron 版本由 [@hawtim](https://github.com/hawtim) 和 [@qier222](https://github.com/qier222) 适配并维护,支持 macOS、Windows、Linux。
访问本项目的 [Releases](https://github.com/qier222/YesPlayMusic/releases)
页面下载安装包。
- macOS 用户可以通过 Homebrew 来安装:`brew install --cask yesplaymusic`
- Windows 用户可以通过 Scoop 来安装:`scoop install extras/yesplaymusic`
## ⚙️ 部署至 Vercel
除了下载安装包使用,你还可以将本项目部署到 Vercel 或你的服务器上。下面是部署到 Vercel 的方法。
本项目的 Demo (https://music.qier222.com) 就是部署在 Vercel 上的网站。
[![Powered by Vercel](https://www.datocms-assets.com/31049/1618983297-powered-by-vercel.svg)](https://vercel.com/?utm_source=ohmusic&utm_campaign=oss)
1. 部署网易云 API详情参见 [Binaryify/NeteaseCloudMusicApi](https://neteasecloudmusicapi.vercel.app/#/?id=%e5%ae%89%e8%a3%85)
。你也可以将 API 部署到 Vercel。
2. 点击本仓库右上角的 Fork复制本仓库到你的 GitHub 账号。
3. 点击仓库的 Add File选择 Create new file输入 `vercel.json`,将下面的内容复制粘贴到文件中,并将 `https://your-netease-api.example.com` 替换为你刚刚部署的网易云 API 地址:
```json
{
"rewrites": [
{
"source": "/api/:match*",
"destination": "https://your-netease-api.example.com/:match*"
}
]
}
```
4. 打开 [Vercel.com](https://vercel.com),使用 GitHub 登录。
5. 点击 Import Git Repository 并选择你刚刚复制的仓库并点击 Import。
6. 点击 PERSONAL ACCOUNT 旁边的 Select。
7. 点击 Environment Variables填写 Name 为 `VUE_APP_NETEASE_API_URL`Value 为 `/api`,点击 Add。最后点击底部的 Deploy 就可以部署到
Vercel 了。
## ⚙️ 部署到自己的服务器
除了部署到 Vercel你还可以部署到自己的服务器上
1. 部署网易云 API详情参见 [Binaryify/NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi)
2. 克隆本仓库
```sh
git clone --recursive https://github.com/qier222/YesPlayMusic.git
```
3. 安装依赖
```sh
yarn install
```
4. (可选)使用 Nginx 反向代理 API将 API 路径映射为 `/api`,如果 API 和网页不在同一个域名下的话(跨域),会有一些 bug。
5. 复制 `/.env.example` 文件为 `/.env`,修改里面 `VUE_APP_NETEASE_API_URL` 的值为网易云 API 地址。本地开发的话可以填写 API 地址为 `http://localhost:3000`YesPlayMusic 地址为 `http://localhost:8080`。如果你使用了反向代理 API可以填写 API 地址为 `/api`
```
VUE_APP_NETEASE_API_URL=http://localhost:3000
```
6. 编译打包
```sh
yarn run build
```
7. 将 `/dist` 目录下的文件上传到你的 Web 服务器
## ⚙️ Docker 部署
1. 构建 Docker Image
```sh
docker build -t yesplaymusic .
```
2. 启动 Docker Container
```sh
docker run -d --name YesPlayMusic -p 80:80 yesplaymusic
```
3. Docker Compose 启动
```sh
docker-compose up -d
```
YesPlayMusic 地址为 `http://localhost`
## ⚙️ 部署至 Replit
1. 新建 Repl选择 Bash 模板
2. 在 Replit shell 中运行以下命令
```sh
bash <(curl -s -L https://raw.githubusercontent.com/qier222/YesPlayMusic/main/install-replit.sh)
```
3. 首次运行成功后,只需点击绿色按钮 `Run` 即可再次运行
4. 由于 replit 个人版限制内存为 1G教育版为 3G构建过程中可能会失败请再次运行上述命令或运行以下命令
```sh
cd /home/runner/${REPL_SLUG}/music && yarn installl && yarn run build
```
## 👷‍♂️ 打包客户端
如果在 Release 页面没有找到适合你的设备的安装包的话,你可以根据下面的步骤来打包自己的客户端。
1. 打包 Electron 需要用到 Node.js 和 Yarn。可前往 [Node.js 官网](https://nodejs.org/zh-cn/) 下载安装包。安装 Node.js
后可在终端里执行 `npm install -g yarn` 来安装 Yarn。
2. 使用 `git clone --recursive https://github.com/qier222/YesPlayMusic.git` 克隆本仓库到本地。
3. 使用 `yarn install` 安装项目依赖。
4. 复制 `/.env.example` 文件为 `/.env`
5. 选择下列表格的命令来打包适合的你的安装包,打包出来的文件在 `/dist_electron` 目录下。了解更多信息可访问 [electron-builder 文档](https://www.electron.build/cli)
| 命令 | 说明 |
| ------------------------------------------ | ------------------------- |
| `yarn electron:build --windows nsis:ia32` | Windows 32 位 |
| `yarn electron:build --windows nsis:arm64` | Windows ARM |
| `yarn electron:build --linux deb:armv7l` | Debian armv7l树莓派等 |
| `yarn electron:build --macos dir:arm64` | macOS ARM |
## :computer: 配置开发环境
本项目由 [NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi) 提供 API。
运行本项目
```shell
# 安装依赖
yarn install
# 创建本地环境变量
cp .env.example .env
# 运行(网页端)
yarn serve
# 运行electron
yarn electron:serve
```
本地运行 NeteaseCloudMusicApi或者将 API [部署至 Vercel](#%EF%B8%8F-部署至-vercel)
```shell
# 运行 API (默认 3000 端口)
yarn netease_api:run
```
## ☑️ Todo
查看 Todo 请访问本项目的 [Projects](https://github.com/qier222/YesPlayMusic/projects/1)
欢迎提 Issue 和 Pull request。
## 📜 开源许可
本项目仅供个人学习研究使用,禁止用于商业及非法用途。
基于 [MIT license](https://opensource.org/licenses/MIT) 许可进行开源。
## 灵感来源
API 源代码来自 [Binaryify/NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi)
- [Apple Music](https://music.apple.com)
- [YouTube Music](https://music.youtube.com)
- [Spotify](https://www.spotify.com)
- [网易云音乐](https://music.163.com)
## 🖼️ 截图
![lyrics][lyrics-screenshot]
![library-dark][library-dark-screenshot]
![album][album-screenshot]
![home-2][home-2-screenshot]
![artist][artist-screenshot]
![search][search-screenshot]
![home][home-screenshot]
![explore][explore-screenshot]
<!-- MARKDOWN LINKS & IMAGES -->
<!-- https://www.markdownguide.org/basic-syntax/#reference-style-links -->
[album-screenshot]: images/album.png
[artist-screenshot]: images/artist.png
[explore-screenshot]: images/explore.png
[home-screenshot]: images/home.png
[home-2-screenshot]: images/home-2.png
[lyrics-screenshot]: images/lyrics.png
[library-screenshot]: images/library.png
[library-dark-screenshot]: images/library-dark.png
[search-screenshot]: images/search.png

@ -0,0 +1,11 @@
module.exports = {
presets: [
[
'@vue/cli-plugin-babel/preset',
{
useBuiltIns: 'usage',
shippedProposals: true,
},
],
],
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 965 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,12 @@
services:
YesPlayMusic:
build:
context: .
image: yesplaymusic
container_name: YesPlayMusic
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
ports:
- 80:80
restart: always

@ -0,0 +1,28 @@
server {
gzip on;
listen 80;
listen [::]:80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
location @rewrites {
rewrite ^(.*)$ /index.html last;
}
location /api/ {
proxy_buffers 16 32k;
proxy_buffer_size 128k;
proxy_busy_buffers_size 128k;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Host $remote_addr;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://localhost:3000/;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

@ -0,0 +1,28 @@
#!/usr/bin/bash
# 初始化 .replit 和 replit.nix
if [[ $1 == i ]];then
echo -e 'run = ["bash", "main.sh"]\n\nentrypoint = "main.sh"' >.replit
echo -e "{ pkgs }: {\n\t\tdeps = [\n\t\t\tpkgs.nodejs-16_x\n\t\t\tpkgs.yarn\n\t\t\tpkgs.bashInteractive\n\t\t];\n}" > replit.nix
exit
fi
# 安装
if [[ ! -d api ]];then
mkdir api
git clone https://github.com/Binaryify/NeteaseCloudMusicApi ./api && \
cd api && npm install && cd ..
fi
if [[ ! -d music ]];then
mkdir music
git clone https://github.com/qier222/YesPlayMusic ./music && \
cd music && cp .env.example .env && npm install --force && npm run build && cd ..
fi
# 启动
PID=`ps -ef | grep npm | awk '{print $2}' | sed '$d'`
if [[ ! -z ${PID} ]];then echo $PID | xargs kill;fi
nohup bash -c 'cd api && PORT=35216 node app.js' > api.log 2>&1
nohup bash -c 'npx serve music/dist/' > music.log 2>&1

@ -0,0 +1,15 @@
{
// @
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"target": "ES6",
"module": "commonjs",
"allowSyntheticDefaultImports": true,
"jsx": "preserve"
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}

@ -0,0 +1,131 @@
{
"name": "yesplaymusic",
"version": "0.4.7",
"private": true,
"description": "A third party music player for Netease Music",
"author": "qier222<qier222@outlook.com>",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"electron:build": "vue-cli-service electron:build -p never",
"electron:build-all": "vue-cli-service electron:build -p never -mwl",
"electron:build-mac": "vue-cli-service electron:build -p never -m",
"electron:build-win": "vue-cli-service electron:build -p never -w",
"electron:build-linux": "vue-cli-service electron:build -p never -l",
"electron:serve": "vue-cli-service electron:serve",
"electron:buildicon": "electron-icon-builder --input=./build/icons/icon.png --output=build --flatten",
"electron:publish": "vue-cli-service electron:build -mwl -p always",
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps",
"prettier": "npx prettier --write ./src",
"netease_api:run": "npx NeteaseCloudMusicApi"
},
"main": "background.js",
"dependencies": {
"@unblockneteasemusic/rust-napi": "^0.4.0",
"NeteaseCloudMusicApi": "^4.8.7",
"axios": "^0.26.1",
"change-case": "^4.1.2",
"cli-color": "^2.0.0",
"color": "^4.2.3",
"core-js": "^3.6.5",
"crypto-js": "^4.0.0",
"dayjs": "^1.8.36",
"dexie": "^3.0.3",
"discord-rich-presence": "^0.0.8",
"electron": "^13.6.7",
"electron-builder": "^23.0.0",
"electron-context-menu": "^3.1.2",
"electron-debug": "^3.1.0",
"electron-devtools-installer": "^3.2",
"electron-icon-builder": "^2.0.1",
"electron-is-dev": "^2.0.0",
"electron-log": "^4.3.0",
"electron-store": "^8.0.1",
"electron-updater": "^5.0.1",
"express": "^4.17.1",
"express-fileupload": "^1.2.0",
"express-http-proxy": "^1.6.2",
"extract-zip": "^2.0.1",
"howler": "^2.2.3",
"js-cookie": "^2.2.1",
"jsbi": "^4.1.0",
"lodash": "^4.17.20",
"md5": "^2.3.0",
"mpris-service": "^2.1.2",
"music-metadata": "^7.5.3",
"node-vibrant": "^3.2.1-alpha.1",
"nprogress": "^0.2.0",
"pac-proxy-agent": "^4.1.0",
"plyr": "^3.6.2",
"prettier": "2.5.1",
"qrcode": "^1.4.4",
"register-service-worker": "^1.7.1",
"svg-sprite-loader": "^6.0.11",
"tunnel": "^0.0.6",
"vscode-codicons": "^0.0.17",
"vue": "^2.6.11",
"vue-clipboard2": "^0.3.1",
"vue-gtag": "1",
"vue-i18n": "^8.22.0",
"vue-router": "^3.4.3",
"vue-slider-component": "^3.2.5",
"vuex": "^3.4.0",
"x11": "^2.3.0"
},
"devDependencies": {
"@types/node": "^17.0.0",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-pwa": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-vue": "^7.9.0",
"husky": "^4.3.0",
"sass": "^1.26.11",
"sass-loader": "^10.0.2",
"vue-cli-plugin-electron-builder": "~2.1.1",
"vue-template-compiler": "^2.6.11"
},
"resolutions": {
"icon-gen": "3.0.0",
"degenerator": "2.2.0",
"electron-builder": "^23.0.0"
},
"eslintConfig": {
"root": true,
"env": {
"node": true,
"browser": true
},
"extends": [
"plugin:vue/essential",
"plugin:vue/recommended",
"plugin:prettier/recommended",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"globals": {
"ipcRenderer": "off"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
],
"husky": {
"hooks": {
"pre-commit": "npm run prettier"
}
},
"__npminstall_done": false
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 913 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 953 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.00251 14.9297L0 1.07422H6.14651L8.00251 4.27503L9.84583 1.07422H16L8.00251 14.9297Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 215 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 801 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 871 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="referrer" content="no-referrer" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
properly without JavaScript enabled. Please enable it to
continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

@ -0,0 +1,2 @@
User-agent: *
Disallow:baidu

@ -0,0 +1,7 @@
commit_template: 'style: with ${restyler.name}'
restylers:
- prettier
- prettier-json
- prettier-markdown
- prettier-yaml
- whitespace

@ -0,0 +1,168 @@
<template>
<div
id="app"
class="no-scrollbar"
:class="{ 'user-select-none': userSelectNone }"
>
<Scrollbar v-show="!showLyrics" ref="scrollbar" />
<transition name="slide-up">
<Player v-if="enablePlayer" v-show="showPlayer" ref="player" />
</transition>
<Navbar v-show="showNavbar" ref="navbar" />
<main
ref="main"
:style="{ overflow: enableScrolling ? 'auto' : 'hidden' }"
@scroll="handleScroll"
>
<keep-alive>
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>
</main>
<Toast />
<ModalAddTrackToPlaylist v-if="isAccountLoggedIn" />
<ModalNewPlaylist v-if="isAccountLoggedIn" />
<transition v-if="enablePlayer" name="slide-up">
<Lyrics v-show="showLyrics" />
</transition>
</div>
</template>
<script>
import ModalAddTrackToPlaylist from './components/ModalAddTrackToPlaylist.vue';
import ModalNewPlaylist from './components/ModalNewPlaylist.vue';
import Scrollbar from './components/Scrollbar.vue';
import Navbar from './components/Navbar.vue';
import Player from './components/Player.vue';
import Toast from './components/Toast.vue';
import { ipcRenderer } from './electron/ipcRenderer';
import { isAccountLoggedIn, isLooseLoggedIn } from '@/utils/auth';
import Lyrics from './views/lyrics.vue';
import { mapState } from 'vuex';
export default {
name: 'App',
components: {
Navbar,
Player,
Toast,
ModalAddTrackToPlaylist,
ModalNewPlaylist,
Lyrics,
Scrollbar,
},
data() {
return {
isElectron: process.env.IS_ELECTRON, // true || undefined
userSelectNone: false,
};
},
computed: {
...mapState(['showLyrics', 'settings', 'player', 'enableScrolling']),
isAccountLoggedIn() {
return isAccountLoggedIn();
},
showPlayer() {
return (
[
'mv',
'loginUsername',
'login',
'loginAccount',
'lastfmCallback',
].includes(this.$route.name) === false
);
},
enablePlayer() {
return this.player.enabled && this.$route.name !== 'lastfmCallback';
},
showNavbar() {
return this.$route.name !== 'lastfmCallback';
},
},
created() {
if (this.isElectron) ipcRenderer(this);
window.addEventListener('keydown', this.handleKeydown);
this.fetchData();
},
methods: {
handleKeydown(e) {
if (e.code === 'Space') {
if (e.target.tagName === 'INPUT') return false;
if (this.$route.name === 'mv') return false;
e.preventDefault();
this.player.playOrPause();
}
},
fetchData() {
if (!isLooseLoggedIn()) return;
this.$store.dispatch('fetchLikedSongs');
this.$store.dispatch('fetchLikedSongsWithDetails');
this.$store.dispatch('fetchLikedPlaylist');
if (isAccountLoggedIn()) {
this.$store.dispatch('fetchLikedAlbums');
this.$store.dispatch('fetchLikedArtists');
this.$store.dispatch('fetchLikedMVs');
this.$store.dispatch('fetchCloudDisk');
}
},
handleScroll() {
this.$refs.scrollbar.handleScroll();
},
},
};
</script>
<style lang="scss">
html,
body,
#app {
position: fixed;
width: 100%;
height: 100%;
}
* {
padding: 0;
margin: 0;
}
#app {
position: relative;
transition: all 0.4s;
}
main {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
padding: 64px 10vw 96px 10vw;
box-sizing: border-box;
scrollbar-width: none; // firefox
}
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
overflow: scroll;
}
@media (max-width: 576px) {
main {
padding: 64px 5vw 96px 5vw;
}
}
main::-webkit-scrollbar {
width: 0px;
}
.slide-up-enter-active,
.slide-up-leave-active {
transition: transform 0.4s;
}
.slide-up-enter,
.slide-up-leave-to {
transform: translateY(100%);
}
</style>

@ -0,0 +1,77 @@
import request from '@/utils/request';
import { mapTrackPlayableStatus } from '@/utils/common';
import { cacheAlbum, getAlbumFromCache } from '@/utils/db';
/**
* 获取专辑内容
* 说明 : 调用此接口 , 传入专辑 id, 可获得专辑内容
* @param {number} id
*/
export function getAlbum(id) {
const fetchLatest = () => {
return request({
url: '/album',
method: 'get',
params: {
id,
},
}).then(data => {
cacheAlbum(id, data);
data.songs = mapTrackPlayableStatus(data.songs);
return data;
});
};
return getAlbumFromCache(id).then(result => {
return result ?? fetchLatest();
});
}
/**
* 全部新碟
* 说明 : 登录后调用此接口 ,可获取全部新碟
* - limit - 返回数量 , 默认为 30
* - offset - 偏移数量用于分页 , :( 页数 -1)*30, 其中 30 limit 的值 , 默认为 0
* - area - ALL:全部,ZH:华语,EA:欧美,KR:韩国,JP:日本
* @param {Object} params
* @param {number} params.limit
* @param {number=} params.offset
* @param {string} params.area
*/
export function newAlbums(params) {
return request({
url: '/album/new',
method: 'get',
params,
});
}
/**
* 专辑动态信息
* 说明 : 调用此接口 , 传入专辑 id, 可获得专辑动态信息,如是否收藏,收藏数,评论数,分享数
* - id - 专辑id
* @param {number} id
*/
export function albumDynamicDetail(id) {
return request({
url: '/album/detail/dynamic',
method: 'get',
params: { id, timestamp: new Date().getTime() },
});
}
/**
* 收藏/取消收藏专辑
* 说明 : 调用此接口,可收藏/取消收藏专辑
* - id - 返专辑 id
* - t - 1 为收藏,其他为取消收藏
* @param {Object} params
* @param {number} params.id
* @param {number} params.t
*/
export function likeAAlbum(params) {
return request({
url: '/album/sub',
method: 'post',
params,
});
}

@ -0,0 +1,107 @@
import request from '@/utils/request';
import { mapTrackPlayableStatus } from '@/utils/common';
/**
* 获取歌手单曲
* 说明 : 调用此接口 , 传入歌手 id, 可获得歌手部分信息和热门歌曲
* @param {number} id - 歌手 id, 可由搜索接口获得
*/
export function getArtist(id) {
return request({
url: '/artists',
method: 'get',
params: {
id,
timestamp: new Date().getTime(),
},
}).then(data => {
data.hotSongs = mapTrackPlayableStatus(data.hotSongs);
return data;
});
}
/**
* 获取歌手专辑
* 说明 : 调用此接口 , 传入歌手 id, 可获得歌手专辑内容
* - id: 歌手 id
* - limit: 取出数量 , 默认为 50
* - offset: 偏移数量 , 用于分页 , :( 页数 -1)*50, 其中 50 limit 的值 , 默认为 0
* @param {Object} params
* @param {number} params.id
* @param {number=} params.limit
* @param {number=} params.offset
*/
export function getArtistAlbum(params) {
return request({
url: '/artist/album',
method: 'get',
params,
});
}
/**
* 歌手榜
* 说明 : 调用此接口 , 可获取排行榜中的歌手榜
* - type : 地区
* 1: 华语
* 2: 欧美
* 3: 韩国
* 4: 日本
* @param {number=} type
*/
export function toplistOfArtists(type = null) {
let params = {};
if (type) {
params.type = type;
}
return request({
url: '/toplist/artist',
method: 'get',
params,
});
}
/**
* 获取歌手 mv
* 说明 : 调用此接口 , 传入歌手 id, 可获得歌手 mv 信息 , 具体 mv 播放地址可调 /mv传入此接口获得的 mvid 来拿到 , : /artist/mv?id=6452,/mv?mvid=5461064
* @param {number} params.id 歌手 id, 可由搜索接口获得
* @param {number} params.offset
* @param {number} params.limit
*/
export function artistMv(params) {
return request({
url: '/artist/mv',
method: 'get',
params,
});
}
/**
* 收藏歌手
* 说明 : 调用此接口 , 传入歌手 id, 可收藏歌手
* - id: 歌手 id
* - t: 操作,1 为收藏,其他为取消收藏
* @param {Object} params
* @param {number} params.id
* @param {number} params.t
*/
export function followAArtist(params) {
return request({
url: '/artist/sub',
method: 'post',
params,
});
}
/**
* 相似歌手
* 说明 : 调用此接口 , 传入歌手 id, 可获得相似歌手
* - id: 歌手 id
* @param {number} id
*/
export function similarArtists(id) {
return request({
url: '/simi/artist',
method: 'post',
params: { id },
});
}

@ -0,0 +1,110 @@
import request from '@/utils/request';
/**
* 手机登录
* - phone: 手机号码
* - password: 密码
* - countrycode: 国家码用于国外手机号登录例如美国传入1
* - md5_password: md5加密后的密码,传入后 password 将失效
* @param {Object} params
* @param {string} params.phone
* @param {string} params.password
* @param {string=} params.countrycode
* @param {string=} params.md5_password
*/
export function loginWithPhone(params) {
return request({
url: '/login/cellphone',
method: 'post',
params,
});
}
/**
* 邮箱登录
* - email: 163 网易邮箱
* - password: 密码
* - md5_password: md5加密后的密码,传入后 password 将失效
* @param {Object} params
* @param {string} params.email
* @param {string} params.password
* @param {string=} params.md5_password
*/
export function loginWithEmail(params) {
return request({
url: '/login',
method: 'post',
params,
});
}
/**
* 二维码key生成接口
*/
export function loginQrCodeKey() {
return request({
url: '/login/qr/key',
method: 'get',
params: {
timestamp: new Date().getTime(),
},
});
}
/**
* 二维码生成接口
* 说明: 调用此接口传入上一个接口生成的key可生成二维码图片的base64和二维码信息,
* 可使用base64展示图片,或者使用二维码信息内容自行使用第三方二维码生产库渲染二维码
* @param {Object} params
* @param {string} params.key
* @param {string=} params.qrimg 传入后会额外返回二维码图片base64编码
*/
export function loginQrCodeCreate(params) {
return request({
url: '/login/qr/create',
method: 'get',
params: {
...params,
timestamp: new Date().getTime(),
},
});
}
/**
* 二维码检测扫码状态接口
* 说明: 轮询此接口可获取二维码扫码状态,800为二维码过期,801为等待扫码,802为待确认,803为授权登录成功(803状态码下会返回cookies)
* @param {string} key
*/
export function loginQrCodeCheck(key) {
return request({
url: '/login/qr/check',
method: 'get',
params: {
key,
timestamp: new Date().getTime(),
},
});
}
/**
* 刷新登录
* 说明 : 调用此接口 , 可刷新登录状态
* - 调用例子 : /login/refresh
*/
export function refreshCookie() {
return request({
url: '/login/refresh',
method: 'post',
});
}
/**
* 退出登录
* 说明 : 调用此接口 , 可退出登录
*/
export function logout() {
return request({
url: '/logout',
method: 'post',
});
}

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

Loading…
Cancel
Save