refactor: version 2.0 (React)
|
|
@ -1,16 +0,0 @@
|
||||||
node_modules
|
|
||||||
npm-debug.log
|
|
||||||
Dockerfile*
|
|
||||||
docker-compose*
|
|
||||||
.dockerignore
|
|
||||||
.git
|
|
||||||
.github
|
|
||||||
.gitignore
|
|
||||||
README.md
|
|
||||||
LICENSE
|
|
||||||
.vscode
|
|
||||||
dist
|
|
||||||
dist_electron
|
|
||||||
build
|
|
||||||
images
|
|
||||||
script
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
charset = utf-8
|
|
||||||
trim_trailing_whitespace = false
|
|
||||||
insert_final_newline = false
|
|
||||||
46
.electron-builder.config.js
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
/**
|
||||||
|
* @type {import('electron-builder').Configuration}
|
||||||
|
* @see https://www.electron.build/configuration/configuration
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
appId: 'yesplaymusic',
|
||||||
|
productName: 'YesPlayMusic',
|
||||||
|
copyright: 'Copyright © 2022 ${author}',
|
||||||
|
asar: true,
|
||||||
|
directories: {
|
||||||
|
output: 'release/${version}',
|
||||||
|
buildResources: 'build',
|
||||||
|
},
|
||||||
|
files: ['dist'],
|
||||||
|
win: {
|
||||||
|
target: [
|
||||||
|
{
|
||||||
|
target: 'nsis',
|
||||||
|
arch: ['x64'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: 'nsis',
|
||||||
|
arch: ['arm64'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: 'nsis',
|
||||||
|
arch: ['ia32'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
artifactName: '${productName}-${version}-Setup.${ext}',
|
||||||
|
},
|
||||||
|
nsis: {
|
||||||
|
oneClick: false,
|
||||||
|
perMachine: false,
|
||||||
|
allowToChangeInstallationDirectory: true,
|
||||||
|
deleteAppDataOnUninstall: true,
|
||||||
|
},
|
||||||
|
mac: {
|
||||||
|
target: ['dmg'],
|
||||||
|
artifactName: '${productName}-${version}-Installer.${ext}',
|
||||||
|
},
|
||||||
|
linux: {
|
||||||
|
target: ['AppImage'],
|
||||||
|
artifactName: '${productName}-${version}-Installer.${ext}',
|
||||||
|
},
|
||||||
|
}
|
||||||
3
.env
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
ELECTRON_WEB_SERVER_PORT = 42710
|
||||||
|
ELECTRON_DEV_NETEASE_API_PORT = 3000
|
||||||
|
VITE_APP_NETEASE_API_URL = /netease
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
VUE_APP_NETEASE_API_URL=/api
|
|
||||||
VUE_APP_ELECTRON_API_URL=/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
|
|
||||||
DEV_SERVER_PORT=20201
|
|
||||||
|
|
||||||
28
.eslintrc.js
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es2021: true,
|
||||||
|
node: true,
|
||||||
|
jest: true,
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:react/recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
],
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: {
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
},
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
plugins: ['react', '@typescript-eslint', 'react-hooks'],
|
||||||
|
rules: {
|
||||||
|
'react-hooks/rules-of-hooks': 'error',
|
||||||
|
'react-hooks/exhaustive-deps': 'warn',
|
||||||
|
'react/react-in-jsx-scope': 'off',
|
||||||
|
'@typescript-eslint/no-inferrable-types': 'off',
|
||||||
|
},
|
||||||
|
}
|
||||||
21
.github/ISSUE_TEMPLATE/----------.md
vendored
|
|
@ -1,21 +0,0 @@
|
||||||
---
|
|
||||||
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 可打开控制台)
|
|
||||||
79
.github/workflows/build.yaml
vendored
|
|
@ -1,79 +0,0 @@
|
||||||
name: Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
- "ci/*"
|
|
||||||
tags:
|
|
||||||
- v*
|
|
||||||
|
|
||||||
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@v2
|
|
||||||
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 }}
|
|
||||||
|
|
||||||
- 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: 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@v2
|
|
||||||
with:
|
|
||||||
name: YesPlayMusic-mac
|
|
||||||
path: dist_electron/*-universal.dmg
|
|
||||||
if-no-files-found: ignore
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: YesPlayMusic-win
|
|
||||||
path: dist_electron/*Setup*.exe
|
|
||||||
if-no-files-found: ignore
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: YesPlayMusic-linux
|
|
||||||
path: dist_electron/*.AppImage
|
|
||||||
if-no-files-found: ignore
|
|
||||||
108
.gitignore
vendored
|
|
@ -1,34 +1,88 @@
|
||||||
.DS_Store
|
# Logs
|
||||||
node_modules
|
logs
|
||||||
/dist
|
*.log
|
||||||
|
|
||||||
# local env files
|
|
||||||
.env
|
|
||||||
.env.local
|
|
||||||
.env.*.local
|
|
||||||
|
|
||||||
# Log files
|
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
pnpm-debug.log*
|
|
||||||
|
|
||||||
# Editor directories and files
|
# Runtime data
|
||||||
.idea
|
pids
|
||||||
.vscode
|
*.pid
|
||||||
*.suo
|
*.seed
|
||||||
*.ntvs*
|
*.pid.lock
|
||||||
*.njsproj
|
|
||||||
*.sln
|
|
||||||
*.sw?
|
|
||||||
|
|
||||||
.vercel
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
#Electron-builder output
|
# Coverage directory used by tools like istanbul
|
||||||
/dist_electron
|
coverage
|
||||||
NeteaseCloudMusicApi-master
|
|
||||||
NeteaseCloudMusicApi-master.zip
|
|
||||||
|
|
||||||
# Local Netlify folder
|
# nyc test coverage
|
||||||
.netlify
|
.nyc_output
|
||||||
vercel.json
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# TypeScript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# next.js build output
|
||||||
|
.next
|
||||||
|
|
||||||
|
# nuxt.js build output
|
||||||
|
.nuxt
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# ----
|
||||||
|
dist
|
||||||
|
**/.tmp
|
||||||
|
release
|
||||||
|
.DS_Store
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
.vscode/settings.json
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
build
|
|
||||||
coverage
|
|
||||||
dist
|
|
||||||
12
.prettierrc
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"trailingComma": "es5",
|
|
||||||
"tabWidth": 2,
|
|
||||||
"useTabs": false,
|
|
||||||
"semi": true,
|
|
||||||
"singleQuote": true,
|
|
||||||
"jsxSingleQuote": true,
|
|
||||||
"arrowParens": "avoid",
|
|
||||||
"endOfLine": "lf",
|
|
||||||
"bracketSpacing": true,
|
|
||||||
"htmlWhitespaceSensitivity": "strict"
|
|
||||||
}
|
|
||||||
39
.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Main(inspector)",
|
||||||
|
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
|
||||||
|
"runtimeArgs": [
|
||||||
|
"--remote-debugging-port=9222",
|
||||||
|
"${workspaceFolder}/dist/main/index.cjs",
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"DEBUG": "true",
|
||||||
|
},
|
||||||
|
"windows": {
|
||||||
|
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
|
||||||
|
},
|
||||||
|
"sourceMaps": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Main(vite)",
|
||||||
|
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
|
||||||
|
"runtimeArgs": [
|
||||||
|
"${workspaceFolder}/dist/main/index.cjs",
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"VITE_DEV_SERVER_HOST": "127.0.0.1",
|
||||||
|
"VITE_DEV_SERVER_PORT": "3344",
|
||||||
|
},
|
||||||
|
"windows": {
|
||||||
|
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
|
||||||
|
},
|
||||||
|
"sourceMaps": true
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
13
.vscode/task.json
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"type": "npm",
|
||||||
|
"script": "debug",
|
||||||
|
"problemMatcher": [],
|
||||||
|
"label": "npm: debug",
|
||||||
|
"detail": "cross-env-shell NODE_ENV=debug \"npm run typecheck && node scripts/build.mjs && vite ./packages/renderer\"",
|
||||||
|
"group": "build"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
43
Dockerfile
|
|
@ -1,43 +0,0 @@
|
||||||
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
|
|
||||||
RUN echo $'server { \n\
|
|
||||||
gzip on;\n\
|
|
||||||
listen 80; \n\
|
|
||||||
listen [::]:80; \n\
|
|
||||||
server_name localhost; \n\
|
|
||||||
\n\
|
|
||||||
location / { \n\
|
|
||||||
root /usr/share/nginx/html; \n\
|
|
||||||
index index.html; \n\
|
|
||||||
try_files $uri $uri/ /index.html; \n\
|
|
||||||
} \n\
|
|
||||||
\n\
|
|
||||||
location @rewrites { \n\
|
|
||||||
rewrite ^(.*)$ /index.html last; \n\
|
|
||||||
} \n\
|
|
||||||
\n\
|
|
||||||
location /api/ { \n\
|
|
||||||
proxy_set_header Host $host; \n\
|
|
||||||
proxy_set_header X-Real-IP $remote_addr; \n\
|
|
||||||
proxy_set_header X-Forwarded-For $remote_addr; \n\
|
|
||||||
proxy_set_header X-Forwarded-Host $remote_addr; \n\
|
|
||||||
proxy_set_header X-NginX-Proxy true; \n\
|
|
||||||
proxy_pass http://localhost:3000/; \n\
|
|
||||||
} \n\
|
|
||||||
}' > /etc/nginx/conf.d/default.conf
|
|
||||||
|
|
||||||
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 NeteaseCloudMusicApi
|
|
||||||
|
|
||||||
COPY --from=build /app/dist /usr/share/nginx/html
|
|
||||||
|
|
||||||
CMD nginx ; exec npx NeteaseCloudMusicApi
|
|
||||||
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2020-2022 qier222
|
Copyright (c) 2021 草鞋没号
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
||||||
232
README.md
|
|
@ -1,231 +1 @@
|
||||||
<br />
|
# YesPlayMusic 2.0
|
||||||
<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> |
|
|
||||||
<a href="#%EF%B8%8F-安装" target="blank"><strong>📦️ 下载安装包</strong></a> |
|
|
||||||
<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 用户也可以通过 `brew install --cask yesplaymusic` 来安装。
|
|
||||||
|
|
||||||
## ⚙️ 部署至 Vercel
|
|
||||||
|
|
||||||
除了下载安装包使用,你还可以将本项目部署到 Vercel 或你的服务器上。下面是部署到 Vercel 的方法。
|
|
||||||
|
|
||||||
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`
|
|
||||||
|
|
||||||
## 👷♂️ 打包客户端
|
|
||||||
|
|
||||||
如果在 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
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
presets: [
|
|
||||||
[
|
|
||||||
'@vue/cli-plugin-babel/preset',
|
|
||||||
{
|
|
||||||
useBuiltIns: 'usage',
|
|
||||||
shippedProposals: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
|
||||||
};
|
|
||||||
BIN
build/icon.icns
Normal file
BIN
build/icon.ico
Normal file
|
After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 523 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 474 B |
|
Before Width: | Height: | Size: 750 B |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 965 B |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 353 KiB |
|
Before Width: | Height: | Size: 276 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
BIN
build/installerIcon.ico
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
build/uninstallerIcon.ico
Normal file
|
After Width: | Height: | Size: 36 KiB |
|
|
@ -1,9 +0,0 @@
|
||||||
services:
|
|
||||||
YesPlayMusic:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
image: yesplaymusic
|
|
||||||
container_name: YesPlayMusic
|
|
||||||
ports:
|
|
||||||
- 80:80
|
|
||||||
restart: always
|
|
||||||
BIN
images/album.png
|
Before Width: | Height: | Size: 253 KiB |
|
Before Width: | Height: | Size: 228 KiB |
|
Before Width: | Height: | Size: 354 KiB |
|
Before Width: | Height: | Size: 312 KiB |
BIN
images/home.png
|
Before Width: | Height: | Size: 389 KiB |
|
Before Width: | Height: | Size: 335 KiB |
|
Before Width: | Height: | Size: 324 KiB |
BIN
images/logo.png
|
Before Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 339 KiB |
|
Before Width: | Height: | Size: 276 KiB |
|
|
@ -1,14 +0,0 @@
|
||||||
{
|
|
||||||
// 支持 @ 的别名解析
|
|
||||||
"compilerOptions": {
|
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["src/*"]
|
|
||||||
},
|
|
||||||
"target": "ES6",
|
|
||||||
"module": "commonjs",
|
|
||||||
"allowSyntheticDefaultImports": true
|
|
||||||
},
|
|
||||||
"include": ["src/**/*"],
|
|
||||||
"exclude": ["node_modules"]
|
|
||||||
}
|
|
||||||
195
package.json
|
|
@ -1,130 +1,85 @@
|
||||||
{
|
{
|
||||||
"name": "yesplaymusic",
|
"name": "yesplamusic",
|
||||||
"version": "0.4.4",
|
"productName": "YesPlayMusic",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "A third party music player for Netease Music",
|
"version": "2.0.0",
|
||||||
"author": "qier222<qier222@outlook.com>",
|
"description": "A nifty third-party NetEase Music player",
|
||||||
|
"homepage": "https://github.com/qier222/YesPlayMusic",
|
||||||
|
"license": "MIT",
|
||||||
|
"author": "qier222 <qier222@outlook.com>",
|
||||||
|
"repository": "github:qier222/YesPlayMusic",
|
||||||
|
"main": "dist/main/index.cjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"dev": "node scripts/watch.mjs",
|
||||||
"build": "vue-cli-service build",
|
"build": "npm run typecheck && node scripts/build.mjs && electron-builder --config .electron-builder.config.js",
|
||||||
"lint": "vue-cli-service lint",
|
"typecheck": "tsc --noEmit --project packages/renderer/tsconfig.json",
|
||||||
"electron:build": "vue-cli-service electron:build -p never",
|
"debug": "cross-env-shell NODE_ENV=debug \"npm run typecheck && node scripts/build.mjs && vite ./packages/renderer\"",
|
||||||
"electron:build-all": "vue-cli-service electron:build -p never -mwl",
|
"eslint": "eslint --ext .ts,.js ./",
|
||||||
"electron:build-mac": "vue-cli-service electron:build -p never -m",
|
"prettier": "prettier --write './**/*.{ts,js,tsx,jsx}'"
|
||||||
"electron:build-win": "vue-cli-service electron:build -p never -w",
|
},
|
||||||
"electron:build-linux": "vue-cli-service electron:build -p never -l",
|
"engines": {
|
||||||
"electron:serve": "vue-cli-service electron:serve",
|
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||||
"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": {
|
"dependencies": {
|
||||||
"@unblockneteasemusic/server": "v0.27.0-rc.4",
|
"NeteaseCloudMusicApi": "^4.5.8",
|
||||||
"NeteaseCloudMusicApi": "^4.5.2",
|
|
||||||
"axios": "^0.21.0",
|
|
||||||
"change-case": "^4.1.2",
|
"change-case": "^4.1.2",
|
||||||
"cli-color": "^2.0.0",
|
"cookie-parser": "^1.4.6",
|
||||||
"color": "^3.1.3",
|
"electron-log": "^4.4.6",
|
||||||
"core-js": "^3.6.5",
|
"electron-store": "^8.0.1",
|
||||||
"crypto-js": "^4.0.0",
|
"express": "^4.17.3",
|
||||||
"dayjs": "^1.8.36",
|
"realm": "^10.13.0"
|
||||||
"dexie": "^3.0.3",
|
|
||||||
"discord-rich-presence": "^0.0.8",
|
|
||||||
"electron": "^13.6.7",
|
|
||||||
"electron-builder": "^23.0.0",
|
|
||||||
"electron-context-menu": "^2.3.0",
|
|
||||||
"electron-debug": "^3.1.0",
|
|
||||||
"electron-devtools-installer": "^3.2",
|
|
||||||
"electron-icon-builder": "^1.0.2",
|
|
||||||
"electron-is-dev": "^1.2.0",
|
|
||||||
"electron-log": "^4.3.0",
|
|
||||||
"electron-store": "^6.0.1",
|
|
||||||
"electron-updater": "^4.3.5",
|
|
||||||
"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": "^5.0.0",
|
|
||||||
"tunnel": "^0.0.6",
|
|
||||||
"vscode-codicons": "^0.0.17",
|
|
||||||
"vue": "^2.6.11",
|
|
||||||
"vue-analytics": "^5.22.1",
|
|
||||||
"vue-clipboard2": "^0.3.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": {
|
"devDependencies": {
|
||||||
"@types/node": "^17.0.0",
|
"@trivago/prettier-plugin-sort-imports": "^3.2.0",
|
||||||
"@vue/cli-plugin-babel": "~4.5.0",
|
"@types/express": "^4.17.13",
|
||||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
"@types/howler": "^2.2.6",
|
||||||
"@vue/cli-plugin-pwa": "~4.5.0",
|
"@types/js-cookie": "^3.0.1",
|
||||||
"@vue/cli-plugin-vuex": "~4.5.0",
|
"@types/lodash-es": "^4.17.6",
|
||||||
"@vue/cli-service": "~4.5.0",
|
"@types/md5": "^2.3.2",
|
||||||
"babel-eslint": "^10.1.0",
|
"@types/qrcode": "^1.4.2",
|
||||||
"eslint": "^6.7.2",
|
"@types/react": "^17.0.39",
|
||||||
"eslint-config-prettier": "^8.1.0",
|
"@types/react-dom": "^17.0.11",
|
||||||
"eslint-plugin-prettier": "^3.3.1",
|
"@typescript-eslint/eslint-plugin": "^5.13.0",
|
||||||
"eslint-plugin-vue": "^7.9.0",
|
"@typescript-eslint/parser": "^5.13.0",
|
||||||
"husky": "^4.3.0",
|
"@vitejs/plugin-react": "^1.2.0",
|
||||||
"sass": "^1.26.11",
|
"ahooks": "^3.1.13",
|
||||||
"sass-loader": "^10.0.2",
|
"ansi-styles": "^6.1.0",
|
||||||
"vue-cli-plugin-electron-builder": "~2.1.1",
|
"autoprefixer": "^10.4.2",
|
||||||
"vue-template-compiler": "^2.6.11"
|
"axios": "^0.26.0",
|
||||||
},
|
"classnames": "^2.3.1",
|
||||||
"resolutions": {
|
"color.js": "^1.2.0",
|
||||||
"icon-gen": "3.0.0",
|
"colord": "^2.9.2",
|
||||||
"degenerator": "2.2.0",
|
"cross-env": "^7.0.3",
|
||||||
"electron-builder": "^23.0.0"
|
"dayjs": "^1.10.8",
|
||||||
},
|
"electron": "^17.0.0",
|
||||||
"eslintConfig": {
|
"electron-builder": "^22.14.13",
|
||||||
"root": true,
|
"electron-devtools-installer": "^3.2.0",
|
||||||
"env": {
|
"eslint": "^8.10.0",
|
||||||
"node": true,
|
"eslint-plugin-react": "^7.29.3",
|
||||||
"browser": true
|
"eslint-plugin-react-hooks": "^4.3.0",
|
||||||
},
|
"howler": "^2.2.3",
|
||||||
"extends": [
|
"js-cookie": "^3.0.1",
|
||||||
"plugin:vue/essential",
|
"lodash-es": "^4.17.21",
|
||||||
"plugin:vue/recommended",
|
"md5": "^2.3.0",
|
||||||
"plugin:prettier/recommended",
|
"postcss": "^8.4.7",
|
||||||
"eslint:recommended"
|
"prettier": "2.5.1",
|
||||||
],
|
"prettier-plugin-tailwindcss": "^0.1.8",
|
||||||
"parserOptions": {
|
"qrcode": "^1.5.0",
|
||||||
"parser": "babel-eslint"
|
"react": "^17.0.2",
|
||||||
},
|
"react-dom": "^17.0.2",
|
||||||
"globals": {
|
"react-hot-toast": "^2.2.0",
|
||||||
"ipcRenderer": "off"
|
"react-query": "^3.34.16",
|
||||||
},
|
"react-router-dom": "^6.2.2",
|
||||||
"rules": {}
|
"react-use": "^17.3.2",
|
||||||
},
|
"rollup-plugin-visualizer": "^5.6.0",
|
||||||
"browserslist": [
|
"sass": "^1.49.9",
|
||||||
"> 1%",
|
"tailwindcss": "^3.0.23",
|
||||||
"last 2 versions",
|
"typescript": "^4.6.2",
|
||||||
"not dead"
|
"unplugin-auto-import": "^0.6.4",
|
||||||
],
|
"valtio": "^1.3.1",
|
||||||
"husky": {
|
"valtio-persist": "^1.0.2",
|
||||||
"hooks": {
|
"vite": "^2.8.0",
|
||||||
"pre-commit": "npm run prettier"
|
"vite-plugin-resolve": "^1.5.2",
|
||||||
}
|
"vite-plugin-svg-icons": "^2.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
136
packages/main/index.ts
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
import {
|
||||||
|
BrowserWindow,
|
||||||
|
BrowserWindowConstructorOptions,
|
||||||
|
app,
|
||||||
|
shell,
|
||||||
|
} from 'electron'
|
||||||
|
import installExtension, {
|
||||||
|
REACT_DEVELOPER_TOOLS,
|
||||||
|
REDUX_DEVTOOLS,
|
||||||
|
} from 'electron-devtools-installer'
|
||||||
|
import Store from 'electron-store'
|
||||||
|
import { release } from 'os'
|
||||||
|
import { join } from 'path'
|
||||||
|
import Realm from 'realm'
|
||||||
|
import logger from './logger'
|
||||||
|
import './server'
|
||||||
|
|
||||||
|
const isWindows = process.platform === 'win32'
|
||||||
|
const isMac = process.platform === 'darwin'
|
||||||
|
const isLinux = process.platform === 'linux'
|
||||||
|
const isDev = !app.isPackaged
|
||||||
|
|
||||||
|
// Disable GPU Acceleration for Windows 7
|
||||||
|
if (release().startsWith('6.1')) app.disableHardwareAcceleration()
|
||||||
|
|
||||||
|
// Set application name for Windows 10+ notifications
|
||||||
|
if (process.platform === 'win32') app.setAppUserModelId(app.getName())
|
||||||
|
|
||||||
|
if (!app.requestSingleInstanceLock()) {
|
||||||
|
app.quit()
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TypedElectronStore {
|
||||||
|
window: {
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
x?: number
|
||||||
|
y?: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = new Store<TypedElectronStore>({
|
||||||
|
defaults: {
|
||||||
|
window: {
|
||||||
|
width: 1440,
|
||||||
|
height: 960,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
let win: BrowserWindow | null = null
|
||||||
|
|
||||||
|
async function createWindow() {
|
||||||
|
// Create window
|
||||||
|
const options: BrowserWindowConstructorOptions = {
|
||||||
|
title: 'Main window',
|
||||||
|
webPreferences: {
|
||||||
|
preload: join(__dirname, '../preload/index.cjs'),
|
||||||
|
},
|
||||||
|
width: store.get('window.width'),
|
||||||
|
height: store.get('window.height'),
|
||||||
|
minWidth: 1080,
|
||||||
|
minHeight: 720,
|
||||||
|
vibrancy: 'fullscreen-ui',
|
||||||
|
titleBarStyle: 'hiddenInset',
|
||||||
|
}
|
||||||
|
if (store.get('window')) {
|
||||||
|
options.x = store.get('window.x')
|
||||||
|
options.y = store.get('window.y')
|
||||||
|
}
|
||||||
|
win = new BrowserWindow(options)
|
||||||
|
|
||||||
|
// Web server
|
||||||
|
if (app.isPackaged || process.env['DEBUG']) {
|
||||||
|
win.loadFile(join(__dirname, '../renderer/index.html'))
|
||||||
|
} else {
|
||||||
|
const url = `http://${process.env['VITE_DEV_SERVER_HOST']}:${process.env['VITE_DEV_SERVER_PORT']}`
|
||||||
|
logger.info(`[index] Vite dev server running at: ${url}`)
|
||||||
|
|
||||||
|
win.loadURL(url)
|
||||||
|
win.webContents.openDevTools()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make all links open with the browser, not with the application
|
||||||
|
win.webContents.setWindowOpenHandler(({ url }) => {
|
||||||
|
if (url.startsWith('https:')) shell.openExternal(url)
|
||||||
|
return { action: 'deny' }
|
||||||
|
})
|
||||||
|
|
||||||
|
// Save window position
|
||||||
|
const saveBounds = () => {
|
||||||
|
const bounds = win?.getBounds()
|
||||||
|
if (bounds) {
|
||||||
|
store.set('window', bounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
win.on('resized', saveBounds)
|
||||||
|
win.on('moved', saveBounds)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.whenReady().then(async () => {
|
||||||
|
createWindow()
|
||||||
|
|
||||||
|
// Install devtool extension
|
||||||
|
if (isDev) {
|
||||||
|
installExtension(REACT_DEVELOPER_TOOLS.id).catch(err =>
|
||||||
|
console.log('An error occurred: ', err)
|
||||||
|
)
|
||||||
|
installExtension(REDUX_DEVTOOLS.id).catch(err =>
|
||||||
|
console.log('An error occurred: ', err)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
win = null
|
||||||
|
if (process.platform !== 'darwin') app.quit()
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('second-instance', () => {
|
||||||
|
if (win) {
|
||||||
|
// Focus on the main window if the user tried to open another
|
||||||
|
if (win.isMinimized()) win.restore()
|
||||||
|
win.focus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('activate', () => {
|
||||||
|
const allWindows = BrowserWindow.getAllWindows()
|
||||||
|
if (allWindows.length) {
|
||||||
|
allWindows[0].focus()
|
||||||
|
} else {
|
||||||
|
createWindow()
|
||||||
|
}
|
||||||
|
})
|
||||||
29
packages/main/logger.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import styles from 'ansi-styles'
|
||||||
|
import { app } from 'electron'
|
||||||
|
import logger from 'electron-log'
|
||||||
|
|
||||||
|
const color = (hex: string, text: string) => {
|
||||||
|
return `${styles.color.ansi(styles.hexToAnsi(hex))}${text}${
|
||||||
|
styles.color.close
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.transports.console.format = `${color(
|
||||||
|
'38bdf8',
|
||||||
|
'[main]'
|
||||||
|
)} {h}:{i}:{s}.{ms}{scope} › {text}`
|
||||||
|
|
||||||
|
logger.transports.file.level = app.isPackaged ? 'info' : 'debug'
|
||||||
|
logger.info(
|
||||||
|
color(
|
||||||
|
'335eea',
|
||||||
|
`\n\n██╗ ██╗███████╗███████╗██████╗ ██╗ █████╗ ██╗ ██╗███╗ ███╗██╗ ██╗███████╗██╗ ██████╗
|
||||||
|
╚██╗ ██╔╝██╔════╝██╔════╝██╔══██╗██║ ██╔══██╗╚██╗ ██╔╝████╗ ████║██║ ██║██╔════╝██║██╔════╝
|
||||||
|
╚████╔╝ █████╗ ███████╗██████╔╝██║ ███████║ ╚████╔╝ ██╔████╔██║██║ ██║███████╗██║██║
|
||||||
|
╚██╔╝ ██╔══╝ ╚════██║██╔═══╝ ██║ ██╔══██║ ╚██╔╝ ██║╚██╔╝██║██║ ██║╚════██║██║██║
|
||||||
|
██║ ███████╗███████║██║ ███████╗██║ ██║ ██║ ██║ ╚═╝ ██║╚██████╔╝███████║██║╚██████╗
|
||||||
|
╚═╝ ╚══════╝╚══════╝╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═════╝\n`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
export default logger
|
||||||
45
packages/main/server.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { pathCase } from 'change-case'
|
||||||
|
import cookieParser from 'cookie-parser'
|
||||||
|
import express, { Request, Response } from 'express'
|
||||||
|
import logger from './logger'
|
||||||
|
|
||||||
|
const neteaseApi = require('NeteaseCloudMusicApi')
|
||||||
|
|
||||||
|
const app = express()
|
||||||
|
app.use(cookieParser())
|
||||||
|
const port = Number(process.env['ELECTRON_DEV_NETEASE_API_PORT'] ?? 3000)
|
||||||
|
|
||||||
|
Object.entries(neteaseApi).forEach(([name, handler]) => {
|
||||||
|
if (['serveNcmApi', 'getModulesDefinitions'].includes(name)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrappedHandler = async (req: Request, res: Response) => {
|
||||||
|
logger.info(`[server] Handling request: ${req.path}`)
|
||||||
|
try {
|
||||||
|
const result = await handler({
|
||||||
|
...req.query,
|
||||||
|
// cookie:
|
||||||
|
// 'MUSIC_U=1239b6c1217d8cd240df9c8fa15e99a62f9aaac86baa7a8aa3166acbad267cd8a237494327fc3ec043124f3fcebe94e446b14e3f0c3f8af9fe5c85647582a507',
|
||||||
|
// cookie: req.headers.cookie,
|
||||||
|
cookie: `MUSIC_U=${req.cookies['MUSIC_U']}`,
|
||||||
|
})
|
||||||
|
res.send(result.body)
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).send(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get(
|
||||||
|
`/netease/${pathCase(name)}`,
|
||||||
|
async (req: Request, res: Response) => await wrappedHandler(req, res)
|
||||||
|
)
|
||||||
|
app.post(
|
||||||
|
`/netease/${pathCase(name)}`,
|
||||||
|
async (req: Request, res: Response) => await wrappedHandler(req, res)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.listen(port, () => {
|
||||||
|
logger.info(`[server] API server listening on port ${port}`)
|
||||||
|
})
|
||||||
32
packages/main/vite.config.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import dotenv from 'dotenv'
|
||||||
|
import { builtinModules } from 'module'
|
||||||
|
import path from 'path'
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import pkg from '../../package.json'
|
||||||
|
import esm2cjs from '../../scripts/vite-plugin-esm2cjs'
|
||||||
|
|
||||||
|
dotenv.config({
|
||||||
|
path: path.resolve(process.cwd(), '.env'),
|
||||||
|
})
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
root: __dirname,
|
||||||
|
build: {
|
||||||
|
outDir: '../../dist/main',
|
||||||
|
emptyOutDir: true,
|
||||||
|
lib: {
|
||||||
|
entry: 'index.ts',
|
||||||
|
formats: ['cjs'],
|
||||||
|
fileName: () => '[name].cjs',
|
||||||
|
},
|
||||||
|
minify: process.env./* from mode option */ NODE_ENV === 'production',
|
||||||
|
sourcemap: process.env./* from mode option */ NODE_ENV === 'debug',
|
||||||
|
rollupOptions: {
|
||||||
|
external: [
|
||||||
|
'electron',
|
||||||
|
...builtinModules,
|
||||||
|
...Object.keys(pkg.dependencies || {}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
36
packages/preload/index.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { contextBridge, ipcRenderer } from 'electron'
|
||||||
|
import fs from 'fs'
|
||||||
|
import { useLoading } from './loading'
|
||||||
|
import { domReady } from './utils'
|
||||||
|
|
||||||
|
const { appendLoading, removeLoading } = useLoading()
|
||||||
|
|
||||||
|
;(async () => {
|
||||||
|
await domReady()
|
||||||
|
|
||||||
|
appendLoading()
|
||||||
|
})()
|
||||||
|
|
||||||
|
// --------- Expose some API to the Renderer process. ---------
|
||||||
|
contextBridge.exposeInMainWorld('fs', fs)
|
||||||
|
contextBridge.exposeInMainWorld('removeLoading', removeLoading)
|
||||||
|
contextBridge.exposeInMainWorld('ipcRenderer', withPrototype(ipcRenderer))
|
||||||
|
|
||||||
|
// `exposeInMainWorld` can't detect attributes and methods of `prototype`, manually patching it.
|
||||||
|
function withPrototype(obj: Record<string, any>) {
|
||||||
|
const protos = Object.getPrototypeOf(obj)
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(protos)) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(obj, key)) continue
|
||||||
|
|
||||||
|
if (typeof value === 'function') {
|
||||||
|
// Some native APIs, like `NodeJS.EventEmitter['on']`, don't work in the Renderer process. Wrapping them into a function.
|
||||||
|
obj[key] = function (...args: any) {
|
||||||
|
return value.call(obj, ...args)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
obj[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
54
packages/preload/loading.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
/**
|
||||||
|
* https://tobiasahlin.com/spinkit
|
||||||
|
* https://connoratherton.com/loaders
|
||||||
|
* https://projects.lukehaas.me/css-loaders
|
||||||
|
* https://matejkustec.github.io/SpinThatShit
|
||||||
|
*/
|
||||||
|
export function useLoading() {
|
||||||
|
const className = `loaders-css__square-spin`
|
||||||
|
const styleContent = `
|
||||||
|
@keyframes square-spin {
|
||||||
|
25% { transform: perspective(100px) rotateX(180deg) rotateY(0); }
|
||||||
|
50% { transform: perspective(100px) rotateX(180deg) rotateY(180deg); }
|
||||||
|
75% { transform: perspective(100px) rotateX(0) rotateY(180deg); }
|
||||||
|
100% { transform: perspective(100px) rotateX(0) rotateY(0); }
|
||||||
|
}
|
||||||
|
.${className} > div {
|
||||||
|
animation-fill-mode: both;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
background: #fff;
|
||||||
|
animation: square-spin 3s 0s cubic-bezier(0.09, 0.57, 0.49, 0.9) infinite;
|
||||||
|
}
|
||||||
|
.app-loading-wrap {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #282c34;
|
||||||
|
z-index: 9;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const oStyle = document.createElement('style')
|
||||||
|
const oDiv = document.createElement('div')
|
||||||
|
|
||||||
|
oStyle.id = 'app-loading-style'
|
||||||
|
oStyle.innerHTML = styleContent
|
||||||
|
oDiv.className = 'app-loading-wrap'
|
||||||
|
oDiv.innerHTML = `<div class="${className}"><div></div></div>`
|
||||||
|
|
||||||
|
return {
|
||||||
|
appendLoading() {
|
||||||
|
document.head.appendChild(oStyle)
|
||||||
|
document.body.appendChild(oDiv)
|
||||||
|
},
|
||||||
|
removeLoading() {
|
||||||
|
document.head.removeChild(oStyle)
|
||||||
|
document.body.removeChild(oDiv)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
16
packages/preload/utils.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
/** Document ready */
|
||||||
|
export const domReady = (
|
||||||
|
condition: DocumentReadyState[] = ['complete', 'interactive']
|
||||||
|
) => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
if (condition.includes(document.readyState)) {
|
||||||
|
resolve(true)
|
||||||
|
} else {
|
||||||
|
document.addEventListener('readystatechange', () => {
|
||||||
|
if (condition.includes(document.readyState)) {
|
||||||
|
resolve(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
30
packages/preload/vite.config.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import dotenv from 'dotenv'
|
||||||
|
import { builtinModules } from 'module'
|
||||||
|
import path from 'path'
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import pkg from '../../package.json'
|
||||||
|
|
||||||
|
dotenv.config({
|
||||||
|
path: path.resolve(process.cwd(), '.env'),
|
||||||
|
})
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
root: __dirname,
|
||||||
|
build: {
|
||||||
|
outDir: '../../dist/preload',
|
||||||
|
emptyOutDir: true,
|
||||||
|
lib: {
|
||||||
|
entry: 'index.ts',
|
||||||
|
formats: ['cjs'],
|
||||||
|
fileName: () => '[name].cjs',
|
||||||
|
},
|
||||||
|
minify: process.env./* from mode option */ NODE_ENV === 'production',
|
||||||
|
rollupOptions: {
|
||||||
|
external: [
|
||||||
|
'electron',
|
||||||
|
...builtinModules,
|
||||||
|
...Object.keys(pkg.dependencies || {}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
17
packages/renderer/index.html
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/src/public/favicon.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
|
||||||
|
<title>YesPlayMusic</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
BIN
packages/renderer/public/fonts/Manrope-Bold.ttf
Executable file
BIN
packages/renderer/public/fonts/Manrope-ExtraBold.ttf
Executable file
BIN
packages/renderer/public/fonts/Manrope-ExtraLight.ttf
Executable file
BIN
packages/renderer/public/fonts/Manrope-Light.ttf
Executable file
BIN
packages/renderer/public/fonts/Manrope-Medium.ttf
Executable file
BIN
packages/renderer/public/fonts/Manrope-Regular.ttf
Executable file
BIN
packages/renderer/public/fonts/Manrope-SemiBold.ttf
Executable file
BIN
packages/renderer/public/fonts/PlusJakartaSans-Bold.woff2
Normal file
BIN
packages/renderer/public/fonts/PlusJakartaSans-BoldItalic.woff2
Normal file
BIN
packages/renderer/public/fonts/PlusJakartaSans-ExtraBold.woff2
Normal file
BIN
packages/renderer/public/fonts/PlusJakartaSans-ExtraLight.woff2
Normal file
BIN
packages/renderer/public/fonts/PlusJakartaSans-Italic.woff2
Normal file
BIN
packages/renderer/public/fonts/PlusJakartaSans-Light.woff2
Normal file
BIN
packages/renderer/public/fonts/PlusJakartaSans-LightItalic.woff2
Normal file
BIN
packages/renderer/public/fonts/PlusJakartaSans-Medium.woff2
Normal file
BIN
packages/renderer/public/fonts/PlusJakartaSans-Regular.woff2
Normal file
36
packages/renderer/src/App.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { Toaster } from 'react-hot-toast'
|
||||||
|
import { QueryClientProvider } from 'react-query'
|
||||||
|
import { ReactQueryDevtools } from 'react-query/devtools'
|
||||||
|
import Player from '@/components/Player'
|
||||||
|
import Sidebar from '@/components/Sidebar'
|
||||||
|
import reactQueryClient from '@/utils/reactQueryClient'
|
||||||
|
import Main from './components/Main'
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
return (
|
||||||
|
<QueryClientProvider client={reactQueryClient}>
|
||||||
|
<div id="layout" className="grid select-none grid-cols-[16rem_auto]">
|
||||||
|
<Sidebar />
|
||||||
|
<Main />
|
||||||
|
<Player />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Toaster position="bottom-center" containerStyle={{ bottom: '5rem' }} />
|
||||||
|
|
||||||
|
{/* Devtool */}
|
||||||
|
<ReactQueryDevtools
|
||||||
|
initialIsOpen={false}
|
||||||
|
toggleButtonProps={{
|
||||||
|
style: {
|
||||||
|
position: 'fixed',
|
||||||
|
right: '0',
|
||||||
|
left: 'auto',
|
||||||
|
bottom: '4rem',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</QueryClientProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App
|
||||||
29
packages/renderer/src/api/album.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export enum AlbumApiNames {
|
||||||
|
FETCH_ALBUM = 'fetchAlbum',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 专辑详情
|
||||||
|
export interface FetchAlbumParams {
|
||||||
|
id: number
|
||||||
|
}
|
||||||
|
interface FetchAlbumResponse {
|
||||||
|
code: number
|
||||||
|
resourceState: boolean
|
||||||
|
album: Album
|
||||||
|
songs: Track[]
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
export function fetchAlbum(
|
||||||
|
params: FetchAlbumParams,
|
||||||
|
noCache: boolean
|
||||||
|
): Promise<FetchAlbumResponse> {
|
||||||
|
const otherParams: { timestamp?: number } = {}
|
||||||
|
if (noCache) otherParams.timestamp = new Date().getTime()
|
||||||
|
return request({
|
||||||
|
url: '/album',
|
||||||
|
method: 'get',
|
||||||
|
params: { ...params, ...otherParams },
|
||||||
|
})
|
||||||
|
}
|
||||||
51
packages/renderer/src/api/artist.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export enum ArtistApiNames {
|
||||||
|
FETCH_ARTIST = 'fetchArtist',
|
||||||
|
FETCH_ARTIST_ALBUMS = 'fetchArtistAlbums',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 歌手详情
|
||||||
|
export interface FetchArtistParams {
|
||||||
|
id: number
|
||||||
|
}
|
||||||
|
interface FetchArtistResponse {
|
||||||
|
code: number
|
||||||
|
more: boolean
|
||||||
|
artist: Artist
|
||||||
|
hotSongs: Track[]
|
||||||
|
}
|
||||||
|
export function fetchArtist(
|
||||||
|
params: FetchArtistParams,
|
||||||
|
noCache: boolean
|
||||||
|
): Promise<FetchArtistResponse> {
|
||||||
|
const otherParams: { timestamp?: number } = {}
|
||||||
|
if (noCache) otherParams.timestamp = new Date().getTime()
|
||||||
|
return request({
|
||||||
|
url: '/artists',
|
||||||
|
method: 'get',
|
||||||
|
params: { ...params, ...otherParams },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取歌手的专辑列表
|
||||||
|
export interface FetchArtistAlbumsParams {
|
||||||
|
id: number
|
||||||
|
limit?: number // default: 50
|
||||||
|
offset?: number // default: 0
|
||||||
|
}
|
||||||
|
interface FetchArtistAlbumsResponse {
|
||||||
|
code: number
|
||||||
|
hotAlbums: Album[]
|
||||||
|
more: boolean
|
||||||
|
artist: Artist
|
||||||
|
}
|
||||||
|
export function fetchArtistAlbums(
|
||||||
|
params: FetchArtistAlbumsParams
|
||||||
|
): Promise<FetchArtistAlbumsResponse> {
|
||||||
|
return request({
|
||||||
|
url: 'artist/album',
|
||||||
|
method: 'get',
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
115
packages/renderer/src/api/auth.ts
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
import type { fetchUserAccountResponse } from '@/api/user'
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 手机号登录
|
||||||
|
interface LoginWithPhoneParams {
|
||||||
|
countrycode: number | string
|
||||||
|
phone: string
|
||||||
|
password?: string
|
||||||
|
md5_password?: string
|
||||||
|
captcha?: string | number
|
||||||
|
}
|
||||||
|
export interface LoginWithPhoneResponse {
|
||||||
|
loginType: number
|
||||||
|
code: number
|
||||||
|
cookie: string
|
||||||
|
}
|
||||||
|
export function loginWithPhone(
|
||||||
|
params: LoginWithPhoneParams
|
||||||
|
): Promise<LoginWithPhoneResponse> {
|
||||||
|
return request({
|
||||||
|
url: '/login/cellphone',
|
||||||
|
method: 'post',
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 邮箱登录
|
||||||
|
export interface LoginWithEmailParams {
|
||||||
|
email: string
|
||||||
|
password?: string
|
||||||
|
md5_password?: string
|
||||||
|
}
|
||||||
|
export interface loginWithEmailResponse extends fetchUserAccountResponse {
|
||||||
|
code: number
|
||||||
|
cookie: string
|
||||||
|
loginType: number
|
||||||
|
token: string
|
||||||
|
binding: {
|
||||||
|
bindingTime: number
|
||||||
|
expired: boolean
|
||||||
|
expiresIn: number
|
||||||
|
id: number
|
||||||
|
refreshTime: number
|
||||||
|
tokenJsonStr: string
|
||||||
|
type: number
|
||||||
|
url: string
|
||||||
|
userId: number
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
export function loginWithEmail(
|
||||||
|
params: LoginWithEmailParams
|
||||||
|
): Promise<loginWithEmailResponse> {
|
||||||
|
return request({
|
||||||
|
url: '/login',
|
||||||
|
method: 'post',
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成二维码key
|
||||||
|
export interface fetchLoginQrCodeKeyResponse {
|
||||||
|
code: number
|
||||||
|
data: {
|
||||||
|
code: number
|
||||||
|
unikey: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function fetchLoginQrCodeKey(): Promise<fetchLoginQrCodeKeyResponse> {
|
||||||
|
return request({
|
||||||
|
url: '/login/qr/key',
|
||||||
|
method: 'get',
|
||||||
|
params: {
|
||||||
|
timestamp: new Date().getTime(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 二维码检测扫码状态接口
|
||||||
|
// 说明: 轮询此接口可获取二维码扫码状态,800为二维码过期,801为等待扫码,802为待确认,803为授权登录成功(803状态码下会返回cookies)
|
||||||
|
export interface CheckLoginQrCodeStatusParams {
|
||||||
|
key: string
|
||||||
|
}
|
||||||
|
export interface CheckLoginQrCodeStatusResponse {
|
||||||
|
code: number
|
||||||
|
message?: string
|
||||||
|
cookie?: string
|
||||||
|
}
|
||||||
|
export function checkLoginQrCodeStatus(
|
||||||
|
params: CheckLoginQrCodeStatusParams
|
||||||
|
): Promise<CheckLoginQrCodeStatusResponse> {
|
||||||
|
return request({
|
||||||
|
url: '/login/qr/check',
|
||||||
|
method: 'get',
|
||||||
|
params: {
|
||||||
|
key: params.key,
|
||||||
|
timestamp: new Date().getTime(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新登录
|
||||||
|
export function refreshCookie() {
|
||||||
|
return request({
|
||||||
|
url: '/login/refresh',
|
||||||
|
method: 'post',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 退出登录
|
||||||
|
export function logout() {
|
||||||
|
return request({
|
||||||
|
url: '/logout',
|
||||||
|
method: 'post',
|
||||||
|
})
|
||||||
|
}
|
||||||
57
packages/renderer/src/api/playlist.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export enum PlaylistApiNames {
|
||||||
|
FETCH_PLAYLIST = 'fetchPlaylist',
|
||||||
|
FETCH_RECOMMENDED_PLAYLISTS = 'fetchRecommendedPlaylists',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 歌单详情
|
||||||
|
export interface FetchPlaylistParams {
|
||||||
|
id: number
|
||||||
|
s?: number // 歌单最近的 s 个收藏者
|
||||||
|
}
|
||||||
|
interface FetchPlaylistResponse {
|
||||||
|
code: number
|
||||||
|
playlist: Playlist
|
||||||
|
privileges: unknown // TODO: unknown type
|
||||||
|
relatedVideos: null
|
||||||
|
resEntrance: null
|
||||||
|
sharedPrivilege: null
|
||||||
|
urls: null
|
||||||
|
}
|
||||||
|
export function fetchPlaylist(
|
||||||
|
params: FetchPlaylistParams,
|
||||||
|
noCache: boolean
|
||||||
|
): Promise<FetchPlaylistResponse> {
|
||||||
|
const otherParams: { timestamp?: number } = {}
|
||||||
|
if (noCache) otherParams.timestamp = new Date().getTime()
|
||||||
|
if (!params.s) params.s = 0 // 网易云默认返回8个收藏者,这里设置为0,减少返回的JSON体积
|
||||||
|
return request({
|
||||||
|
url: '/playlist/detail',
|
||||||
|
method: 'get',
|
||||||
|
params: {
|
||||||
|
...params,
|
||||||
|
...otherParams,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 推荐歌单
|
||||||
|
interface FetchRecommendedPlaylistsParams {
|
||||||
|
limit?: number
|
||||||
|
}
|
||||||
|
export interface FetchRecommendedPlaylistsResponse {
|
||||||
|
code: number
|
||||||
|
category: number
|
||||||
|
hasTaste: boolean
|
||||||
|
result: Playlist[]
|
||||||
|
}
|
||||||
|
export function fetchRecommendedPlaylists(
|
||||||
|
params: FetchRecommendedPlaylistsParams
|
||||||
|
): Promise<FetchRecommendedPlaylistsResponse> {
|
||||||
|
return request({
|
||||||
|
url: '/personalized',
|
||||||
|
method: 'get',
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
100
packages/renderer/src/api/search.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export enum SearchApiNames {
|
||||||
|
SEARCH = 'search',
|
||||||
|
MULTI_MATCH_SEARCH = 'multiMatchSearch',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
export enum SearchTypes {
|
||||||
|
SINGLE = 1,
|
||||||
|
ALBUM = 10,
|
||||||
|
ARTIST = 100,
|
||||||
|
PLAYLIST = 1000,
|
||||||
|
USER = 1002,
|
||||||
|
MV = 1004,
|
||||||
|
LYRICS = 1006,
|
||||||
|
RADIO = 1009,
|
||||||
|
VIDEO = 1014,
|
||||||
|
ALL = 1018,
|
||||||
|
}
|
||||||
|
export interface SearchParams {
|
||||||
|
keywords: string
|
||||||
|
limit?: number // 返回数量 , 默认为 30
|
||||||
|
offset?: number // 偏移数量,用于分页 , 如 : 如 :( 页数 -1)*30, 其中 30 为 limit 的值 , 默认为 0
|
||||||
|
type?: SearchTypes // type: 搜索类型
|
||||||
|
}
|
||||||
|
interface SearchResponse {
|
||||||
|
code: number
|
||||||
|
result: {
|
||||||
|
album: {
|
||||||
|
albums: Album[]
|
||||||
|
more: boolean
|
||||||
|
moreText: string
|
||||||
|
resourceIds: number[]
|
||||||
|
}
|
||||||
|
artist: {
|
||||||
|
artists: Artist[]
|
||||||
|
more: boolean
|
||||||
|
moreText: string
|
||||||
|
resourceIds: number[]
|
||||||
|
}
|
||||||
|
playList: {
|
||||||
|
playLists: Playlist[]
|
||||||
|
more: boolean
|
||||||
|
moreText: string
|
||||||
|
resourceIds: number[]
|
||||||
|
}
|
||||||
|
song: {
|
||||||
|
songs: Track[]
|
||||||
|
more: boolean
|
||||||
|
moreText: string
|
||||||
|
resourceIds: number[]
|
||||||
|
}
|
||||||
|
user: {
|
||||||
|
users: User[]
|
||||||
|
more: boolean
|
||||||
|
moreText: string
|
||||||
|
resourceIds: number[]
|
||||||
|
}
|
||||||
|
circle: unknown
|
||||||
|
new_mlog: unknown
|
||||||
|
order: string[]
|
||||||
|
rec_type: null
|
||||||
|
rec_query: null[]
|
||||||
|
sim_query: unknown
|
||||||
|
voice: unknown
|
||||||
|
voiceList: unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function search(params: SearchParams): Promise<SearchResponse> {
|
||||||
|
return request({
|
||||||
|
url: '/search',
|
||||||
|
method: 'get',
|
||||||
|
params: params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索多重匹配
|
||||||
|
export interface MultiMatchSearchParams {
|
||||||
|
keywords: string
|
||||||
|
}
|
||||||
|
interface MultiMatchSearchResponse {
|
||||||
|
code: number
|
||||||
|
result: {
|
||||||
|
album: Album[]
|
||||||
|
artist: Artist[]
|
||||||
|
playlist: Playlist[]
|
||||||
|
orpheus: unknown
|
||||||
|
orders: Array<'artist' | 'album'>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function multiMatchSearch(
|
||||||
|
params: MultiMatchSearchParams
|
||||||
|
): Promise<MultiMatchSearchResponse> {
|
||||||
|
return request({
|
||||||
|
url: '/search/multimatch',
|
||||||
|
method: 'get',
|
||||||
|
params: params,
|
||||||
|
})
|
||||||
|
}
|
||||||
73
packages/renderer/src/api/track.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export enum TrackApiNames {
|
||||||
|
FETCH_TRACKS = 'fetchTracks',
|
||||||
|
FETCH_AUDIO_SOURCE = 'fetchAudioSource',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取歌曲详情
|
||||||
|
export interface FetchTracksParams {
|
||||||
|
ids: number[]
|
||||||
|
}
|
||||||
|
interface FetchTracksResponse {
|
||||||
|
code: number
|
||||||
|
songs: Track[]
|
||||||
|
privileges: {
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function fetchTracks(
|
||||||
|
params: FetchTracksParams
|
||||||
|
): Promise<FetchTracksResponse> {
|
||||||
|
return request({
|
||||||
|
url: '/song/detail',
|
||||||
|
method: 'get',
|
||||||
|
params: {
|
||||||
|
ids: params.ids.join(','),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取音源URL
|
||||||
|
export interface FetchAudioSourceParams {
|
||||||
|
id: number
|
||||||
|
br?: number // bitrate, default 999000,320000 = 320kbps
|
||||||
|
}
|
||||||
|
interface FetchAudioSourceResponse {
|
||||||
|
code: number
|
||||||
|
data: {
|
||||||
|
br: number
|
||||||
|
canExtend: boolean
|
||||||
|
code: number
|
||||||
|
encodeType: 'mp3' | null
|
||||||
|
expi: number
|
||||||
|
fee: number
|
||||||
|
flag: number
|
||||||
|
freeTimeTrialPrivilege: {
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
|
freeTrialPrivilege: {
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
|
freeTrialInfo: null
|
||||||
|
gain: number
|
||||||
|
id: number
|
||||||
|
level: 'standard' | 'null'
|
||||||
|
md5: string | null
|
||||||
|
payed: number
|
||||||
|
size: number
|
||||||
|
type: 'mp3' | null
|
||||||
|
uf: null
|
||||||
|
url: string | null
|
||||||
|
urlSource: number
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
export function fetchAudioSource(
|
||||||
|
params: FetchAudioSourceParams
|
||||||
|
): Promise<FetchAudioSourceResponse> {
|
||||||
|
return request({
|
||||||
|
url: '/song/url',
|
||||||
|
method: 'get',
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,10 @@
|
||||||
import request from '@/utils/request';
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export enum UserApiNames {
|
||||||
|
FETCH_USER_ACCOUNT = 'fetchUserAccount',
|
||||||
|
FETCH_USER_LIKED_SONGS_IDS = 'fetchUserLikedSongsIDs',
|
||||||
|
FETCH_USER_PLAYLISTS = 'fetchUserPlaylists',
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户详情
|
* 获取用户详情
|
||||||
|
|
@ -14,74 +20,118 @@ export function userDetail(uid) {
|
||||||
uid,
|
uid,
|
||||||
timestamp: new Date().getTime(),
|
timestamp: new Date().getTime(),
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 获取账号详情
|
||||||
* 获取账号详情
|
export interface fetchUserAccountResponse {
|
||||||
* 说明 : 登录后调用此接口 ,可获取用户账号信息
|
code: number
|
||||||
*/
|
account: {
|
||||||
export function userAccount() {
|
anonimousUser: boolean
|
||||||
|
ban: number
|
||||||
|
baoyueVersion: number
|
||||||
|
createTime: number
|
||||||
|
donateVersion: number
|
||||||
|
id: number
|
||||||
|
paidFee: boolean
|
||||||
|
status: number
|
||||||
|
tokenVersion: number
|
||||||
|
type: number
|
||||||
|
userName: string
|
||||||
|
vipType: number
|
||||||
|
whitelistAuthority: number
|
||||||
|
} | null
|
||||||
|
profile: {
|
||||||
|
userId: number
|
||||||
|
userType: number
|
||||||
|
nickname: string
|
||||||
|
avatarImgId: number
|
||||||
|
avatarUrl: string
|
||||||
|
backgroundImgId: number
|
||||||
|
backgroundUrl: string
|
||||||
|
signature: string
|
||||||
|
createTime: number
|
||||||
|
userName: string
|
||||||
|
accountType: number
|
||||||
|
shortUserName: string
|
||||||
|
birthday: number
|
||||||
|
authority: number
|
||||||
|
gender: number
|
||||||
|
accountStatus: number
|
||||||
|
province: number
|
||||||
|
city: number
|
||||||
|
authStatus: number
|
||||||
|
description: string | null
|
||||||
|
detailDescription: string | null
|
||||||
|
defaultAvatar: boolean
|
||||||
|
expertTags: [] | null
|
||||||
|
experts: [] | null
|
||||||
|
djStatus: number
|
||||||
|
locationStatus: number
|
||||||
|
vipType: number
|
||||||
|
followed: boolean
|
||||||
|
mutual: boolean
|
||||||
|
authenticated: boolean
|
||||||
|
lastLoginTime: number
|
||||||
|
lastLoginIP: string
|
||||||
|
remarkName: string | null
|
||||||
|
viptypeVersion: number
|
||||||
|
authenticationTypes: number
|
||||||
|
avatarDetail: string | null
|
||||||
|
anchor: boolean
|
||||||
|
} | null
|
||||||
|
}
|
||||||
|
export function fetchUserAccount(): Promise<fetchUserAccountResponse> {
|
||||||
return request({
|
return request({
|
||||||
url: '/user/account',
|
url: '/user/account',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
params: {
|
||||||
timestamp: new Date().getTime(),
|
timestamp: new Date().getTime(),
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 获取用户歌单
|
||||||
* 获取用户歌单
|
export interface FetchUserPlaylistsParams {
|
||||||
* 说明 : 登录后调用此接口 , 传入用户 id, 可以获取用户歌单
|
uid: number
|
||||||
* - uid : 用户 id
|
offset: number
|
||||||
* - limit : 返回数量 , 默认为 30
|
limit?: number // default 30
|
||||||
* - offset : 偏移数量,用于分页 , 如 :( 页数 -1)*30, 其中 30 为 limit 的值 , 默认为 0
|
}
|
||||||
* @param {Object} params
|
interface FetchUserPlaylistsResponse {
|
||||||
* @param {number} params.uid
|
code: number
|
||||||
* @param {number} params.limit
|
more: false
|
||||||
* @param {number=} params.offset
|
version: string
|
||||||
*/
|
playlist: Playlist[]
|
||||||
export function userPlaylist(params) {
|
}
|
||||||
|
export function fetchUserPlaylists(
|
||||||
|
params: FetchUserPlaylistsParams
|
||||||
|
): Promise<FetchUserPlaylistsResponse> {
|
||||||
return request({
|
return request({
|
||||||
url: '/user/playlist',
|
url: '/user/playlist',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params,
|
params,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export interface FetchUserLikedSongsIDsParams {
|
||||||
* 获取用户播放记录
|
uid: number
|
||||||
* 说明 : 登录后调用此接口 , 传入用户 id, 可获取用户播放记录
|
|
||||||
* - uid : 用户 id
|
|
||||||
* - type : type=1 时只返回 weekData, type=0 时返回 allData
|
|
||||||
* @param {Object} params
|
|
||||||
* @param {number} params.uid
|
|
||||||
* @param {number} params.type
|
|
||||||
*/
|
|
||||||
export function userPlayHistory(params) {
|
|
||||||
return request({
|
|
||||||
url: '/user/record',
|
|
||||||
method: 'get',
|
|
||||||
params,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
interface FetchUserLikedSongsIDsResponse {
|
||||||
/**
|
code: number
|
||||||
* 喜欢音乐列表(需要登录)
|
checkPoint: number
|
||||||
* 说明 : 调用此接口 , 传入用户 id, 可获取已喜欢音乐id列表(id数组)
|
ids: number[]
|
||||||
* - uid: 用户 id
|
}
|
||||||
* @param {number} uid
|
export function fetchUserLikedSongsIDs(
|
||||||
*/
|
params: FetchUserLikedSongsIDsParams
|
||||||
export function userLikedSongsIDs(uid) {
|
): Promise<FetchUserLikedSongsIDsResponse> {
|
||||||
return request({
|
return request({
|
||||||
url: '/likelist',
|
url: '/likelist',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
params: {
|
||||||
uid,
|
uid: params.uid,
|
||||||
timestamp: new Date().getTime(),
|
timestamp: new Date().getTime(),
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -98,7 +148,7 @@ export function dailySignin(type = 0) {
|
||||||
type,
|
type,
|
||||||
timestamp: new Date().getTime(),
|
timestamp: new Date().getTime(),
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -118,7 +168,7 @@ export function likedAlbums(params) {
|
||||||
limit: params.limit,
|
limit: params.limit,
|
||||||
timestamp: new Date().getTime(),
|
timestamp: new Date().getTime(),
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -133,7 +183,7 @@ export function likedArtists(params) {
|
||||||
limit: params.limit,
|
limit: params.limit,
|
||||||
timestamp: new Date().getTime(),
|
timestamp: new Date().getTime(),
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -148,15 +198,15 @@ export function likedMVs(params) {
|
||||||
limit: params.limit,
|
limit: params.limit,
|
||||||
timestamp: new Date().getTime(),
|
timestamp: new Date().getTime(),
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上传歌曲到云盘(需要登录)
|
* 上传歌曲到云盘(需要登录)
|
||||||
*/
|
*/
|
||||||
export function uploadSong(file) {
|
export function uploadSong(file) {
|
||||||
let formData = new FormData();
|
let formData = new FormData()
|
||||||
formData.append('songFile', file);
|
formData.append('songFile', file)
|
||||||
return request({
|
return request({
|
||||||
url: '/cloud',
|
url: '/cloud',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
|
|
@ -169,8 +219,8 @@ export function uploadSong(file) {
|
||||||
},
|
},
|
||||||
timeout: 200000,
|
timeout: 200000,
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
alert(`上传失败,Error: ${error}`);
|
alert(`上传失败,Error: ${error}`)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -183,12 +233,12 @@ export function uploadSong(file) {
|
||||||
* @param {number=} params.offset
|
* @param {number=} params.offset
|
||||||
*/
|
*/
|
||||||
export function cloudDisk(params = {}) {
|
export function cloudDisk(params = {}) {
|
||||||
params.timestamp = new Date().getTime();
|
params.timestamp = new Date().getTime()
|
||||||
return request({
|
return request({
|
||||||
url: '/user/cloud',
|
url: '/user/cloud',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params,
|
params,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -202,7 +252,7 @@ export function cloudDiskTrackDetail(id) {
|
||||||
timestamp: new Date().getTime(),
|
timestamp: new Date().getTime(),
|
||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -217,5 +267,5 @@ export function cloudDiskTrackDelete(id) {
|
||||||
timestamp: new Date().getTime(),
|
timestamp: new Date().getTime(),
|
||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
1
packages/renderer/src/assets/icons/back.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="chevron-left" class="svg-inline--fa fa-chevron-left" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path fill="currentColor" d="M224 480c-8.188 0-16.38-3.125-22.62-9.375l-192-192c-12.5-12.5-12.5-32.75 0-45.25l192-192c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25L77.25 256l169.4 169.4c12.5 12.5 12.5 32.75 0 45.25C240.4 476.9 232.2 480 224 480z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 455 B |
1
packages/renderer/src/assets/icons/chevron-up.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="chevron-up" class="svg-inline--fa fa-chevron-up fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M240.971 130.524l194.343 194.343c9.373 9.373 9.373 24.569 0 33.941l-22.667 22.667c-9.357 9.357-24.522 9.375-33.901.04L224 227.495 69.255 381.516c-9.379 9.335-24.544 9.317-33.901-.04l-22.667-22.667c-9.373-9.373-9.373-24.569 0-33.941L207.03 130.525c9.372-9.373 24.568-9.373 33.941-.001z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 525 B |
1
packages/renderer/src/assets/icons/compass.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="48px" height="48px"><circle cx="12" cy="12" r="10" opacity=".35"/><path d="M15.839,6.559l-5.7,1.9c-0.793,0.264-1.416,0.887-1.68,1.68l-1.9,5.7c-0.33,0.99,0.612,1.933,1.603,1.603l5.7-1.9 c0.793-0.264,1.416-0.887,1.68-1.68l1.9-5.7C17.771,7.171,16.829,6.228,15.839,6.559z M12,14c-1.105,0-2-0.895-2-2 c0-1.105,0.895-2,2-2s2,0.895,2,2C14,13.105,13.105,14,12,14z"/></svg>
|
||||||
|
After Width: | Height: | Size: 433 B |
1
packages/renderer/src/assets/icons/dislike.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="thumbs-down" class="svg-inline--fa fa-thumbs-down" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M96 32.04H32c-17.67 0-32 14.32-32 31.1v223.1c0 17.67 14.33 31.1 32 31.1h64c17.67 0 32-14.33 32-31.1V64.03C128 46.36 113.7 32.04 96 32.04zM467.3 240.2C475.1 231.7 480 220.4 480 207.9c0-23.47-16.87-42.92-39.14-47.09C445.3 153.6 448 145.1 448 135.1c0-21.32-14-39.18-33.25-45.43C415.5 87.12 416 83.61 416 79.98C416 53.47 394.5 32 368 32h-58.69c-34.61 0-68.28 11.22-95.97 31.98L179.2 89.57C167.1 98.63 160 112.9 160 127.1l.1074 160c0 0-.0234-.0234 0 0c.0703 13.99 6.123 27.94 17.91 37.36l16.3 13.03C276.2 403.9 239.4 480 302.5 480c30.96 0 49.47-24.52 49.47-48.11c0-15.15-11.76-58.12-34.52-96.02H464c26.52 0 48-21.47 48-47.98C512 262.5 492.2 241.9 467.3 240.2z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 889 B |
4
packages/renderer/src/assets/icons/dj.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
|
||||||
|
width="24" height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
style=" fill:#000000;"><path d="M19.971,14.583C19.985,14.39,20,14.197,20,14c0-0.513-0.053-1.014-0.145-1.5C19.947,12.014,20,11.513,20,11 c0-4.418-3.582-8-8-8s-8,3.582-8,8c0,0.513,0.053,1.014,0.145,1.5C4.053,12.986,4,13.487,4,14c0,0.197,0.015,0.39,0.029,0.583 C3.433,14.781,3,15.337,3,16c0,0.828,0.672,1.5,1.5,1.5c0.103,0,0.203-0.01,0.3-0.03C6.093,20.148,8.827,22,12,22 s5.907-1.852,7.2-4.53c0.097,0.02,0.197,0.03,0.3,0.03c0.828,0,1.5-0.672,1.5-1.5C21,15.337,20.567,14.781,19.971,14.583z" opacity=".35"></path><path d="M21,18h-2v-6h2c1.105,0,2,0.895,2,2v2C23,17.105,22.105,18,21,18z"></path><path d="M3,12h2v6H3c-1.105,0-2-0.895-2-2v-2C1,12.895,1.895,12,3,12z"></path><path d="M5,13c0-0.843,0-1.638,0-2c0-3.866,3.134-7,7-7s7,3.134,7,7c0,0.362,0,1.157,0,2h2c0-0.859,0-1.617,0-2c0-4.971-4.029-9-9-9 s-9,4.029-9,9c0,0.383,0,1.141,0,2H5z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 946 B |