mirror of
https://github.com/GiriNeko/YesPlayMusic.git
synced 2025-12-16 13:17:46 +00:00
feat: updates
This commit is contained in:
parent
08abf8229f
commit
fb21405bf9
33 changed files with 699 additions and 361 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -81,6 +81,7 @@ typings/
|
|||
# ----
|
||||
dist
|
||||
**/.tmp
|
||||
/tmp
|
||||
release
|
||||
.DS_Store
|
||||
dist-ssr
|
||||
|
|
|
|||
|
|
@ -30,9 +30,9 @@
|
|||
"realm": "^10.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@trivago/prettier-plugin-sort-imports": "^3.2.0",
|
||||
"@types/cookie-parser": "^1.4.2",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/express-fileupload": "^1.2.2",
|
||||
"@types/howler": "^2.2.6",
|
||||
"@types/js-cookie": "^3.0.1",
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
|
|
@ -60,10 +60,13 @@
|
|||
"eslint": "^8.11.0",
|
||||
"eslint-plugin-react": "^7.29.4",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"express-fileupload": "^1.3.1",
|
||||
"fast-folder-size": "^1.6.1",
|
||||
"howler": "^2.2.3",
|
||||
"js-cookie": "^3.0.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"md5": "^2.3.0",
|
||||
"music-metadata": "^7.12.2",
|
||||
"postcss": "^8.4.12",
|
||||
"prettier": "2.5.1",
|
||||
"prettier-plugin-tailwindcss": "^0.1.8",
|
||||
|
|
|
|||
267
packages/main/cache.ts
Normal file
267
packages/main/cache.ts
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
import { db, ModelNames, realm } from './database'
|
||||
import type { FetchTracksResponse } from '../renderer/src/api/track'
|
||||
import { app, ipcMain } from 'electron'
|
||||
import { Request, Response } from 'express'
|
||||
import logger from './logger'
|
||||
import fs from 'fs'
|
||||
import * as musicMetadata from 'music-metadata'
|
||||
|
||||
export async function setCache(api: string, data: any, query: any) {
|
||||
switch (api) {
|
||||
case 'user/account':
|
||||
case 'personalized':
|
||||
case 'likelist': {
|
||||
if (!data) return
|
||||
db.set(ModelNames.ACCOUNT_DATA, api, data)
|
||||
break
|
||||
}
|
||||
case 'user/playlist': {
|
||||
if (!data.playlist) return
|
||||
db.set(ModelNames.USER_PLAYLISTS, Number(query.uid), data)
|
||||
break
|
||||
}
|
||||
case 'song/detail': {
|
||||
if (!data.songs) return
|
||||
const tracks = (data as FetchTracksResponse).songs
|
||||
db.batchSet(
|
||||
ModelNames.TRACK,
|
||||
tracks.map(t => ({
|
||||
id: t.id,
|
||||
json: JSON.stringify(t),
|
||||
updateAt: Date.now(),
|
||||
}))
|
||||
)
|
||||
break
|
||||
}
|
||||
case 'album': {
|
||||
if (!data.album) return
|
||||
db.set(ModelNames.ALBUM, Number(data.album.id), data)
|
||||
break
|
||||
}
|
||||
case 'playlist/detail': {
|
||||
if (!data.playlist) return
|
||||
db.set(ModelNames.PLAYLIST, Number(data.playlist.id), data)
|
||||
break
|
||||
}
|
||||
case 'artist/album': {
|
||||
if (!data.hotAlbums) return
|
||||
db.set(ModelNames.ARTIST_ALBUMS, Number(data.artist.id), data)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the cache is expired
|
||||
* @param updateAt from database, milliseconds
|
||||
* @param staleTime minutes
|
||||
*/
|
||||
const isCacheExpired = (updateAt: number, staleTime: number) => {
|
||||
return Date.now() - updateAt > staleTime * 1000 * 60
|
||||
}
|
||||
|
||||
export function getCache(
|
||||
api: string,
|
||||
query: any,
|
||||
checkIsExpired: boolean = false
|
||||
): any {
|
||||
switch (api) {
|
||||
case 'user/account':
|
||||
case 'personalized':
|
||||
case 'likelist': {
|
||||
const data = db.get(ModelNames.ACCOUNT_DATA, api) as any
|
||||
if (data?.json) return JSON.parse(data.json)
|
||||
break
|
||||
}
|
||||
case 'user/playlist': {
|
||||
if (!query.uid) return
|
||||
const userPlaylists = db.get(
|
||||
ModelNames.USER_PLAYLISTS,
|
||||
Number(query?.uid)
|
||||
) as any
|
||||
if (userPlaylists?.json) return JSON.parse(userPlaylists.json)
|
||||
break
|
||||
}
|
||||
case 'song/detail': {
|
||||
const ids: string[] = query?.ids.split(',')
|
||||
const idsQuery = ids.map(id => `id = ${id}`).join(' OR ')
|
||||
const tracksRaw = realm
|
||||
.objects(ModelNames.TRACK)
|
||||
.filtered(`(${idsQuery})`)
|
||||
if (tracksRaw.length !== ids.length) {
|
||||
return
|
||||
}
|
||||
const tracks = ids.map(id => {
|
||||
const track = tracksRaw.find(t => t.id === Number(id)) as any
|
||||
return JSON.parse(track.json)
|
||||
})
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
songs: tracks,
|
||||
privileges: {},
|
||||
}
|
||||
}
|
||||
case 'album': {
|
||||
if (!query?.id) return
|
||||
const album = db.get(ModelNames.ALBUM, Number(query?.id)) as any
|
||||
if (checkIsExpired && isCacheExpired(album?.updateAt, 24 * 60)) return
|
||||
if (album?.json) return JSON.parse(album.json)
|
||||
break
|
||||
}
|
||||
case 'playlist/detail': {
|
||||
if (!query?.id) return
|
||||
const playlist = db.get(ModelNames.PLAYLIST, Number(query?.id)) as any
|
||||
if (checkIsExpired && isCacheExpired(playlist?.updateAt, 10)) return
|
||||
if (playlist?.json) return JSON.parse(playlist.json)
|
||||
break
|
||||
}
|
||||
case 'artist/album': {
|
||||
if (!query?.id) return
|
||||
const artistAlbums = db.get(
|
||||
ModelNames.ARTIST_ALBUMS,
|
||||
Number(query?.id)
|
||||
) as any
|
||||
if (checkIsExpired && isCacheExpired(artistAlbums?.updateAt, 30)) return
|
||||
if (artistAlbums?.json) return JSON.parse(artistAlbums.json)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCacheForExpress(api: string, req: Request) {
|
||||
// Get track detail cache
|
||||
if (api === 'song/detail') {
|
||||
const cache = getCache(api, req.query)
|
||||
if (cache) {
|
||||
logger.info(`[cache] Cache hit for ${req.path}`)
|
||||
return cache
|
||||
}
|
||||
}
|
||||
|
||||
// Get audio cache if API is song/detail
|
||||
if (api === 'song/url') {
|
||||
const cache = db.get(ModelNames.AUDIO, Number(req.query.id)) as any
|
||||
if (!cache) return
|
||||
|
||||
const audioFileName = `${cache.id}-${cache.br}.${cache.type}`
|
||||
|
||||
const isAudioFileExists = fs.existsSync(
|
||||
`${app.getPath('userData')}/audio_cache/${audioFileName}`
|
||||
)
|
||||
if (!isAudioFileExists) return
|
||||
|
||||
logger.info(`[cache] Audio cache hit for ${req.path}`)
|
||||
|
||||
return {
|
||||
data: [
|
||||
{
|
||||
source: cache.source,
|
||||
id: cache.id,
|
||||
url: `http://127.0.0.1:42710/yesplaymusic/audio/${audioFileName}`,
|
||||
br: cache.br,
|
||||
size: 0,
|
||||
md5: '',
|
||||
code: 200,
|
||||
expi: 0,
|
||||
type: cache.type,
|
||||
gain: 0,
|
||||
fee: 8,
|
||||
uf: null,
|
||||
payed: 0,
|
||||
flag: 4,
|
||||
canExtend: false,
|
||||
freeTrialInfo: null,
|
||||
level: 'standard',
|
||||
encodeType: cache.type,
|
||||
freeTrialPrivilege: {
|
||||
resConsumable: false,
|
||||
userConsumable: false,
|
||||
listenType: null,
|
||||
},
|
||||
freeTimeTrialPrivilege: {
|
||||
resConsumable: false,
|
||||
userConsumable: false,
|
||||
type: 0,
|
||||
remainTime: 0,
|
||||
},
|
||||
urlSource: 0,
|
||||
},
|
||||
],
|
||||
code: 200,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getAudioCache(fileName: string, res: Response) {
|
||||
if (!fileName) {
|
||||
return res.status(400).send({ error: 'No filename provided' })
|
||||
}
|
||||
const id = Number(fileName.split('-')[0])
|
||||
|
||||
try {
|
||||
const path = `${app.getPath('userData')}/audio_cache/${fileName}`
|
||||
const audio = fs.readFileSync(path)
|
||||
if (audio.byteLength === 0) {
|
||||
db.delete(ModelNames.AUDIO, Number(id))
|
||||
fs.unlinkSync(path)
|
||||
return res.status(404).send({ error: 'Audio not found' })
|
||||
}
|
||||
res.send(audio)
|
||||
} catch (error) {
|
||||
res.status(500).send({ error })
|
||||
}
|
||||
}
|
||||
|
||||
// Cache audio info local folder
|
||||
export async function cacheAudio(
|
||||
buffer: Buffer,
|
||||
{ id, source }: { id: number; source: string }
|
||||
) {
|
||||
const path = `${app.getPath('userData')}/audio_cache`
|
||||
|
||||
try {
|
||||
fs.statSync(path)
|
||||
} catch (e) {
|
||||
fs.mkdirSync(path)
|
||||
}
|
||||
|
||||
const meta = await musicMetadata.parseBuffer(buffer)
|
||||
const br = meta.format.bitrate
|
||||
const type = {
|
||||
'MPEG 1 Layer 3': 'mp3',
|
||||
'Ogg Vorbis': 'ogg',
|
||||
AAC: 'm4a',
|
||||
FLAC: 'flac',
|
||||
unknown: 'unknown',
|
||||
}[meta.format.codec ?? 'unknown']
|
||||
|
||||
await fs.writeFile(`${path}/${id}-${br}.${type}`, buffer, error => {
|
||||
if (error) {
|
||||
return logger.error(`[cache] cacheAudio failed: ${error}`)
|
||||
}
|
||||
logger.info(`Audio file ${id}-${br}.${type} cached!`)
|
||||
|
||||
realm.write(() => {
|
||||
realm.create(
|
||||
ModelNames.AUDIO,
|
||||
{
|
||||
id: Number(id),
|
||||
type,
|
||||
br,
|
||||
source,
|
||||
updateAt: Date.now(),
|
||||
},
|
||||
'modified'
|
||||
)
|
||||
})
|
||||
|
||||
logger.info(`[cache] cacheAudio ${id}-${br}.${type}`)
|
||||
})
|
||||
}
|
||||
|
||||
ipcMain.on('getApiCacheSync', (event, args) => {
|
||||
const { api, query } = args
|
||||
const data = getCache(api, query, false)
|
||||
event.returnValue = data
|
||||
})
|
||||
|
|
@ -1,48 +1,78 @@
|
|||
import Realm from 'realm'
|
||||
import type { FetchTracksResponse } from '../renderer/src/api/track'
|
||||
import type { FetchAlbumResponse } from '../renderer/src/api/album'
|
||||
import path from 'path'
|
||||
import { app } from 'electron'
|
||||
|
||||
enum ModelNames {
|
||||
export enum ModelNames {
|
||||
ACCOUNT_DATA = 'AccountData',
|
||||
TRACK = 'Track',
|
||||
ALBUM = 'Album',
|
||||
ARTIST = 'Artist',
|
||||
PLAYLIST = 'Playlist',
|
||||
ARTIST_ALBUMS = 'ArtistAlbums',
|
||||
USER_PLAYLISTS = 'UserPlaylists',
|
||||
AUDIO = 'Audio',
|
||||
}
|
||||
|
||||
const universalProperties = {
|
||||
export enum AudioSources {
|
||||
NETEASE = 'netease',
|
||||
KUWO = 'kuwo',
|
||||
QQ = 'qq',
|
||||
KUGOU = 'kugou',
|
||||
YOUTUBE = 'youtube',
|
||||
MIGU = 'migu',
|
||||
JOOX = 'joox',
|
||||
BILIBILI = 'bilibili',
|
||||
}
|
||||
|
||||
const RegularSchemas = [
|
||||
ModelNames.USER_PLAYLISTS,
|
||||
ModelNames.ARTIST_ALBUMS,
|
||||
ModelNames.PLAYLIST,
|
||||
ModelNames.ALBUM,
|
||||
ModelNames.TRACK,
|
||||
].map(name => ({
|
||||
primaryKey: 'id',
|
||||
name,
|
||||
properties: {
|
||||
id: 'int',
|
||||
json: 'string',
|
||||
updateAt: 'int',
|
||||
}
|
||||
},
|
||||
}))
|
||||
|
||||
const TrackSchema = {
|
||||
name: ModelNames.TRACK,
|
||||
properties: universalProperties,
|
||||
export const realm = new Realm({
|
||||
path: path.resolve(app.getPath('userData'), './api_cache/db.realm'),
|
||||
schema: [
|
||||
...RegularSchemas,
|
||||
{
|
||||
name: ModelNames.ACCOUNT_DATA,
|
||||
properties: {
|
||||
id: 'string',
|
||||
json: 'string',
|
||||
updateAt: 'int',
|
||||
},
|
||||
primaryKey: 'id',
|
||||
}
|
||||
|
||||
const AlbumSchema = {
|
||||
name: ModelNames.ALBUM,
|
||||
properties: universalProperties,
|
||||
},
|
||||
{
|
||||
name: ModelNames.AUDIO,
|
||||
properties: {
|
||||
id: 'int',
|
||||
br: 'int',
|
||||
type: 'string',
|
||||
source: 'string',
|
||||
updateAt: 'int',
|
||||
},
|
||||
primaryKey: 'id',
|
||||
}
|
||||
|
||||
const PlaylistSchema = {
|
||||
name: ModelNames.PLAYLIST,
|
||||
properties: universalProperties,
|
||||
primaryKey: 'id',
|
||||
}
|
||||
|
||||
const realm = new Realm({
|
||||
path: './.tmp/db.realm',
|
||||
schema: [TrackSchema, AlbumSchema, PlaylistSchema],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export const database = {
|
||||
get: (model: ModelNames, key: number) => {
|
||||
export const db = {
|
||||
get: (model: ModelNames, key: number | string) => {
|
||||
return realm.objectForPrimaryKey(model, key)
|
||||
},
|
||||
set: (model: ModelNames, key: number, value: any) => {
|
||||
set: (model: ModelNames, key: number | string, value: any) => {
|
||||
realm.write(() => {
|
||||
realm.create(
|
||||
model,
|
||||
{
|
||||
|
|
@ -52,81 +82,16 @@ export const database = {
|
|||
},
|
||||
'modified'
|
||||
)
|
||||
})
|
||||
},
|
||||
batchSet: (model: ModelNames, items: any[]) => {
|
||||
realm.write(() => {
|
||||
items.forEach(item => {
|
||||
realm.create(model, item, 'modified')
|
||||
})
|
||||
})
|
||||
},
|
||||
delete: (model: ModelNames, key: number) => {
|
||||
realm.delete(realm.objectForPrimaryKey(model, key))
|
||||
},
|
||||
}
|
||||
|
||||
export async function setTracks(data: FetchTracksResponse) {
|
||||
if (!data.songs) return
|
||||
const tracks = data.songs
|
||||
realm.write(() => {
|
||||
tracks.forEach(track => {
|
||||
database.set(ModelNames.TRACK, track.id, track)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export async function setCache(api: string, data: any) {
|
||||
switch (api) {
|
||||
case 'song_detail': {
|
||||
setTracks(data)
|
||||
return
|
||||
}
|
||||
case 'album': {
|
||||
if (!data.album) return
|
||||
realm.write(() => {
|
||||
database.set(ModelNames.ALBUM, Number(data.album.id), data)
|
||||
})
|
||||
return
|
||||
}
|
||||
case 'playlist_detail': {
|
||||
if (!data.playlist) return
|
||||
realm.write(() => {
|
||||
database.set(ModelNames.PLAYLIST, Number(data.playlist.id), data)
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getCache(api: string, query: any) {
|
||||
switch (api) {
|
||||
case 'song_detail': {
|
||||
const ids: string[] = query?.ids.split(',')
|
||||
const idsQuery = ids.map(id => `id = ${id}`).join(' OR ')
|
||||
const tracksRaw = realm
|
||||
.objects(ModelNames.TRACK)
|
||||
.filtered(`(${idsQuery})`)
|
||||
if (tracksRaw.length !== ids.length) {
|
||||
return
|
||||
}
|
||||
const tracks = tracksRaw.map(track => JSON.parse(track.json))
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
songs: tracks,
|
||||
privileges: {},
|
||||
}
|
||||
}
|
||||
case 'album': {
|
||||
if (!query?.id) return
|
||||
const album = realm.objectForPrimaryKey(
|
||||
ModelNames.ALBUM,
|
||||
Number(query?.id)
|
||||
)?.json
|
||||
if (album) return JSON.parse(album)
|
||||
return
|
||||
}
|
||||
case 'playlist_detail': {
|
||||
if (!query?.id) return
|
||||
const playlist = realm.objectForPrimaryKey(
|
||||
ModelNames.PLAYLIST,
|
||||
Number(query?.id)
|
||||
)?.json
|
||||
if (playlist) return JSON.parse(playlist)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import './preload' // must be first
|
||||
import {
|
||||
BrowserWindow,
|
||||
BrowserWindowConstructorOptions,
|
||||
|
|
@ -6,7 +7,7 @@ import {
|
|||
} from 'electron'
|
||||
import Store from 'electron-store'
|
||||
import { release } from 'os'
|
||||
import { join } from 'path'
|
||||
import path, { join } from 'path'
|
||||
import logger from './logger'
|
||||
import './server'
|
||||
import './database'
|
||||
|
|
@ -96,6 +97,7 @@ async function createWindow() {
|
|||
}
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
logger.info('[index] app ready')
|
||||
createWindow()
|
||||
|
||||
// Install devtool extension
|
||||
|
|
@ -107,10 +109,10 @@ app.whenReady().then(async () => {
|
|||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
} = require('electron-devtools-installer')
|
||||
installExtension(REACT_DEVELOPER_TOOLS.id).catch(err =>
|
||||
console.log('An error occurred: ', err)
|
||||
logger.info('An error occurred: ', err)
|
||||
)
|
||||
installExtension(REDUX_DEVTOOLS.id).catch(err =>
|
||||
console.log('An error occurred: ', err)
|
||||
logger.info('An error occurred: ', err)
|
||||
)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
17
packages/main/preload.ts
Normal file
17
packages/main/preload.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import logger from './logger'
|
||||
import path from 'path'
|
||||
import { app } from 'electron'
|
||||
import fs from 'fs'
|
||||
|
||||
const isDev = !app.isPackaged
|
||||
|
||||
if (isDev) {
|
||||
const devUserDataPath = path.resolve(process.cwd(), './tmp/userData')
|
||||
try {
|
||||
fs.statSync(devUserDataPath)
|
||||
} catch (e) {
|
||||
fs.mkdirSync(devUserDataPath)
|
||||
}
|
||||
app.setPath('appData', devUserDataPath)
|
||||
}
|
||||
logger.info(`[index] userData path: ${app.getPath('userData')}`)
|
||||
|
|
@ -2,27 +2,32 @@ import { pathCase } from 'change-case'
|
|||
import cookieParser from 'cookie-parser'
|
||||
import express, { Request, Response } from 'express'
|
||||
import logger from './logger'
|
||||
import { getCache, setCache } from './database'
|
||||
import {
|
||||
setCache,
|
||||
getCacheForExpress,
|
||||
cacheAudio,
|
||||
getAudioCache,
|
||||
} from './cache'
|
||||
import fileUpload from 'express-fileupload'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const neteaseApi = require('NeteaseCloudMusicApi') as (params: any) => any[]
|
||||
|
||||
const app = express()
|
||||
app.use(cookieParser())
|
||||
const port = Number(process.env['ELECTRON_DEV_NETEASE_API_PORT'] ?? 3000)
|
||||
app.use(fileUpload())
|
||||
|
||||
Object.entries(neteaseApi).forEach(([name, handler]) => {
|
||||
if (['serveNcmApi', 'getModulesDefinitions'].includes(name)) return
|
||||
|
||||
name = pathCase(name)
|
||||
|
||||
const wrappedHandler = async (req: Request, res: Response) => {
|
||||
logger.info(`[server] Handling request: ${req.path}`)
|
||||
|
||||
// Get from cache
|
||||
const cacheResult = getCache(name, req.query)
|
||||
if (cacheResult) {
|
||||
logger.info(`[server] Cache hit for ${req.path}`)
|
||||
return res.json(cacheResult)
|
||||
}
|
||||
const cache = await getCacheForExpress(name, req)
|
||||
if (cache) return res.json(cache)
|
||||
|
||||
// Request netease api
|
||||
try {
|
||||
|
|
@ -30,18 +35,54 @@ Object.entries(neteaseApi).forEach(([name, handler]) => {
|
|||
...req.query,
|
||||
cookie: `MUSIC_U=${req.cookies['MUSIC_U']}`,
|
||||
})
|
||||
res.send(result.body)
|
||||
setCache(name, result.body)
|
||||
|
||||
setCache(name, result.body, req.query)
|
||||
return res.send(result.body)
|
||||
} catch (error) {
|
||||
res.status(500).send(error)
|
||||
return res.status(500).send(error)
|
||||
}
|
||||
}
|
||||
|
||||
const neteasePath = `/netease/${pathCase(name)}`
|
||||
app.get(neteasePath, wrappedHandler)
|
||||
app.post(neteasePath, wrappedHandler)
|
||||
app.get(`/netease/${name}`, wrappedHandler)
|
||||
app.post(`/netease/${name}`, wrappedHandler)
|
||||
})
|
||||
|
||||
// Cache audio
|
||||
app.get(
|
||||
'/yesplaymusic/audio/:filename',
|
||||
async (req: Request, res: Response) => {
|
||||
getAudioCache(req.params.filename, res)
|
||||
}
|
||||
)
|
||||
app.post('/yesplaymusic/audio/:id', async (req: Request, res: Response) => {
|
||||
const id = Number(req.params.id)
|
||||
const { url } = req.query
|
||||
if (isNaN(id)) {
|
||||
return res.status(400).send({ error: 'Invalid param id' })
|
||||
}
|
||||
if (!url) {
|
||||
return res.status(400).send({ error: 'Invalid query url' })
|
||||
}
|
||||
|
||||
if (!req.files || Object.keys(req.files).length === 0 || !req.files.file) {
|
||||
return res.status(400).send('No audio were uploaded.')
|
||||
}
|
||||
if ('length' in req.files.file) {
|
||||
return res.status(400).send('Only can upload one audio at a time.')
|
||||
}
|
||||
|
||||
try {
|
||||
await cacheAudio(req.files.file.data, {
|
||||
id: id,
|
||||
source: 'netease',
|
||||
})
|
||||
res.status(200).send('Audio cached!')
|
||||
} catch (error) {
|
||||
res.status(500).send({ error })
|
||||
}
|
||||
})
|
||||
|
||||
const port = Number(process.env['ELECTRON_DEV_NETEASE_API_PORT'] ?? 3000)
|
||||
app.listen(port, () => {
|
||||
logger.info(`[server] API server listening on port ${port}`)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export enum AlbumApiNames {
|
|||
export interface FetchAlbumParams {
|
||||
id: number
|
||||
}
|
||||
interface FetchAlbumResponse {
|
||||
export interface FetchAlbumResponse {
|
||||
code: number
|
||||
resourceState: boolean
|
||||
album: Album
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export enum ArtistApiNames {
|
|||
export interface FetchArtistParams {
|
||||
id: number
|
||||
}
|
||||
interface FetchArtistResponse {
|
||||
export interface FetchArtistResponse {
|
||||
code: number
|
||||
more: boolean
|
||||
artist: Artist
|
||||
|
|
@ -34,7 +34,7 @@ export interface FetchArtistAlbumsParams {
|
|||
limit?: number // default: 50
|
||||
offset?: number // default: 0
|
||||
}
|
||||
interface FetchArtistAlbumsResponse {
|
||||
export interface FetchArtistAlbumsResponse {
|
||||
code: number
|
||||
hotAlbums: Album[]
|
||||
more: boolean
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export interface FetchPlaylistParams {
|
|||
id: number
|
||||
s?: number // 歌单最近的 s 个收藏者
|
||||
}
|
||||
interface FetchPlaylistResponse {
|
||||
export interface FetchPlaylistResponse {
|
||||
code: number
|
||||
playlist: Playlist
|
||||
privileges: unknown // TODO: unknown type
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export interface FetchAudioSourceParams {
|
|||
id: number
|
||||
br?: number // bitrate, default 999000,320000 = 320kbps
|
||||
}
|
||||
interface FetchAudioSourceResponse {
|
||||
export interface FetchAudioSourceResponse {
|
||||
code: number
|
||||
data: {
|
||||
br: number
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ export interface FetchUserPlaylistsParams {
|
|||
offset: number
|
||||
limit?: number // default 30
|
||||
}
|
||||
interface FetchUserPlaylistsResponse {
|
||||
export interface FetchUserPlaylistsResponse {
|
||||
code: number
|
||||
more: false
|
||||
version: string
|
||||
|
|
@ -116,7 +116,7 @@ export function fetchUserPlaylists(
|
|||
export interface FetchUserLikedSongsIDsParams {
|
||||
uid: number
|
||||
}
|
||||
interface FetchUserLikedSongsIDsResponse {
|
||||
export interface FetchUserLikedSongsIDsResponse {
|
||||
code: number
|
||||
checkPoint: number
|
||||
ids: number[]
|
||||
|
|
|
|||
29
packages/renderer/src/api/yesplaymusic.ts
Normal file
29
packages/renderer/src/api/yesplaymusic.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import axios, { AxiosInstance } from 'axios'
|
||||
|
||||
const baseURL = String(
|
||||
import.meta.env.DEV ? '/yesplaymusic' : `http://127.0.0.1:42710/yesplaymusic`
|
||||
)
|
||||
|
||||
const request: AxiosInstance = axios.create({
|
||||
baseURL,
|
||||
withCredentials: true,
|
||||
timeout: 15000,
|
||||
})
|
||||
|
||||
export async function cacheAudio(id: number, audio: string) {
|
||||
const file = await axios.get(audio, { responseType: 'arraybuffer' })
|
||||
if (file.status !== 200) return
|
||||
|
||||
const formData = new FormData()
|
||||
const blob = new Blob([file.data], { type: 'multipart/form-data' })
|
||||
formData.append('file', blob)
|
||||
|
||||
request.post(`/audio/${id}`, formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
params: {
|
||||
url: audio,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ import Skeleton from '@/components/Skeleton'
|
|||
import SvgIcon from '@/components/SvgIcon'
|
||||
import { prefetchAlbum } from '@/hooks/useAlbum'
|
||||
import { prefetchPlaylist } from '@/hooks/usePlaylist'
|
||||
import { formatDate, resizeImage } from '@/utils/common'
|
||||
import { formatDate, resizeImage, scrollToTop } from '@/utils/common'
|
||||
|
||||
export enum Subtitle {
|
||||
COPYWRITER = 'copywriter',
|
||||
|
|
@ -111,6 +111,7 @@ const CoverRow = ({
|
|||
if (playlists) navigate(`/playlist/${id}`)
|
||||
if (artists) navigate(`/artist/${id}`)
|
||||
if (navigateCallback) navigateCallback()
|
||||
scrollToTop()
|
||||
}
|
||||
|
||||
const prefetch = (id: number) => {
|
||||
|
|
|
|||
|
|
@ -87,7 +87,11 @@ const MediaControls = () => {
|
|||
>
|
||||
<SvgIcon
|
||||
className='h-[1.5rem] w-[1.5rem] '
|
||||
name={state === PlayerState.PLAYING ? 'pause' : 'play'}
|
||||
name={
|
||||
[PlayerState.PLAYING, PlayerState.LOADING].includes(state)
|
||||
? 'pause'
|
||||
: 'play'
|
||||
}
|
||||
/>
|
||||
</IconButton>
|
||||
<IconButton onClick={() => track && player.nextTrack()} disabled={!track}>
|
||||
|
|
@ -125,6 +129,7 @@ const Progress = () => {
|
|||
() => playerSnapshot.progress,
|
||||
[playerSnapshot.progress]
|
||||
)
|
||||
const state = useMemo(() => playerSnapshot.state, [playerSnapshot.state])
|
||||
const track = useMemo(() => playerSnapshot.track, [playerSnapshot.track])
|
||||
|
||||
return (
|
||||
|
|
@ -133,7 +138,11 @@ const Progress = () => {
|
|||
<Slider
|
||||
min={0}
|
||||
max={(track.dt ?? 0) / 1000}
|
||||
value={progress}
|
||||
value={
|
||||
state === PlayerState.PLAYING || state === PlayerState.PAUSED
|
||||
? progress
|
||||
: 0
|
||||
}
|
||||
onChange={value => {
|
||||
player.progress = value
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { NavLink } from 'react-router-dom'
|
||||
import SvgIcon from '@/components/SvgIcon'
|
||||
import { prefetchPlaylist } from '@/hooks/usePlaylist'
|
||||
import useUser from '@/hooks/useUser'
|
||||
import useUserPlaylists from '@/hooks/useUserPlaylists'
|
||||
import { scrollToTop } from '@/utils/common'
|
||||
import { prefetchPlaylist } from '@/hooks/usePlaylist'
|
||||
|
||||
interface Tab {
|
||||
name: string
|
||||
|
|
@ -70,10 +70,10 @@ const Playlists = () => {
|
|||
<div className='mb-16 overflow-auto pb-2'>
|
||||
{playlists?.playlist?.map(playlist => (
|
||||
<NavLink
|
||||
onMouseOver={() => prefetchPlaylist({ id: playlist.id })}
|
||||
key={playlist.id}
|
||||
onClick={() => scrollToTop()}
|
||||
to={`/playlist/${playlist.id}`}
|
||||
onMouseOver={() => prefetchPlaylist({ id: playlist.id })}
|
||||
className={({ isActive }: { isActive: boolean }) =>
|
||||
classNames(
|
||||
'btn-hover-animation line-clamp-1 my-px mx-3 flex cursor-default items-center rounded-lg px-3 py-[0.38rem] text-sm text-black opacity-70 transition-colors duration-200 after:scale-[0.97] after:bg-black/[.06] dark:text-white dark:after:bg-white/20',
|
||||
|
|
|
|||
|
|
@ -13,8 +13,6 @@ const Slider = ({
|
|||
onlyCallOnChangeAfterDragEnded?: boolean
|
||||
orientation?: 'horizontal' | 'vertical'
|
||||
}) => {
|
||||
console.log('[Slider.tsx] rendering')
|
||||
|
||||
const sliderRef = useRef<HTMLInputElement>(null)
|
||||
const [isDragging, setIsDragging] = useState(false)
|
||||
const [draggingValue, setDraggingValue] = useState(value)
|
||||
|
|
@ -133,7 +131,7 @@ const Slider = ({
|
|||
<div
|
||||
className={classNames(
|
||||
'absolute h-[2px] group-hover:bg-brand-500',
|
||||
isDragging ? 'bg-brand-500' : 'bg-gray-300 dark:bg-gray-400'
|
||||
isDragging ? 'bg-brand-500' : 'bg-gray-300 dark:bg-gray-600'
|
||||
)}
|
||||
style={usedTrackStyle}
|
||||
></div>
|
||||
|
|
|
|||
|
|
@ -1,61 +0,0 @@
|
|||
import style from './Slider.module.scss'
|
||||
|
||||
const Slider = () => {
|
||||
const [value, setValue] = useState(50)
|
||||
|
||||
const thumbStyle = useMemo(
|
||||
() => ({
|
||||
left: `${value}%`,
|
||||
transform: `translate(-${value}%, -9px)`,
|
||||
}),
|
||||
[value]
|
||||
)
|
||||
|
||||
const usedTrackStyle = useMemo(
|
||||
() => ({
|
||||
width: `${value}%`,
|
||||
}),
|
||||
[value]
|
||||
)
|
||||
|
||||
const onDragging = false
|
||||
|
||||
const [isHover, setIsHover] = useState(false)
|
||||
|
||||
return (
|
||||
<div
|
||||
onMouseEnter={() => setIsHover(true)}
|
||||
onMouseLeave={() => setIsHover(false)}
|
||||
>
|
||||
<div className='absolute h-[2px] w-full bg-gray-500 bg-opacity-10'></div>
|
||||
<div
|
||||
className={classNames(
|
||||
'absolute h-[2px]',
|
||||
onDragging || isHover ? 'bg-brand-500' : 'bg-gray-500'
|
||||
)}
|
||||
style={usedTrackStyle}
|
||||
></div>
|
||||
|
||||
<div
|
||||
className={classNames(
|
||||
'absolute flex h-5 w-5 items-center justify-center rounded-full bg-brand-500 bg-opacity-20',
|
||||
!onDragging && !isHover && 'opacity-0'
|
||||
)}
|
||||
style={thumbStyle}
|
||||
>
|
||||
<div className='absolute h-2 w-2 rounded-full bg-brand-500'></div>
|
||||
</div>
|
||||
|
||||
<input
|
||||
type='range'
|
||||
min='0'
|
||||
max='100'
|
||||
value={value}
|
||||
onChange={e => setValue(Number(e.target.value))}
|
||||
className='absolute h-[2px] w-full appearance-none opacity-0'
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Slider
|
||||
|
|
@ -71,7 +71,7 @@ const Track = memo(
|
|||
!isSkeleton && {
|
||||
'btn-hover-animation after:bg-gray-100 dark:after:bg-white/[.08]':
|
||||
!isHighlight,
|
||||
'bg-brand-100 dark:bg-gray-800': isHighlight,
|
||||
'bg-brand-50 dark:bg-gray-800': isHighlight,
|
||||
}
|
||||
)}
|
||||
>
|
||||
|
|
@ -122,7 +122,8 @@ const Track = memo(
|
|||
className={classNames(
|
||||
'ml-1',
|
||||
isHighlight ? 'text-brand-500/[.8]' : 'text-gray-400'
|
||||
)}>
|
||||
)}
|
||||
>
|
||||
({subtitle})
|
||||
</span>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { NavLink } from 'react-router-dom'
|
|||
import ArtistInline from '@/components/ArtistsInline'
|
||||
import Skeleton from '@/components/Skeleton'
|
||||
import SvgIcon from '@/components/SvgIcon'
|
||||
import { prefetchAlbum } from '@/hooks/useAlbum'
|
||||
import useUser from '@/hooks/useUser'
|
||||
import useUserLikedSongsIDs from '@/hooks/useUserLikedSongsIDs'
|
||||
import { formatDuration, resizeImage } from '@/utils/common'
|
||||
|
|
@ -31,8 +30,10 @@ const Track = memo(
|
|||
className={classNames(
|
||||
'group grid w-full rounded-xl after:scale-[.98] after:rounded-xl dark:after:bg-white/[.08]',
|
||||
'grid-cols-12 p-2 pr-4',
|
||||
!isSkeleton && !isPlaying && 'btn-hover-animation after:bg-gray-100 dark:after:bg-white/[.08]',
|
||||
!isSkeleton && isPlaying && 'bg-brand-100 dark:bg-gray-800'
|
||||
!isSkeleton &&
|
||||
!isPlaying &&
|
||||
'btn-hover-animation after:bg-gray-100 dark:after:bg-white/[.08]',
|
||||
!isSkeleton && isPlaying && 'bg-brand-50 dark:bg-gray-800'
|
||||
)}
|
||||
>
|
||||
{/* Track info */}
|
||||
|
|
@ -78,7 +79,9 @@ const Track = memo(
|
|||
<div
|
||||
className={classNames(
|
||||
'text-sm',
|
||||
isPlaying ? 'text-brand-500' : 'text-gray-600 dark:text-gray-400'
|
||||
isPlaying
|
||||
? 'text-brand-500'
|
||||
: 'text-gray-600 dark:text-gray-400'
|
||||
)}
|
||||
>
|
||||
{isSkeleton ? (
|
||||
|
|
@ -98,7 +101,6 @@ const Track = memo(
|
|||
<Fragment>
|
||||
<NavLink
|
||||
to={`/album/${track.al.id}`}
|
||||
onMouseOver={() => prefetchAlbum({ id: track.al.id })}
|
||||
className={classNames(
|
||||
'hover:underline',
|
||||
isPlaying && 'text-brand-500'
|
||||
|
|
@ -137,7 +139,9 @@ const Track = memo(
|
|||
<div
|
||||
className={classNames(
|
||||
'min-w-[2.5rem] text-right',
|
||||
isPlaying ? 'text-brand-500' : 'text-gray-600 dark:text-gray-400'
|
||||
isPlaying
|
||||
? 'text-brand-500'
|
||||
: 'text-gray-600 dark:text-gray-400'
|
||||
)}
|
||||
>
|
||||
{formatDuration(track.dt, 'en', 'hh:mm:ss')}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { fetchAlbum } from '@/api/album'
|
||||
import { AlbumApiNames } from '@/api/album'
|
||||
import type { FetchAlbumParams } from '@/api/album'
|
||||
import type { FetchAlbumParams, FetchAlbumResponse } from '@/api/album'
|
||||
import reactQueryClient from '@/utils/reactQueryClient'
|
||||
|
||||
const fetch = async (params: FetchAlbumParams, noCache?: boolean) => {
|
||||
|
|
@ -17,7 +17,14 @@ export default function useAlbum(params: FetchAlbumParams, noCache?: boolean) {
|
|||
() => fetch(params, noCache),
|
||||
{
|
||||
enabled: !!params.id,
|
||||
staleTime: Infinity,
|
||||
staleTime: 24 * 60 * 60 * 1000, // 24 hours
|
||||
placeholderData: (): FetchAlbumResponse =>
|
||||
window.ipcRenderer.sendSync('getApiCacheSync', {
|
||||
api: 'album',
|
||||
query: {
|
||||
id: params.id,
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import { fetchArtistAlbums } from '@/api/artist'
|
||||
import { ArtistApiNames } from '@/api/artist'
|
||||
import type { FetchArtistAlbumsParams } from '@/api/artist'
|
||||
import type {
|
||||
FetchArtistAlbumsParams,
|
||||
FetchArtistAlbumsResponse,
|
||||
} from '@/api/artist'
|
||||
|
||||
export default function useUserAlbums(params: FetchArtistAlbumsParams) {
|
||||
return useQuery(
|
||||
|
|
@ -12,6 +15,13 @@ export default function useUserAlbums(params: FetchArtistAlbumsParams) {
|
|||
{
|
||||
enabled: !!params.id && params.id !== 0,
|
||||
staleTime: 3600000,
|
||||
placeholderData: (): FetchArtistAlbumsResponse =>
|
||||
window.ipcRenderer.sendSync('getApiCacheSync', {
|
||||
api: 'artist/album',
|
||||
query: {
|
||||
id: params.id,
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { fetchPlaylist } from '@/api/playlist'
|
||||
import { PlaylistApiNames } from '@/api/playlist'
|
||||
import type { FetchPlaylistParams } from '@/api/playlist'
|
||||
import type { FetchPlaylistParams, FetchPlaylistResponse } from '@/api/playlist'
|
||||
import reactQueryClient from '@/utils/reactQueryClient'
|
||||
|
||||
const fetch = (params: FetchPlaylistParams, noCache?: boolean) => {
|
||||
|
|
@ -16,7 +16,14 @@ export default function usePlaylist(
|
|||
() => fetch(params, noCache),
|
||||
{
|
||||
enabled: !!(params.id && params.id > 0 && !isNaN(Number(params.id))),
|
||||
staleTime: 3600000,
|
||||
staleTime: 60 * 60 * 1000, // 1 hour
|
||||
placeholderData: (): FetchPlaylistResponse | undefined =>
|
||||
window.ipcRenderer.sendSync('getApiCacheSync', {
|
||||
api: 'playlist/detail',
|
||||
query: {
|
||||
id: params.id,
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
import { TrackApiNames, fetchAudioSource, fetchTracks } from '@/api/track'
|
||||
import type { FetchAudioSourceParams, FetchTracksParams } from '@/api/track'
|
||||
import type {
|
||||
FetchAudioSourceParams,
|
||||
FetchTracksParams,
|
||||
FetchTracksResponse,
|
||||
} from '@/api/track'
|
||||
import reactQueryClient from '@/utils/reactQueryClient'
|
||||
|
||||
export default function useTracks(params: FetchTracksParams) {
|
||||
|
|
@ -12,6 +16,13 @@ export default function useTracks(params: FetchTracksParams) {
|
|||
enabled: params.ids.length !== 0,
|
||||
refetchInterval: false,
|
||||
staleTime: Infinity,
|
||||
initialData: (): FetchTracksResponse | undefined =>
|
||||
window.ipcRenderer.sendSync('getApiCacheSync', {
|
||||
api: 'song/detail',
|
||||
query: {
|
||||
ids: params.ids.join(','),
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -37,7 +48,7 @@ export function fetchAudioSourceWithReactQuery(params: FetchAudioSourceParams) {
|
|||
},
|
||||
{
|
||||
retry: 3,
|
||||
staleTime: 1200000,
|
||||
staleTime: 0, // TODO: Web版1小时缓存
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
import { fetchUserAccount } from '@/api/user'
|
||||
import { fetchUserAccount, fetchUserAccountResponse } from '@/api/user'
|
||||
import { UserApiNames } from '@/api/user'
|
||||
|
||||
export default function useUser() {
|
||||
return useQuery(UserApiNames.FETCH_USER_ACCOUNT, fetchUserAccount, {
|
||||
refetchOnWindowFocus: true,
|
||||
placeholderData: (): fetchUserAccountResponse | undefined =>
|
||||
window.ipcRenderer.sendSync('getApiCacheSync', {
|
||||
api: 'user/account',
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import type { FetchUserLikedSongsIDsParams } from '@/api/user'
|
||||
import type {
|
||||
FetchUserLikedSongsIDsParams,
|
||||
FetchUserLikedSongsIDsResponse,
|
||||
} from '@/api/user'
|
||||
import { UserApiNames, fetchUserLikedSongsIDs } from '@/api/user'
|
||||
|
||||
export default function useUserLikedSongsIDs(
|
||||
|
|
@ -10,6 +13,13 @@ export default function useUserLikedSongsIDs(
|
|||
{
|
||||
enabled: !!(params.uid && params.uid !== 0),
|
||||
refetchOnWindowFocus: true,
|
||||
placeholderData: (): FetchUserLikedSongsIDsResponse | undefined =>
|
||||
window.ipcRenderer.sendSync('getApiCacheSync', {
|
||||
api: 'likelist',
|
||||
query: {
|
||||
uid: params.uid,
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import type { FetchUserPlaylistsParams } from '@/api/user'
|
||||
import type {
|
||||
FetchUserPlaylistsParams,
|
||||
FetchUserPlaylistsResponse,
|
||||
} from '@/api/user'
|
||||
import { UserApiNames, fetchUserPlaylists } from '@/api/user'
|
||||
|
||||
export default function useUserPlaylists(params: FetchUserPlaylistsParams) {
|
||||
|
|
@ -14,6 +17,13 @@ export default function useUserPlaylists(params: FetchUserPlaylistsParams) {
|
|||
params.uid !== 0 &&
|
||||
params.offset !== undefined
|
||||
),
|
||||
placeholderData: (): FetchUserPlaylistsResponse =>
|
||||
window.ipcRenderer.sendSync('getApiCacheSync', {
|
||||
api: 'user/playlist',
|
||||
query: {
|
||||
uid: params.uid,
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,16 @@ export default function Home() {
|
|||
const {
|
||||
data: recommendedPlaylists,
|
||||
isLoading: isLoadingRecommendedPlaylists,
|
||||
} = useQuery(PlaylistApiNames.FETCH_RECOMMENDED_PLAYLISTS, () => {
|
||||
} = useQuery(
|
||||
PlaylistApiNames.FETCH_RECOMMENDED_PLAYLISTS,
|
||||
() => {
|
||||
return fetchRecommendedPlaylists({})
|
||||
})
|
||||
},
|
||||
{
|
||||
placeholderData: () =>
|
||||
window.ipcRenderer.sendSync('getApiCacheSync', { api: 'personalized' }),
|
||||
}
|
||||
)
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@ export function resizeImage(
|
|||
if (!Object.keys(sizeMap).includes(size)) {
|
||||
console.error(`Invalid cover size: ${size}`)
|
||||
}
|
||||
return `${url}?param=${sizeMap[size]}y${sizeMap[size]}`
|
||||
return `${url}?param=${sizeMap[size]}y${sizeMap[size]}`.replace(
|
||||
'http://',
|
||||
'https://'
|
||||
)
|
||||
}
|
||||
|
||||
export const storage = {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
fetchAudioSourceWithReactQuery,
|
||||
fetchTracksWithReactQuery,
|
||||
} from '@/hooks/useTracks'
|
||||
import { cacheAudio } from '@/api/yesplaymusic'
|
||||
|
||||
type TrackID = number
|
||||
enum TrackListSourceType {
|
||||
|
|
@ -19,9 +20,10 @@ export enum Mode {
|
|||
}
|
||||
export enum State {
|
||||
INITIALIZING = 'initializing',
|
||||
READY = 'ready',
|
||||
PLAYING = 'playing',
|
||||
PAUSED = 'paused',
|
||||
LOADED = 'loaded',
|
||||
LOADING = 'loading',
|
||||
}
|
||||
export enum RepeatMode {
|
||||
OFF = 'off',
|
||||
|
|
@ -107,8 +109,7 @@ export class Player {
|
|||
|
||||
private _setupProgressInterval() {
|
||||
this._progressInterval = setInterval(() => {
|
||||
this._progress = _howler.seek()
|
||||
console.log(this.progress)
|
||||
if (this.state === State.PLAYING) this._progress = _howler.seek()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
|
|
@ -116,6 +117,7 @@ export class Player {
|
|||
* Fetch track details from Netease based on this.trackID
|
||||
*/
|
||||
private async _fetchTrack(trackID: TrackID) {
|
||||
this.state = State.LOADING
|
||||
const response = await fetchTracksWithReactQuery({ ids: [trackID] })
|
||||
if (response.songs.length) {
|
||||
return response.songs[0]
|
||||
|
|
@ -161,6 +163,8 @@ export class Player {
|
|||
})
|
||||
_howler = howler
|
||||
this.play()
|
||||
this.state = State.PLAYING
|
||||
_howler.once('load', () => this._cacheAudio(this.trackID, audio))
|
||||
|
||||
if (!this._progressInterval) {
|
||||
this._setupProgressInterval()
|
||||
|
|
@ -177,6 +181,11 @@ export class Player {
|
|||
}
|
||||
}
|
||||
|
||||
private _cacheAudio(id: number, audio: string) {
|
||||
if (audio.includes('yesplaymusic')) return
|
||||
cacheAudio(id, audio)
|
||||
}
|
||||
|
||||
/**
|
||||
* Play current track
|
||||
* @param {boolean} fade fade in
|
||||
|
|
@ -184,6 +193,7 @@ export class Player {
|
|||
play() {
|
||||
_howler.play()
|
||||
this.state = State.PLAYING
|
||||
this._progress = _howler.seek()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -84,6 +84,11 @@ export default defineConfig({
|
|||
changeOrigin: true,
|
||||
rewrite: path => path.replace(/^\/netease/, ''),
|
||||
},
|
||||
'/yesplaymusic/': {
|
||||
target: `http://127.0.0.1:${process.env.ELECTRON_DEV_NETEASE_API_PORT}/yesplaymusic`,
|
||||
changeOrigin: true,
|
||||
rewrite: path => path.replace(/^\/yesplaymusic/, ''),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
|||
234
pnpm-lock.yaml
generated
234
pnpm-lock.yaml
generated
|
|
@ -1,9 +1,9 @@
|
|||
lockfileVersion: 5.3
|
||||
|
||||
specifiers:
|
||||
'@trivago/prettier-plugin-sort-imports': ^3.2.0
|
||||
'@types/cookie-parser': ^1.4.2
|
||||
'@types/express': ^4.17.13
|
||||
'@types/express-fileupload': ^1.2.2
|
||||
'@types/howler': ^2.2.6
|
||||
'@types/js-cookie': ^3.0.1
|
||||
'@types/lodash-es': ^4.17.6
|
||||
|
|
@ -19,6 +19,7 @@ specifiers:
|
|||
ansi-styles: ^6.1.0
|
||||
autoprefixer: ^10.4.4
|
||||
axios: ^0.26.1
|
||||
body-parser: ^1.19.2
|
||||
change-case: ^4.1.2
|
||||
classnames: ^2.3.1
|
||||
color.js: ^1.2.0
|
||||
|
|
@ -37,10 +38,13 @@ specifiers:
|
|||
eslint-plugin-react: ^7.29.4
|
||||
eslint-plugin-react-hooks: ^4.3.0
|
||||
express: ^4.17.3
|
||||
express-fileupload: ^1.3.1
|
||||
fast-folder-size: ^1.6.1
|
||||
howler: ^2.2.3
|
||||
js-cookie: ^3.0.1
|
||||
lodash-es: ^4.17.21
|
||||
md5: ^2.3.0
|
||||
music-metadata: ^7.12.2
|
||||
postcss: ^8.4.12
|
||||
prettier: 2.5.1
|
||||
prettier-plugin-tailwindcss: ^0.1.8
|
||||
|
|
@ -74,9 +78,9 @@ dependencies:
|
|||
realm: 10.13.0
|
||||
|
||||
devDependencies:
|
||||
'@trivago/prettier-plugin-sort-imports': 3.2.0_prettier@2.5.1
|
||||
'@types/cookie-parser': 1.4.2
|
||||
'@types/express': 4.17.13
|
||||
'@types/express-fileupload': 1.2.2
|
||||
'@types/howler': 2.2.6
|
||||
'@types/js-cookie': 3.0.1
|
||||
'@types/lodash-es': 4.17.6
|
||||
|
|
@ -91,6 +95,7 @@ devDependencies:
|
|||
ansi-styles: 6.1.0
|
||||
autoprefixer: 10.4.4_postcss@8.4.12
|
||||
axios: 0.26.1
|
||||
body-parser: 1.19.2
|
||||
classnames: 2.3.1
|
||||
color.js: 1.2.0
|
||||
colord: 2.9.2
|
||||
|
|
@ -104,10 +109,13 @@ devDependencies:
|
|||
eslint: 8.11.0
|
||||
eslint-plugin-react: 7.29.4_eslint@8.11.0
|
||||
eslint-plugin-react-hooks: 4.3.0_eslint@8.11.0
|
||||
express-fileupload: 1.3.1
|
||||
fast-folder-size: 1.6.1
|
||||
howler: 2.2.3
|
||||
js-cookie: 3.0.1
|
||||
lodash-es: 4.17.21
|
||||
md5: 2.3.0
|
||||
music-metadata: 7.12.2
|
||||
postcss: 8.4.12
|
||||
prettier: 2.5.1
|
||||
prettier-plugin-tailwindcss: 0.1.8_prettier@2.5.1
|
||||
|
|
@ -159,30 +167,6 @@ packages:
|
|||
engines: {node: '>=6.9.0'}
|
||||
dev: true
|
||||
|
||||
/@babel/core/7.13.10:
|
||||
resolution: {integrity: sha512-bfIYcT0BdKeAZrovpMqX2Mx5NrgAckGbwT982AkdS5GNfn3KMGiprlBAtmBcFZRUmpaufS6WZFP8trvx8ptFDw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.16.7
|
||||
'@babel/generator': 7.17.3
|
||||
'@babel/helper-compilation-targets': 7.16.7_@babel+core@7.13.10
|
||||
'@babel/helper-module-transforms': 7.17.6
|
||||
'@babel/helpers': 7.17.2
|
||||
'@babel/parser': 7.17.3
|
||||
'@babel/template': 7.16.7
|
||||
'@babel/traverse': 7.17.3
|
||||
'@babel/types': 7.17.0
|
||||
convert-source-map: 1.8.0
|
||||
debug: 4.3.3
|
||||
gensync: 1.0.0-beta.2
|
||||
json5: 2.2.0
|
||||
lodash: 4.17.21
|
||||
semver: 6.3.0
|
||||
source-map: 0.5.7
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@babel/core/7.17.2:
|
||||
resolution: {integrity: sha512-R3VH5G42VSDolRHyUO4V2cfag8WHcZyxdq5Z/m8Xyb92lW/Erm/6kM+XtRFGf3Mulre3mveni2NHfEUws8wSvw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
|
@ -206,14 +190,6 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@babel/generator/7.13.9:
|
||||
resolution: {integrity: sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==}
|
||||
dependencies:
|
||||
'@babel/types': 7.17.0
|
||||
jsesc: 2.5.2
|
||||
source-map: 0.5.7
|
||||
dev: true
|
||||
|
||||
/@babel/generator/7.17.3:
|
||||
resolution: {integrity: sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
|
@ -230,19 +206,6 @@ packages:
|
|||
'@babel/types': 7.17.0
|
||||
dev: true
|
||||
|
||||
/@babel/helper-compilation-targets/7.16.7_@babel+core@7.13.10:
|
||||
resolution: {integrity: sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0
|
||||
dependencies:
|
||||
'@babel/compat-data': 7.17.0
|
||||
'@babel/core': 7.13.10
|
||||
'@babel/helper-validator-option': 7.16.7
|
||||
browserslist: 4.19.1
|
||||
semver: 6.3.0
|
||||
dev: true
|
||||
|
||||
/@babel/helper-compilation-targets/7.16.7_@babel+core@7.17.2:
|
||||
resolution: {integrity: sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
|
@ -358,12 +321,6 @@ packages:
|
|||
js-tokens: 4.0.0
|
||||
dev: true
|
||||
|
||||
/@babel/parser/7.14.6:
|
||||
resolution: {integrity: sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/@babel/parser/7.17.3:
|
||||
resolution: {integrity: sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
|
@ -440,22 +397,6 @@ packages:
|
|||
'@babel/types': 7.17.0
|
||||
dev: true
|
||||
|
||||
/@babel/traverse/7.13.0:
|
||||
resolution: {integrity: sha512-xys5xi5JEhzC3RzEmSGrs/b3pJW/o87SypZ+G/PhaE7uqVQNv/jlmVIBXuoh5atqQ434LfXV+sf23Oxj0bchJQ==}
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.16.7
|
||||
'@babel/generator': 7.17.3
|
||||
'@babel/helper-function-name': 7.16.7
|
||||
'@babel/helper-split-export-declaration': 7.16.7
|
||||
'@babel/parser': 7.17.3
|
||||
'@babel/types': 7.17.0
|
||||
debug: 4.3.3
|
||||
globals: 11.12.0
|
||||
lodash: 4.17.21
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@babel/traverse/7.17.3:
|
||||
resolution: {integrity: sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
|
@ -474,14 +415,6 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@babel/types/7.13.0:
|
||||
resolution: {integrity: sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA==}
|
||||
dependencies:
|
||||
'@babel/helper-validator-identifier': 7.16.7
|
||||
lodash: 4.17.21
|
||||
to-fast-properties: 2.0.0
|
||||
dev: true
|
||||
|
||||
/@babel/types/7.17.0:
|
||||
resolution: {integrity: sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
|
@ -643,7 +576,6 @@ packages:
|
|||
|
||||
/@tokenizer/token/0.3.0:
|
||||
resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==}
|
||||
dev: false
|
||||
|
||||
/@tootallnate/once/1.1.2:
|
||||
resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==}
|
||||
|
|
@ -655,23 +587,6 @@ packages:
|
|||
engines: {node: '>= 10'}
|
||||
dev: true
|
||||
|
||||
/@trivago/prettier-plugin-sort-imports/3.2.0_prettier@2.5.1:
|
||||
resolution: {integrity: sha512-DnwLe+z8t/dZX5xBbYZV1+C5STkyK/P6SSq3Nk6NXlJZsgvDZX2eN4ND7bMFgGV/NL/YChWzcNf6ziGba1ktQQ==}
|
||||
peerDependencies:
|
||||
prettier: 2.x
|
||||
dependencies:
|
||||
'@babel/core': 7.13.10
|
||||
'@babel/generator': 7.13.9
|
||||
'@babel/parser': 7.14.6
|
||||
'@babel/traverse': 7.13.0
|
||||
'@babel/types': 7.13.0
|
||||
javascript-natural-sort: 0.7.1
|
||||
lodash: 4.17.21
|
||||
prettier: 2.5.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@trysound/sax/0.2.0:
|
||||
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
|
@ -684,6 +599,12 @@ packages:
|
|||
'@types/node': 14.18.10
|
||||
dev: true
|
||||
|
||||
/@types/busboy/0.3.2:
|
||||
resolution: {integrity: sha512-iEvdm9Z9KdSs/ozuh1Z7ZsXrOl8F4M/CLMXPZHr3QuJ4d6Bjn+HBMC5EMKpwpAo8oi8iK9GZfFoHaIMrrZgwVw==}
|
||||
dependencies:
|
||||
'@types/node': 14.18.10
|
||||
dev: true
|
||||
|
||||
/@types/connect/3.4.35:
|
||||
resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==}
|
||||
dependencies:
|
||||
|
|
@ -702,6 +623,13 @@ packages:
|
|||
'@types/ms': 0.7.31
|
||||
dev: true
|
||||
|
||||
/@types/express-fileupload/1.2.2:
|
||||
resolution: {integrity: sha512-sWU1EVFfLsdAginKVrkwTRbRPnbn7dawxEFEBgaRDcpNFCUuksZtASaAKEhqwEIg6fSdeTyI6dIUGl3thhrypg==}
|
||||
dependencies:
|
||||
'@types/busboy': 0.3.2
|
||||
'@types/express': 4.17.13
|
||||
dev: true
|
||||
|
||||
/@types/express-serve-static-core/4.17.28:
|
||||
resolution: {integrity: sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==}
|
||||
dependencies:
|
||||
|
|
@ -1456,6 +1384,13 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/binary/0.3.0:
|
||||
resolution: {integrity: sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=}
|
||||
dependencies:
|
||||
buffers: 0.1.1
|
||||
chainsaw: 0.1.0
|
||||
dev: true
|
||||
|
||||
/bindings/1.5.0:
|
||||
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
|
||||
dependencies:
|
||||
|
|
@ -1476,6 +1411,10 @@ packages:
|
|||
bluebird: 3.7.2
|
||||
dev: true
|
||||
|
||||
/bluebird/3.4.7:
|
||||
resolution: {integrity: sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=}
|
||||
dev: true
|
||||
|
||||
/bluebird/3.7.2:
|
||||
resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==}
|
||||
dev: true
|
||||
|
|
@ -1494,7 +1433,6 @@ packages:
|
|||
qs: 6.9.7
|
||||
raw-body: 2.4.3
|
||||
type-is: 1.6.18
|
||||
dev: false
|
||||
|
||||
/boolbase/1.0.0:
|
||||
resolution: {integrity: sha1-aN/1++YMUes3cl6p4+0xDcwed24=}
|
||||
|
|
@ -1619,12 +1557,22 @@ packages:
|
|||
/buffer-from/1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
|
||||
/buffer-indexof-polyfill/1.0.2:
|
||||
resolution: {integrity: sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==}
|
||||
engines: {node: '>=0.10'}
|
||||
dev: true
|
||||
|
||||
/buffer/5.7.1:
|
||||
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
ieee754: 1.2.1
|
||||
|
||||
/buffers/0.1.1:
|
||||
resolution: {integrity: sha1-skV5w77U1tOWru5tmorn9Ugqt7s=}
|
||||
engines: {node: '>=0.2.0'}
|
||||
dev: true
|
||||
|
||||
/builder-util-runtime/8.9.2:
|
||||
resolution: {integrity: sha512-rhuKm5vh7E0aAmT6i8aoSfEjxzdYEFX7zDApK+eNgOhjofnWb74d9SRJv0H/8nsgOkos0TZ4zxW0P8J4N7xQ2A==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
|
@ -1664,12 +1612,10 @@ packages:
|
|||
engines: {node: '>=4.5.0'}
|
||||
dependencies:
|
||||
dicer: 0.3.0
|
||||
dev: false
|
||||
|
||||
/bytes/3.1.2:
|
||||
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/cache-base/1.0.1:
|
||||
resolution: {integrity: sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==}
|
||||
|
|
@ -1751,6 +1697,12 @@ packages:
|
|||
resolution: {integrity: sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=}
|
||||
dev: false
|
||||
|
||||
/chainsaw/0.1.0:
|
||||
resolution: {integrity: sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=}
|
||||
dependencies:
|
||||
traverse: 0.3.9
|
||||
dev: true
|
||||
|
||||
/chalk/1.1.3:
|
||||
resolution: {integrity: sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
|
@ -2054,7 +2006,6 @@ packages:
|
|||
/content-type/1.0.4:
|
||||
resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/convert-source-map/1.8.0:
|
||||
resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==}
|
||||
|
|
@ -2342,7 +2293,6 @@ packages:
|
|||
/depd/1.1.2:
|
||||
resolution: {integrity: sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/destroy/1.0.4:
|
||||
resolution: {integrity: sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=}
|
||||
|
|
@ -2372,7 +2322,6 @@ packages:
|
|||
engines: {node: '>=4.5.0'}
|
||||
dependencies:
|
||||
streamsearch: 0.1.2
|
||||
dev: false
|
||||
|
||||
/didyoumean/1.2.2:
|
||||
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
||||
|
|
@ -2536,6 +2485,12 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/duplexer2/0.1.4:
|
||||
resolution: {integrity: sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=}
|
||||
dependencies:
|
||||
readable-stream: 2.3.7
|
||||
dev: true
|
||||
|
||||
/duplexer3/0.1.4:
|
||||
resolution: {integrity: sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=}
|
||||
dev: true
|
||||
|
|
@ -2549,7 +2504,6 @@ packages:
|
|||
|
||||
/ee-first/1.1.1:
|
||||
resolution: {integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=}
|
||||
dev: false
|
||||
|
||||
/ejs/3.1.6:
|
||||
resolution: {integrity: sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==}
|
||||
|
|
@ -3166,7 +3120,6 @@ packages:
|
|||
engines: {node: '>=12.0.0'}
|
||||
dependencies:
|
||||
busboy: 0.3.1
|
||||
dev: false
|
||||
|
||||
/express/4.17.3:
|
||||
resolution: {integrity: sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==}
|
||||
|
|
@ -3259,6 +3212,14 @@ packages:
|
|||
/fast-deep-equal/3.1.3:
|
||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
|
||||
/fast-folder-size/1.6.1:
|
||||
resolution: {integrity: sha512-F3tRpfkAzb7TT2JNKaJUglyuRjRa+jelQD94s9OSqkfEeytLmupCqQiD+H2KoIXGtp4pB5m4zNmv5m2Ktcr+LA==}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
unzipper: 0.10.11
|
||||
dev: true
|
||||
|
||||
/fast-glob/3.2.11:
|
||||
resolution: {integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==}
|
||||
engines: {node: '>=8.6.0'}
|
||||
|
|
@ -3310,7 +3271,6 @@ packages:
|
|||
readable-web-to-node-stream: 3.0.2
|
||||
strtok3: 6.3.0
|
||||
token-types: 4.2.0
|
||||
dev: false
|
||||
|
||||
/file-uri-to-path/1.0.0:
|
||||
resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
|
||||
|
|
@ -3504,6 +3464,16 @@ packages:
|
|||
dev: true
|
||||
optional: true
|
||||
|
||||
/fstream/1.0.12:
|
||||
resolution: {integrity: sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==}
|
||||
engines: {node: '>=0.6'}
|
||||
dependencies:
|
||||
graceful-fs: 4.2.9
|
||||
inherits: 2.0.4
|
||||
mkdirp: 0.5.5
|
||||
rimraf: 2.7.1
|
||||
dev: true
|
||||
|
||||
/ftp/0.3.10:
|
||||
resolution: {integrity: sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0=}
|
||||
engines: {node: '>=0.8.0'}
|
||||
|
|
@ -3878,7 +3848,6 @@ packages:
|
|||
setprototypeof: 1.2.0
|
||||
statuses: 1.5.0
|
||||
toidentifier: 1.0.1
|
||||
dev: false
|
||||
|
||||
/http-proxy-agent/4.0.1:
|
||||
resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==}
|
||||
|
|
@ -3951,7 +3920,6 @@ packages:
|
|||
engines: {node: '>=0.10.0'}
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
dev: false
|
||||
|
||||
/iconv-lite/0.6.3:
|
||||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||
|
|
@ -4349,10 +4317,6 @@ packages:
|
|||
minimatch: 3.1.2
|
||||
dev: true
|
||||
|
||||
/javascript-natural-sort/0.7.1:
|
||||
resolution: {integrity: sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k=}
|
||||
dev: true
|
||||
|
||||
/js-base64/2.6.4:
|
||||
resolution: {integrity: sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==}
|
||||
dev: true
|
||||
|
|
@ -4547,6 +4511,10 @@ packages:
|
|||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||
dev: true
|
||||
|
||||
/listenercount/1.0.1:
|
||||
resolution: {integrity: sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=}
|
||||
dev: true
|
||||
|
||||
/loader-utils/1.4.0:
|
||||
resolution: {integrity: sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
|
|
@ -4674,12 +4642,10 @@ packages:
|
|||
/media-typer/0.3.0:
|
||||
resolution: {integrity: sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/media-typer/1.1.0:
|
||||
resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/merge-descriptors/1.0.1:
|
||||
resolution: {integrity: sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=}
|
||||
|
|
@ -4852,7 +4818,6 @@ packages:
|
|||
token-types: 4.2.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/nano-css/5.3.4_react-dom@17.0.2+react@17.0.2:
|
||||
resolution: {integrity: sha512-wfcviJB6NOxDIDfr7RFn/GlaN7I/Bhe4d39ZRCJ3xvZX60LVe2qZ+rDqM49nm4YT81gAjzS+ZklhKP/Gnfnubg==}
|
||||
|
|
@ -5103,7 +5068,6 @@ packages:
|
|||
engines: {node: '>= 0.8'}
|
||||
dependencies:
|
||||
ee-first: 1.1.1
|
||||
dev: false
|
||||
|
||||
/once/1.4.0:
|
||||
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
|
||||
|
|
@ -5303,7 +5267,6 @@ packages:
|
|||
/peek-readable/4.1.0:
|
||||
resolution: {integrity: sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==}
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/pend/1.2.0:
|
||||
resolution: {integrity: sha1-elfrVQpng/kRUzH89GY9XI4AelA=}
|
||||
|
|
@ -5592,7 +5555,6 @@ packages:
|
|||
/qs/6.9.7:
|
||||
resolution: {integrity: sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==}
|
||||
engines: {node: '>=0.6'}
|
||||
dev: false
|
||||
|
||||
/query-string/4.3.4:
|
||||
resolution: {integrity: sha1-u7aTucqRXCMlFbIosaArYJBD2+s=}
|
||||
|
|
@ -5628,7 +5590,6 @@ packages:
|
|||
http-errors: 1.8.1
|
||||
iconv-lite: 0.4.24
|
||||
unpipe: 1.0.0
|
||||
dev: false
|
||||
|
||||
/rc/1.2.8:
|
||||
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
|
||||
|
|
@ -5798,7 +5759,6 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
readable-stream: 3.6.0
|
||||
dev: false
|
||||
|
||||
/readdirp/3.6.0:
|
||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||
|
|
@ -5992,6 +5952,13 @@ packages:
|
|||
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/rimraf/2.7.1:
|
||||
resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
glob: 7.2.0
|
||||
dev: true
|
||||
|
||||
/rimraf/3.0.2:
|
||||
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
|
||||
hasBin: true
|
||||
|
|
@ -6190,9 +6157,12 @@ packages:
|
|||
split-string: 3.1.0
|
||||
dev: true
|
||||
|
||||
/setimmediate/1.0.5:
|
||||
resolution: {integrity: sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=}
|
||||
dev: true
|
||||
|
||||
/setprototypeof/1.2.0:
|
||||
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
|
||||
dev: false
|
||||
|
||||
/shebang-command/2.0.0:
|
||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||
|
|
@ -6427,7 +6397,6 @@ packages:
|
|||
/statuses/1.5.0:
|
||||
resolution: {integrity: sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/stream-counter/1.0.0:
|
||||
resolution: {integrity: sha1-kc8lac5NxQYf6816yyY5SloRR1E=}
|
||||
|
|
@ -6437,7 +6406,6 @@ packages:
|
|||
/streamsearch/0.1.2:
|
||||
resolution: {integrity: sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=}
|
||||
engines: {node: '>=0.8.0'}
|
||||
dev: false
|
||||
|
||||
/strict-uri-encode/1.1.0:
|
||||
resolution: {integrity: sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=}
|
||||
|
|
@ -6524,7 +6492,6 @@ packages:
|
|||
dependencies:
|
||||
'@tokenizer/token': 0.3.0
|
||||
peek-readable: 4.1.0
|
||||
dev: false
|
||||
|
||||
/stylis/4.0.13:
|
||||
resolution: {integrity: sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==}
|
||||
|
|
@ -6763,7 +6730,6 @@ packages:
|
|||
/toidentifier/1.0.1:
|
||||
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
|
||||
engines: {node: '>=0.6'}
|
||||
dev: false
|
||||
|
||||
/token-types/4.2.0:
|
||||
resolution: {integrity: sha512-P0rrp4wUpefLncNamWIef62J0v0kQR/GfDVji9WKY7GDCWy5YbVSrKUTam07iWPZQGy0zWNOfstYTykMmPNR7w==}
|
||||
|
|
@ -6771,7 +6737,6 @@ packages:
|
|||
dependencies:
|
||||
'@tokenizer/token': 0.3.0
|
||||
ieee754: 1.2.1
|
||||
dev: false
|
||||
|
||||
/tough-cookie/2.5.0:
|
||||
resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==}
|
||||
|
|
@ -6785,6 +6750,10 @@ packages:
|
|||
resolution: {integrity: sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=}
|
||||
dev: false
|
||||
|
||||
/traverse/0.3.9:
|
||||
resolution: {integrity: sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=}
|
||||
dev: true
|
||||
|
||||
/traverse/0.6.6:
|
||||
resolution: {integrity: sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=}
|
||||
dev: true
|
||||
|
|
@ -6866,7 +6835,6 @@ packages:
|
|||
dependencies:
|
||||
media-typer: 0.3.0
|
||||
mime-types: 2.1.34
|
||||
dev: false
|
||||
|
||||
/typedarray-to-buffer/3.1.5:
|
||||
resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==}
|
||||
|
|
@ -6933,7 +6901,6 @@ packages:
|
|||
/unpipe/1.0.0:
|
||||
resolution: {integrity: sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/unplugin-auto-import/0.6.6_rollup@2.70.1+vite@2.8.6:
|
||||
resolution: {integrity: sha512-x3YxAI9ePoumXOakuS5YJlFkSyAkl5vJlaFZSJhSp75nH5gg8LpqQ/0Gz1/CG/JRRv+xaE1CZpEV161AqFGjEg==}
|
||||
|
|
@ -6996,6 +6963,21 @@ packages:
|
|||
yaku: 0.16.7
|
||||
dev: true
|
||||
|
||||
/unzipper/0.10.11:
|
||||
resolution: {integrity: sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw==}
|
||||
dependencies:
|
||||
big-integer: 1.6.51
|
||||
binary: 0.3.0
|
||||
bluebird: 3.4.7
|
||||
buffer-indexof-polyfill: 1.0.2
|
||||
duplexer2: 0.1.4
|
||||
fstream: 1.0.12
|
||||
graceful-fs: 4.2.9
|
||||
listenercount: 1.0.1
|
||||
readable-stream: 2.3.7
|
||||
setimmediate: 1.0.5
|
||||
dev: true
|
||||
|
||||
/update-notifier/5.1.0:
|
||||
resolution: {integrity: sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==}
|
||||
engines: {node: '>=10'}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,4 @@ module.exports = {
|
|||
// Tailwind CSS
|
||||
plugins: [require('prettier-plugin-tailwindcss')],
|
||||
tailwindConfig: './tailwind.config.js',
|
||||
|
||||
// Sort import order
|
||||
importOrder: ['^@/(.*)$', '^[./]'],
|
||||
importOrderSeparation: false,
|
||||
importOrderSortSpecifiers: true,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue