feat: updates

This commit is contained in:
qier222 2023-01-07 14:39:03 +08:00
parent 884f3df41a
commit c6c59b2cd9
No known key found for this signature in database
84 changed files with 3531 additions and 2616 deletions

View file

@ -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,

View file

@ -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'

View 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'

View file

@ -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

View file

@ -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'

View file

@ -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) => {

View file

@ -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) {

View file

@ -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,

View file

@ -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%

View file

@ -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() {

View file

@ -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())
}

View file

@ -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`,

View file

@ -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": "*"
}
}

View file

@ -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'),
})