diff --git a/.envrc b/.envrc
new file mode 100644
index 0000000..fd24c66
--- /dev/null
+++ b/.envrc
@@ -0,0 +1,4 @@
+source_url "https://raw.githubusercontent.com/cachix/devenv/82c0147677e510b247d8b9165c54f73d32dfd899/direnvrc" "sha256-7u4iDd1nZpxL4tCzmPG0dQgC5V+/44Ba+tHkPob1v2k="
+
+export NIXPKGS_ALLOW_INSECURE=1
+use devenv
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 8a103f3..4caf481 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -81,6 +81,7 @@ jobs:
- name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1.6.0
env:
+ VUE_APP_NETEASE_API_URL: /api
VUE_APP_ELECTRON_API_URL: /api
VUE_APP_ELECTRON_API_URL_DEV: http://127.0.0.1:10754
VUE_APP_LASTFM_API_KEY: 09c55292403d961aa517ff7f5e8a3d9c
diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml
new file mode 100644
index 0000000..f8e0be8
--- /dev/null
+++ b/.github/workflows/sync.yml
@@ -0,0 +1,48 @@
+name: Upstream Sync
+
+permissions:
+ contents: write
+ issues: write
+ actions: write
+
+on:
+ schedule:
+ - cron: '0 * * * *' # every hour
+ workflow_dispatch:
+
+jobs:
+ sync_latest_from_upstream:
+ name: Sync latest commits from upstream repo
+ runs-on: ubuntu-latest
+ if: ${{ github.event.repository.fork }}
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Clean issue notice
+ uses: actions-cool/issues-helper@v3
+ with:
+ actions: 'close-issues'
+ labels: '🚨 Sync Fail'
+
+ - name: Sync upstream changes
+ id: sync
+ uses: aormsby/Fork-Sync-With-Upstream-action@v3.4
+ with:
+ upstream_sync_repo: qier222/YesPlayMusic
+ upstream_sync_branch: master
+ target_sync_branch: master
+ target_repo_token: ${{ secrets.GITHUB_TOKEN }} # automatically generated, no need to set
+ test_mode: false
+
+ - name: Sync check
+ if: failure()
+ uses: actions-cool/issues-helper@v3
+ with:
+ actions: 'create-issue'
+ title: '🚨 同步失败 | Sync Fail'
+ labels: '🚨 Sync Fail'
+ body: |
+ Due to a change in the workflow file of the upstream repository, GitHub has automatically suspended the scheduled automatic update. You need to manually sync your fork.
+
+ 由于上游仓库的 workflow 文件变更,导致 GitHub 自动暂停了本次自动更新,你需要手动 Sync Fork 一次。
diff --git a/.gitignore b/.gitignore
index b96fe0f..d8dd8fc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,3 +32,12 @@ NeteaseCloudMusicApi-master.zip
# Local Netlify folder
.netlify
vercel.json
+# Devenv
+.devenv*
+devenv.local.nix
+
+# direnv
+.direnv
+
+# pre-commit
+.pre-commit-config.yaml
diff --git a/README.md b/README.md
index 6764526..146f524 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
",
@@ -27,7 +27,7 @@
},
"dependencies": {
"@unblockneteasemusic/rust-napi": "^0.4.0",
- "NeteaseCloudMusicApi": "^4.8.7",
+ "NeteaseCloudMusicApi": "^4.23.3",
"axios": "^0.26.1",
"change-case": "^4.1.2",
"cli-color": "^2.0.0",
@@ -47,6 +47,8 @@
"electron-log": "^4.3.0",
"electron-store": "^8.0.1",
"electron-updater": "^5.0.1",
+ "esbuild": "^0.20.1",
+ "esbuild-loader": "^4.0.3",
"express": "^4.17.1",
"express-fileupload": "^1.2.0",
"express-http-proxy": "^1.6.2",
@@ -85,11 +87,11 @@
"@vue/cli-service": "~4.5.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
- "prettier": "2.5.1",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-vue": "^7.9.0",
"husky": "^4.3.0",
+ "prettier": "2.5.1",
"sass": "^1.26.11",
"sass-loader": "^10.0.2",
"vue-cli-plugin-electron-builder": "~2.1.1",
diff --git a/public/robots.txt b/public/robots.txt
index eb05362..1f53798 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -1,2 +1,2 @@
User-agent: *
-Disallow:
+Disallow: /
diff --git a/src/api/artist.js b/src/api/artist.js
index 462f707..925df45 100644
--- a/src/api/artist.js
+++ b/src/api/artist.js
@@ -1,5 +1,7 @@
import request from '@/utils/request';
import { mapTrackPlayableStatus } from '@/utils/common';
+import { isAccountLoggedIn } from '@/utils/auth';
+import { getTrackDetail } from '@/api/track';
/**
* 获取歌手单曲
@@ -14,7 +16,13 @@ export function getArtist(id) {
id,
timestamp: new Date().getTime(),
},
- }).then(data => {
+ }).then(async data => {
+ if (!isAccountLoggedIn()) {
+ const trackIDs = data.hotSongs.map(t => t.id);
+ const tracks = await getTrackDetail(trackIDs.join(','));
+ data.hotSongs = tracks.songs;
+ return data;
+ }
data.hotSongs = mapTrackPlayableStatus(data.hotSongs);
return data;
});
diff --git a/src/assets/icons/fullscreen-exit.svg b/src/assets/icons/fullscreen-exit.svg
new file mode 100644
index 0000000..f76f601
--- /dev/null
+++ b/src/assets/icons/fullscreen-exit.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/fullscreen.svg b/src/assets/icons/fullscreen.svg
new file mode 100644
index 0000000..e6128c0
--- /dev/null
+++ b/src/assets/icons/fullscreen.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/components/CoverRow.vue b/src/components/CoverRow.vue
index 77eb66e..76c1e1b 100644
--- a/src/components/CoverRow.vue
+++ b/src/components/CoverRow.vue
@@ -50,7 +50,7 @@ export default {
props: {
items: { type: Array, required: true },
type: { type: String, required: true },
- subText: { type: String, default: 'null' },
+ subText: { type: String, default: 'none' },
subTextFontSize: { type: String, default: '16px' },
showPlayCount: { type: Boolean, default: false },
columnNumber: { type: Number, default: 5 },
@@ -96,7 +96,7 @@ export default {
return this.type === 'playlist' && item.privacy === 10;
},
isExplicit(item) {
- return this.type === 'album' && item.mark === 1056768;
+ return this.type === 'album' && (item.mark & 1048576) === 1048576;
},
getTitleLink(item) {
return `/${this.type}/${item.id}`;
diff --git a/src/components/TrackListItem.vue b/src/components/TrackListItem.vue
index 3eef677..a3a6238 100644
--- a/src/components/TrackListItem.vue
+++ b/src/components/TrackListItem.vue
@@ -42,13 +42,15 @@
:exclude="$parent.albumObject.artist.name"
prefix="-"
/>
-
diff --git a/src/electron/ipcMain.js b/src/electron/ipcMain.js
index 4310342..aa0e3d7 100644
--- a/src/electron/ipcMain.js
+++ b/src/electron/ipcMain.js
@@ -240,7 +240,7 @@ export function initIpcMain(win, store, trayEventEmitter) {
details: track.name + ' - ' + track.ar.map(ar => ar.name).join(','),
state: track.al.name,
endTimestamp: Date.now() + track.dt,
- largeImageKey: 'logo',
+ largeImageKey: track.al.picUrl,
largeImageText: 'Listening ' + track.name,
smallImageKey: 'play',
smallImageText: 'Playing',
@@ -252,7 +252,7 @@ export function initIpcMain(win, store, trayEventEmitter) {
client.updatePresence({
details: track.name + ' - ' + track.ar.map(ar => ar.name).join(','),
state: track.al.name,
- largeImageKey: 'logo',
+ largeImageKey: track.al.picUrl,
largeImageText: 'YesPlayMusic',
smallImageKey: 'pause',
smallImageText: 'Pause',
diff --git a/src/electron/services.js b/src/electron/services.js
index 1840672..3a1106f 100644
--- a/src/electron/services.js
+++ b/src/electron/services.js
@@ -1,4 +1,5 @@
import clc from 'cli-color';
+import checkAuthToken from '../utils/checkAuthToken';
import server from 'NeteaseCloudMusicApi/server';
export async function startNeteaseMusicApi() {
diff --git a/src/locale/lang/en.js b/src/locale/lang/en.js
index 6728b8a..d880e32 100644
--- a/src/locale/lang/en.js
+++ b/src/locale/lang/en.js
@@ -244,6 +244,8 @@ export default {
minePlaylists: 'My Playlists',
likedPlaylists: 'Liked Playlists',
cardiacMode: 'Cardiac Mode',
+ copyLyric: 'Copy Lyric',
+ copyLyricWithTranslation: 'Copy Lyric With Translation',
},
toast: {
savedToPlaylist: 'Saved to playlist',
diff --git a/src/locale/lang/tr.js b/src/locale/lang/tr.js
index 296aac3..fda2939 100644
--- a/src/locale/lang/tr.js
+++ b/src/locale/lang/tr.js
@@ -230,6 +230,8 @@ export default {
minePlaylists: 'My Playlists',
likedPlaylists: 'Liked Playlists',
cardiacMode: 'Cardiac Mode',
+ copyLyric: 'Copy Lyric',
+ copyLyricWithTranslation: 'Copy Lyric With Translation',
},
toast: {
savedToMyLikedSongs: 'Beğendiğim Müziklere Kaydet',
diff --git a/src/locale/lang/zh-CN.js b/src/locale/lang/zh-CN.js
index 9fbcafc..543132a 100644
--- a/src/locale/lang/zh-CN.js
+++ b/src/locale/lang/zh-CN.js
@@ -243,6 +243,8 @@ export default {
minePlaylists: '创建的歌单',
likedPlaylists: '收藏的歌单',
cardiacMode: '心动模式',
+ copyLyric: '复制歌词',
+ copyLyricWithTranslation: '复制歌词(含翻译)',
},
toast: {
savedToPlaylist: '已添加到歌单',
diff --git a/src/locale/lang/zh-TW.js b/src/locale/lang/zh-TW.js
index 0882921..384a0cf 100644
--- a/src/locale/lang/zh-TW.js
+++ b/src/locale/lang/zh-TW.js
@@ -240,6 +240,8 @@ export default {
minePlaylists: '我建立的歌單',
likedPlaylists: '收藏的歌單',
cardiacMode: '心動模式',
+ copyLyric: '複製歌詞',
+ copyLyricWithTranslation: '複製歌詞(含翻譯)',
},
toast: {
savedToPlaylist: '已新增至歌單',
diff --git a/src/main.js b/src/main.js
index 73f7e8e..50d73a2 100644
--- a/src/main.js
+++ b/src/main.js
@@ -7,7 +7,6 @@ import i18n from '@/locale';
import '@/assets/icons';
import '@/utils/filters';
import './registerServiceWorker';
-import { dailyTask } from '@/utils/common';
import '@/assets/css/global.scss';
import NProgress from 'nprogress';
import '@/assets/css/nprogress.css';
@@ -38,7 +37,6 @@ Vue.use(
Vue.config.productionTip = false;
NProgress.configure({ showSpinner: false, trickleSpeed: 100 });
-dailyTask();
new Vue({
i18n,
diff --git a/src/store/initLocalStorage.js b/src/store/initLocalStorage.js
index ce0e802..9540efb 100644
--- a/src/store/initLocalStorage.js
+++ b/src/store/initLocalStorage.js
@@ -36,6 +36,8 @@ let localStorage = {
server: '',
port: null,
},
+ enableRealIP: false,
+ realIP: null,
shortcuts: shortcuts,
},
data: {
diff --git a/src/utils/Player.js b/src/utils/Player.js
index 19e1ad8..11af73a 100644
--- a/src/utils/Player.js
+++ b/src/utils/Player.js
@@ -3,7 +3,7 @@ import { getArtist } from '@/api/artist';
import { trackScrobble, trackUpdateNowPlaying } from '@/api/lastfm';
import { fmTrash, personalFM } from '@/api/others';
import { getPlaylistDetail, intelligencePlaylist } from '@/api/playlist';
-import { getLyric, getMP3, getTrackDetail, scrobble } from '@/api/track';
+import { getLyric, getMP3, getTrackDetail } from '@/api/track';
import store from '@/store';
import { isAccountLoggedIn } from '@/utils/auth';
import { cacheTrackSource, getTrackSource } from '@/utils/db';
@@ -130,6 +130,8 @@ export default class {
if (shuffle) {
this._shuffleTheList();
}
+ // 同步当前歌曲在列表中的下标
+ this.current = this.list.indexOf(this.currentTrackID);
}
get reversed() {
return this._reversed;
@@ -307,11 +309,6 @@ export default class {
);
const trackDuration = ~~(track.dt / 1000);
time = completed ? trackDuration : ~~time;
- scrobble({
- id: track.id,
- sourceid: this.playlistSource.id,
- time,
- });
if (
store.state.lastfm.key !== undefined &&
(time >= trackDuration / 2 || time >= 240)
@@ -892,7 +889,7 @@ export default class {
if (autoPlayTrackID === 'first') {
this._replaceCurrentTrack(this.list[0]);
} else {
- this.current = trackIDs.indexOf(autoPlayTrackID);
+ this.current = this.list.indexOf(autoPlayTrackID);
this._replaceCurrentTrack(autoPlayTrackID);
}
}
diff --git a/src/utils/checkAuthToken.js b/src/utils/checkAuthToken.js
new file mode 100644
index 0000000..1086521
--- /dev/null
+++ b/src/utils/checkAuthToken.js
@@ -0,0 +1,8 @@
+import os from 'os';
+import fs from 'fs';
+import path from 'path';
+
+// extract from NeteasyCloudMusicAPI/generateConfig.js and avoid bugs in there (generateConfig require main.js but the main.js has bugs)
+if (!fs.existsSync(path.resolve(os.tmpdir(), 'anonymous_token'))) {
+ fs.writeFileSync(path.resolve(os.tmpdir(), 'anonymous_token'), '', 'utf-8');
+}
diff --git a/src/utils/lyrics.js b/src/utils/lyrics.js
index b9a7e59..b883f46 100644
--- a/src/utils/lyrics.js
+++ b/src/utils/lyrics.js
@@ -84,3 +84,30 @@ function trimContent(content) {
let t = content.trim();
return t.length < 1 ? content : t;
}
+
+/**
+ * @param {string} lyric
+ */
+export async function copyLyric(lyric) {
+ const textToCopy = lyric;
+ if (navigator.clipboard && navigator.clipboard.writeText) {
+ try {
+ await navigator.clipboard.writeText(textToCopy);
+ } catch (err) {
+ alert('复制失败,请手动复制!');
+ }
+ } else {
+ const tempInput = document.createElement('textarea');
+ tempInput.value = textToCopy;
+ tempInput.style.position = 'absolute';
+ tempInput.style.left = '-9999px';
+ document.body.appendChild(tempInput);
+ tempInput.select();
+ try {
+ document.execCommand('copy');
+ } catch (err) {
+ alert('复制失败,请手动复制!');
+ }
+ document.body.removeChild(tempInput);
+ }
+}
diff --git a/src/utils/request.js b/src/utils/request.js
index 52ac6f4..6ac7bc3 100644
--- a/src/utils/request.js
+++ b/src/utils/request.js
@@ -38,8 +38,15 @@ service.interceptors.request.use(function (config) {
config.params.realIP = '211.161.244.70';
}
+ // Force real_ip
+ const enableRealIP = JSON.parse(
+ localStorage.getItem('settings')
+ ).enableRealIP;
+ const realIP = JSON.parse(localStorage.getItem('settings')).realIP;
if (process.env.VUE_APP_REAL_IP) {
config.params.realIP = process.env.VUE_APP_REAL_IP;
+ } else if (enableRealIP) {
+ config.params.realIP = realIP;
}
const proxy = JSON.parse(localStorage.getItem('settings')).proxyConfig;
@@ -57,8 +64,16 @@ service.interceptors.response.use(
},
async error => {
/** @type {import('axios').AxiosResponse | null} */
- const response = error.response;
- const data = response.data;
+ let response;
+ let data;
+ if (error === 'TypeError: baseURL is undefined') {
+ response = error;
+ data = error;
+ console.error("You must set up the baseURL in the service's config");
+ } else if (error.response) {
+ response = error.response;
+ data = response.data;
+ }
if (
response &&
diff --git a/src/views/album.vue b/src/views/album.vue
index 7b032b7..acc8a40 100644
--- a/src/views/album.vue
+++ b/src/views/album.vue
@@ -28,7 +28,9 @@
Compilation by Various Artists
-
{{
diff --git a/src/views/artist.vue b/src/views/artist.vue
index 4d6aae2..d825436 100644
--- a/src/views/artist.vue
+++ b/src/views/artist.vue
@@ -185,6 +185,7 @@ import {
followAArtist,
similarArtists,
} from '@/api/artist';
+import { getTrackDetail } from '@/api/track';
import locale from '@/locale';
import { isAccountLoggedIn } from '@/utils/auth';
import NProgress from 'nprogress';
@@ -278,7 +279,7 @@ export default {
this.$parent.$refs.main.scrollTo({ top: 0 });
getArtist(id).then(data => {
this.artist = data.artist;
- this.popularTracks = data.hotSongs;
+ this.setPopularTracks(data.hotSongs);
if (next !== undefined) next();
NProgress.done();
this.show = true;
@@ -291,8 +292,16 @@ export default {
this.mvs = data.mvs;
this.hasMoreMV = data.hasMore;
});
- similarArtists(id).then(data => {
- this.similarArtists = data.artists;
+ if (isAccountLoggedIn()) {
+ similarArtists(id).then(data => {
+ this.similarArtists = data.artists;
+ });
+ }
+ },
+ setPopularTracks(hotSongs) {
+ const trackIDs = hotSongs.map(t => t.id);
+ getTrackDetail(trackIDs.join(',')).then(data => {
+ this.popularTracks = data.songs;
});
},
goToAlbum(id) {
diff --git a/src/views/explore.vue b/src/views/explore.vue
index ef190df..90624d0 100644
--- a/src/views/explore.vue
+++ b/src/views/explore.vue
@@ -120,10 +120,13 @@ export default {
setTimeout(() => {
if (!this.show) NProgress.start();
}, 1000);
- this.activeCategory =
- this.$route.query.category === undefined
- ? '全部'
- : this.$route.query.category;
+ const queryCategory = this.$route.query.category;
+ if (queryCategory === undefined) {
+ this.playlists = [];
+ this.activeCategory = '全部';
+ } else {
+ this.activeCategory = queryCategory;
+ }
this.getPlaylist();
},
goToCategory(Category) {
diff --git a/src/views/library.vue b/src/views/library.vue
index 01381e4..da24af7 100644
--- a/src/views/library.vue
+++ b/src/views/library.vue
@@ -215,7 +215,7 @@