mirror of
https://github.com/GiriNeko/YesPlayMusic.git
synced 2025-12-17 21:58:03 +00:00
feat: updates
This commit is contained in:
parent
884f3df41a
commit
c6c59b2cd9
84 changed files with 3531 additions and 2616 deletions
|
|
@ -7,7 +7,7 @@ const pkg = require('./package.json')
|
|||
const electronVersion = pkg.devDependencies.electron.replaceAll('^', '')
|
||||
|
||||
module.exports = {
|
||||
appId: 'com.qier222.yesplaymusic.alpha',
|
||||
appId: 'app.r3play',
|
||||
productName: pkg.productName,
|
||||
copyright: 'Copyright © 2022 qier222',
|
||||
asar: true,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import { app } from 'electron'
|
|||
import fs from 'fs'
|
||||
import SQLite3 from 'better-sqlite3'
|
||||
import log from './log'
|
||||
import { createFileIfNotExist, dirname, isProd } from './utils'
|
||||
import { createFileIfNotExist, dirname } from './utils'
|
||||
import { isProd } from './env'
|
||||
import pkg from '../../../package.json'
|
||||
import { compare, validate } from 'compare-versions'
|
||||
import os from 'os'
|
||||
|
|
|
|||
6
packages/desktop/main/env.ts
Normal file
6
packages/desktop/main/env.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export const isDev = process.env.NODE_ENV === 'development'
|
||||
export const isProd = process.env.NODE_ENV === 'production'
|
||||
export const isWindows = process.platform === 'win32'
|
||||
export const isMac = process.platform === 'darwin'
|
||||
export const isLinux = process.platform === 'linux'
|
||||
export const appName = 'R3Play'
|
||||
|
|
@ -1,12 +1,7 @@
|
|||
import './preload' // must be first
|
||||
import './sentry'
|
||||
import './server'
|
||||
import {
|
||||
BrowserWindow,
|
||||
BrowserWindowConstructorOptions,
|
||||
app,
|
||||
shell,
|
||||
} from 'electron'
|
||||
import { BrowserWindow, BrowserWindowConstructorOptions, app, shell } from 'electron'
|
||||
import { release } from 'os'
|
||||
import { join } from 'path'
|
||||
import log from './log'
|
||||
|
|
@ -15,7 +10,7 @@ import { createTray, YPMTray } from './tray'
|
|||
import { IpcChannels } from '@/shared/IpcChannels'
|
||||
import { createTaskbar, Thumbar } from './windowsTaskbar'
|
||||
import { createMenu } from './menu'
|
||||
import { isDev, isWindows, isLinux, isMac } from './utils'
|
||||
import { isDev, isWindows, isLinux, isMac, appName } from './env'
|
||||
import store from './store'
|
||||
// import './surrealdb'
|
||||
// import Airplay from './airplay'
|
||||
|
|
@ -81,7 +76,7 @@ class Main {
|
|||
|
||||
createWindow() {
|
||||
const options: BrowserWindowConstructorOptions = {
|
||||
title: 'YesPlayMusic',
|
||||
title: appName,
|
||||
webPreferences: {
|
||||
preload: join(__dirname, 'rendererPreload.js'),
|
||||
},
|
||||
|
|
@ -93,6 +88,7 @@ class Main {
|
|||
trafficLightPosition: { x: 24, y: 24 },
|
||||
frame: false,
|
||||
transparent: true,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0)',
|
||||
show: false,
|
||||
}
|
||||
if (store.get('window')) {
|
||||
|
|
@ -132,35 +128,31 @@ class Main {
|
|||
return headers
|
||||
}
|
||||
|
||||
this.win.webContents.session.webRequest.onBeforeSendHeaders(
|
||||
(details, callback) => {
|
||||
const { requestHeaders, url } = details
|
||||
addCORSHeaders(requestHeaders)
|
||||
this.win.webContents.session.webRequest.onBeforeSendHeaders((details, callback) => {
|
||||
const { requestHeaders, url } = details
|
||||
addCORSHeaders(requestHeaders)
|
||||
|
||||
// 不加这几个 header 的话,使用 axios 加载 YouTube 音频会很慢
|
||||
if (url.includes('googlevideo.com')) {
|
||||
requestHeaders['Sec-Fetch-Mode'] = 'no-cors'
|
||||
requestHeaders['Sec-Fetch-Dest'] = 'audio'
|
||||
requestHeaders['Range'] = 'bytes=0-'
|
||||
}
|
||||
|
||||
callback({ requestHeaders })
|
||||
// 不加这几个 header 的话,使用 axios 加载 YouTube 音频会很慢
|
||||
if (url.includes('googlevideo.com')) {
|
||||
requestHeaders['Sec-Fetch-Mode'] = 'no-cors'
|
||||
requestHeaders['Sec-Fetch-Dest'] = 'audio'
|
||||
requestHeaders['Range'] = 'bytes=0-'
|
||||
}
|
||||
)
|
||||
|
||||
this.win.webContents.session.webRequest.onHeadersReceived(
|
||||
(details, callback) => {
|
||||
const { responseHeaders, url } = details
|
||||
if (url.includes('sentry.io')) {
|
||||
callback({ responseHeaders })
|
||||
return
|
||||
}
|
||||
if (responseHeaders) {
|
||||
addCORSHeaders(responseHeaders)
|
||||
}
|
||||
callback({ requestHeaders })
|
||||
})
|
||||
|
||||
this.win.webContents.session.webRequest.onHeadersReceived((details, callback) => {
|
||||
const { responseHeaders, url } = details
|
||||
if (url.includes('sentry.io')) {
|
||||
callback({ responseHeaders })
|
||||
return
|
||||
}
|
||||
)
|
||||
if (responseHeaders) {
|
||||
addCORSHeaders(responseHeaders)
|
||||
}
|
||||
callback({ responseHeaders })
|
||||
})
|
||||
}
|
||||
|
||||
handleWindowEvents() {
|
||||
|
|
@ -176,13 +168,11 @@ class Main {
|
|||
})
|
||||
|
||||
this.win.on('enter-full-screen', () => {
|
||||
this.win &&
|
||||
this.win.webContents.send(IpcChannels.FullscreenStateChange, true)
|
||||
this.win && this.win.webContents.send(IpcChannels.FullscreenStateChange, true)
|
||||
})
|
||||
|
||||
this.win.on('leave-full-screen', () => {
|
||||
this.win &&
|
||||
this.win.webContents.send(IpcChannels.FullscreenStateChange, false)
|
||||
this.win && this.win.webContents.send(IpcChannels.FullscreenStateChange, false)
|
||||
})
|
||||
|
||||
// Save window position
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import log from 'electron-log'
|
||||
import pc from 'picocolors'
|
||||
import { isDev } from './utils'
|
||||
import { isDev } from './env'
|
||||
|
||||
Object.assign(console, log.functions)
|
||||
log.variables.process = 'main'
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ import {
|
|||
MenuItemConstructorOptions,
|
||||
shell,
|
||||
} from 'electron'
|
||||
import { logsPath, isMac } from './utils'
|
||||
import { isMac } from './env'
|
||||
import { logsPath } from './utils'
|
||||
import { exec } from 'child_process'
|
||||
|
||||
export const createMenu = (win: BrowserWindow) => {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import log from './log'
|
||||
import { app } from 'electron'
|
||||
import { isDev } from './env'
|
||||
import {
|
||||
createDirIfNotExist,
|
||||
devUserDataPath,
|
||||
isDev,
|
||||
portableUserDataPath,
|
||||
devUserDataPath,
|
||||
} from './utils'
|
||||
|
||||
if (isDev) {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
import { IpcChannels } from '@/shared/IpcChannels'
|
||||
import { isLinux, isMac, isProd, isWindows } from './utils'
|
||||
import { isLinux, isMac, isProd, isWindows } from './env'
|
||||
const { contextBridge, ipcRenderer } = require('electron')
|
||||
|
||||
if (isProd) {
|
||||
const log = require('electron-log')
|
||||
log.transports.file.level = 'info'
|
||||
log.transports.ipc.level = false
|
||||
log.variables.process = 'renderer'
|
||||
contextBridge.exposeInMainWorld('log', log)
|
||||
}
|
||||
// if (isProd) {
|
||||
// const log = require('electron-log')
|
||||
// log.transports.file.level = 'info'
|
||||
// log.transports.ipc.level = false
|
||||
// log.variables.process = 'renderer'
|
||||
// contextBridge.exposeInMainWorld('log', log)
|
||||
// }
|
||||
|
||||
contextBridge.exposeInMainWorld('ipcRenderer', {
|
||||
invoke: ipcRenderer.invoke,
|
||||
|
|
@ -27,8 +27,7 @@ contextBridge.exposeInMainWorld('ipcRenderer', {
|
|||
|
||||
contextBridge.exposeInMainWorld('env', {
|
||||
isElectron: true,
|
||||
isEnableTitlebar:
|
||||
process.platform === 'win32' || process.platform === 'linux',
|
||||
isEnableTitlebar: process.platform === 'win32' || process.platform === 'linux',
|
||||
isLinux,
|
||||
isMac,
|
||||
isWindows,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import * as Sentry from '@sentry/electron'
|
||||
import pkg from '../../../package.json'
|
||||
import { appName } from './env'
|
||||
import log from './log'
|
||||
|
||||
log.info(`[sentry] sentry initializing`)
|
||||
|
||||
Sentry.init({
|
||||
dsn: 'https://2aaaa67f1c3d4d6baefafa5d58fcf340@o436528.ingest.sentry.io/6274637',
|
||||
release: `yesplaymusic@${pkg.version}`,
|
||||
release: `${appName}@${pkg.version}`,
|
||||
environment: process.env.NODE_ENV,
|
||||
|
||||
// Set tracesSampleRate to 1.0 to capture 100%
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import fs from 'fs'
|
|||
import { app } from 'electron'
|
||||
import type { FetchAudioSourceResponse } from '@/shared/api/Track'
|
||||
import { APIs as CacheAPIs } from '@/shared/CacheAPIs'
|
||||
import { isProd } from './utils'
|
||||
import { appName, isProd } from './env'
|
||||
import { APIs } from '@/shared/CacheAPIs'
|
||||
import history from 'connect-history-api-fallback'
|
||||
import { db, Tables } from './db'
|
||||
|
|
@ -19,7 +19,7 @@ class Server {
|
|||
port = Number(
|
||||
isProd
|
||||
? process.env.ELECTRON_WEB_SERVER_PORT || 42710
|
||||
: process.env.ELECTRON_DEV_NETEASE_API_PORT || 3000
|
||||
: process.env.ELECTRON_DEV_NETEASE_API_PORT || 30001
|
||||
)
|
||||
app = express()
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
|
|
@ -39,9 +39,7 @@ class Server {
|
|||
neteaseHandler() {
|
||||
Object.entries(this.netease).forEach(([name, handler]: [string, any]) => {
|
||||
// 例外处理
|
||||
if (
|
||||
['serveNcmApi', 'getModulesDefinitions', APIs.SongUrl].includes(name)
|
||||
) {
|
||||
if (['serveNcmApi', 'getModulesDefinitions', APIs.SongUrl].includes(name)) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -103,7 +101,7 @@ class Server {
|
|||
{
|
||||
source: cache.source,
|
||||
id: cache.id,
|
||||
url: `http://127.0.0.1:42710/yesplaymusic/audio/${audioFileName}`,
|
||||
url: `http://127.0.0.1:42710/${appName.toLowerCase()}/audio/${audioFileName}`,
|
||||
br: cache.br,
|
||||
size: 0,
|
||||
md5: '',
|
||||
|
|
@ -137,9 +135,7 @@ class Server {
|
|||
}
|
||||
}
|
||||
|
||||
const getFromNetease = async (
|
||||
req: Request
|
||||
): Promise<FetchAudioSourceResponse | undefined> => {
|
||||
const getFromNetease = async (req: Request): Promise<FetchAudioSourceResponse | undefined> => {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const getSongUrl = (require('NeteaseCloudMusicApi') as any).song_url
|
||||
|
|
@ -155,12 +151,10 @@ class Server {
|
|||
// const unmExecutor = new UNM.Executor()
|
||||
const getFromUNM = async (id: number, req: Request) => {
|
||||
log.debug('[server] Fetching audio url from UNM')
|
||||
let track: Track = cache.get(CacheAPIs.Track, { ids: String(id) })
|
||||
?.songs?.[0]
|
||||
let track: Track = cache.get(CacheAPIs.Track, { ids: String(id) })?.songs?.[0]
|
||||
if (!track) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const getSongDetail = (require('NeteaseCloudMusicApi') as any)
|
||||
.song_detail
|
||||
const getSongDetail = (require('NeteaseCloudMusicApi') as any).song_detail
|
||||
|
||||
track = await getSongDetail({ ...req.query, cookie: req.cookies })
|
||||
}
|
||||
|
|
@ -184,14 +178,9 @@ class Server {
|
|||
|
||||
const sourceList = ['ytdl']
|
||||
const context = {}
|
||||
const matchedAudio = await unmExecutor.search(
|
||||
sourceList,
|
||||
trackForUNM,
|
||||
context
|
||||
)
|
||||
const matchedAudio = await unmExecutor.search(sourceList, trackForUNM, context)
|
||||
const retrievedSong = await unmExecutor.retrieve(matchedAudio, context)
|
||||
const source =
|
||||
retrievedSong.source === 'ytdl' ? 'youtube' : retrievedSong.source
|
||||
const source = retrievedSong.source === 'ytdl' ? 'youtube' : retrievedSong.source
|
||||
if (retrievedSong.url) {
|
||||
log.debug(
|
||||
`[server] UMN match: ${matchedAudio.song?.name} (https://youtube.com/v/${matchedAudio.song?.id})`
|
||||
|
|
@ -291,45 +280,38 @@ class Server {
|
|||
|
||||
cacheAudioHandler() {
|
||||
this.app.get(
|
||||
'/yesplaymusic/audio/:filename',
|
||||
`/${appName.toLowerCase()}/audio/:filename`,
|
||||
async (req: Request, res: Response) => {
|
||||
cache.getAudio(req.params.filename, res)
|
||||
}
|
||||
)
|
||||
this.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 cache.setAudio(req.files.file.data, {
|
||||
id,
|
||||
url: String(req.query.url) || '',
|
||||
})
|
||||
res.status(200).send('Audio cached!')
|
||||
} catch (error) {
|
||||
res.status(500).send({ error })
|
||||
}
|
||||
this.app.post(`/${appName.toLowerCase()}/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 cache.setAudio(req.files.file.data, {
|
||||
id,
|
||||
url: String(req.query.url) || '',
|
||||
})
|
||||
res.status(200).send('Audio cached!')
|
||||
} catch (error) {
|
||||
res.status(500).send({ error })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
listen() {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
} from 'electron'
|
||||
import { IpcChannels } from '@/shared/IpcChannels'
|
||||
import { RepeatMode } from '@/shared/playerDataTypes'
|
||||
import { appName } from './env'
|
||||
|
||||
const iconDirRoot =
|
||||
process.env.NODE_ENV === 'development'
|
||||
|
|
@ -134,7 +135,7 @@ class YPMTrayImpl implements YPMTray {
|
|||
this._contextMenu = Menu.buildFromTemplate(this._template)
|
||||
|
||||
this._updateContextMenu()
|
||||
this.setTooltip('YesPlayMusic')
|
||||
this.setTooltip(appName)
|
||||
|
||||
this._tray.on('click', () => win.show())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,17 +2,13 @@ import fs from 'fs'
|
|||
import path from 'path'
|
||||
import os from 'os'
|
||||
import pkg from '../../../package.json'
|
||||
import { appName, isDev } from './env'
|
||||
|
||||
export const isDev = process.env.NODE_ENV === 'development'
|
||||
export const isProd = process.env.NODE_ENV === 'production'
|
||||
export const isWindows = process.platform === 'win32'
|
||||
export const isMac = process.platform === 'darwin'
|
||||
export const isLinux = process.platform === 'linux'
|
||||
export const dirname = isDev ? process.cwd() : __dirname
|
||||
export const devUserDataPath = path.resolve(process.cwd(), '../../tmp/userData')
|
||||
export const portableUserDataPath = path.resolve(
|
||||
process.env.PORTABLE_EXECUTABLE_DIR || '',
|
||||
'./YesPlayMusic-UserData'
|
||||
`./${appName.toLowerCase()}-UserData`
|
||||
)
|
||||
export const logsPath = {
|
||||
linux: `~/.config/${pkg.productName}/logs`,
|
||||
|
|
|
|||
|
|
@ -1,19 +1,17 @@
|
|||
{
|
||||
"name": "desktop",
|
||||
"productName": "YesPlayMusic-alpha",
|
||||
"productName": "R3Play",
|
||||
"private": true,
|
||||
"version": "2.0.0",
|
||||
"main": "./main/index.js",
|
||||
"author": "*",
|
||||
"scripts": {
|
||||
"post-install": "node scripts/build.sqlite3.js",
|
||||
"dev": "node scripts/build.main.mjs --watch",
|
||||
"build": "node scripts/build.main.mjs",
|
||||
"post-install": "tsx scripts/build.sqlite3.ts",
|
||||
"dev": "tsx scripts/build.main.ts --watch",
|
||||
"build": "tsx scripts/build.main.ts",
|
||||
"pack": "electron-builder build -c .electron-builder.config.js",
|
||||
"pack:test": "electron-builder build -c .electron-builder.config.js --publish never --mac --dir --arm64",
|
||||
"test:types": "tsc --noEmit --project ./tsconfig.json",
|
||||
"lint": "eslint --ext .ts,.js ./",
|
||||
"format": "prettier --write './**/*.{ts,js,tsx,jsx}'",
|
||||
"test": "vitest",
|
||||
"test:ui": "vitest --ui",
|
||||
"test:coverage": "vitest run --coverage"
|
||||
|
|
@ -23,48 +21,45 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@sentry/electron": "^3.0.7",
|
||||
"NeteaseCloudMusicApi": "^4.6.7",
|
||||
"better-sqlite3": "7.6.2",
|
||||
"NeteaseCloudMusicApi": "^4.8.4",
|
||||
"better-sqlite3": "8.0.1",
|
||||
"change-case": "^4.1.2",
|
||||
"chromecast-api": "^0.4.0",
|
||||
"compare-versions": "^4.1.3",
|
||||
"connect-history-api-fallback": "^2.0.0",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"electron-log": "^4.4.8",
|
||||
"electron-store": "^8.1.0",
|
||||
"express": "^4.18.1",
|
||||
"fast-folder-size": "^1.7.0",
|
||||
"express": "^4.18.2",
|
||||
"fast-folder-size": "^1.7.1",
|
||||
"pretty-bytes": "^6.0.0",
|
||||
"type-fest": "^3.0.0",
|
||||
"zx": "^7.0.8"
|
||||
"type-fest": "^3.5.0",
|
||||
"zx": "^7.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/better-sqlite3": "^7.6.0",
|
||||
"@types/better-sqlite3": "^7.6.3",
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/express": "^4.17.15",
|
||||
"@types/express-fileupload": "^1.2.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.32.0",
|
||||
"@typescript-eslint/parser": "^5.32.0",
|
||||
"@vitest/ui": "^0.20.3",
|
||||
"axios": "^0.27.2",
|
||||
"axios": "^1.2.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^16.0.0",
|
||||
"electron": "^18.3.6",
|
||||
"electron-builder": "23.3.3",
|
||||
"dotenv": "^16.0.3",
|
||||
"electron": "^22.0.0",
|
||||
"electron-builder": "23.6.0",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-rebuild": "^3.2.9",
|
||||
"electron-releases": "^3.1091.0",
|
||||
"esbuild": "^0.14.53",
|
||||
"eslint": "*",
|
||||
"electron-releases": "^3.1171.0",
|
||||
"esbuild": "^0.16.10",
|
||||
"express-fileupload": "^1.4.0",
|
||||
"minimist": "^1.2.6",
|
||||
"music-metadata": "^7.12.5",
|
||||
"open-cli": "^7.0.1",
|
||||
"minimist": "^1.2.7",
|
||||
"music-metadata": "^8.1.0",
|
||||
"open-cli": "^7.1.0",
|
||||
"ora": "^6.1.2",
|
||||
"picocolors": "^1.0.0",
|
||||
"prettier": "*",
|
||||
"typescript": "*",
|
||||
"vitest": "^0.20.3",
|
||||
"wait-on": "^6.0.1"
|
||||
"wait-on": "^7.0.1",
|
||||
"tsx": "*"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import dotenv from 'dotenv'
|
|||
import pc from 'picocolors'
|
||||
import minimist from 'minimist'
|
||||
|
||||
const isDev = process.env.NODE_ENV === 'development'
|
||||
const env = dotenv.config({
|
||||
path: path.resolve(process.cwd(), '../../.env'),
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue