From 55585a921f6c42f130b2ebd1dd2567f6c4fc2dc9 Mon Sep 17 00:00:00 2001 From: qier222 Date: Tue, 23 Mar 2021 23:43:29 +0800 Subject: [PATCH] feat: support Last.fm scrobble --- .env.example | 3 ++ README.md | 7 +-- public/img/logos/lastfm.png | Bin 0 -> 9135 bytes src/App.vue | 28 ++++++---- src/api/lastfm.js | 79 ++++++++++++++++++++++++++++ src/background.js | 20 +++++++ src/router/index.js | 7 ++- src/store/mutations.js | 3 ++ src/store/state.js | 1 + src/utils/Player.js | 40 +++++++++++--- src/views/lastfmCallback.vue | 98 +++++++++++++++++++++++++++++++++++ src/views/settings.vue | 39 +++++++++++++- 12 files changed, 300 insertions(+), 25 deletions(-) create mode 100644 public/img/logos/lastfm.png create mode 100644 src/api/lastfm.js create mode 100644 src/views/lastfmCallback.vue diff --git a/.env.example b/.env.example index fb667ec..e043bab 100644 --- a/.env.example +++ b/.env.example @@ -2,4 +2,7 @@ VUE_APP_NETEASE_API_URL=/api VUE_APP_ELECTRON_API_URL=/api VUE_APP_ELECTRON_API_URL_DEV=http://127.0.0.1:3000 VUE_APP_ENABLE_SENTRY=false +VUE_APP_LASTFM_API_KEY=09c55292403d961aa517ff7f5e8a3d9c +VUE_APP_LASTFM_API_SHARED_SECRET=307c9fda32b3904e53654baff215cb67 DEV_SERVER_PORT=20201 + diff --git a/README.md b/README.md index 356ec77..888f0c2 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ - 👆 支持 Touch Bar - 🖥️ 支持 PWA,可在 Chrome/Edge 里点击地址栏右边的 ➕ 安装到电脑 - 🙉 支持显示歌曲和专辑的 Explicit 标志 +- 🟥 支持 Last.fm Scrobble - 🛠 更多特性开发中 ## 📦️ 安装 @@ -148,15 +149,9 @@ API 源代码来自 [Binaryify/NeteaseCloudMusicApi](https://github.com/Binaryif [album-screenshot]: images/album.png - [artist-screenshot]: images/artist.png - [explore-screenshot]: images/explore.png - [home-screenshot]: images/home.png - [library-screenshot]: images/library.png - [playlist-screenshot]: images/playlist.png - [search-screenshot]: images/search.png diff --git a/public/img/logos/lastfm.png b/public/img/logos/lastfm.png new file mode 100644 index 0000000000000000000000000000000000000000..e3964f605ec875ed9ca53067b5b27aecaa0615bf GIT binary patch literal 9135 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
- +
@@ -8,15 +8,12 @@
- - +
@@ -53,11 +50,24 @@ export default { }, showPlayer() { return ( - ["mv", "loginUsername", "login", "loginAccount"].includes( - this.$route.name - ) === false + [ + "mv", + "loginUsername", + "login", + "loginAccount", + "lastfmCallback", + ].includes(this.$route.name) === false ); }, + enablePlayer() { + return ( + this.$store.state.player.enabled && + this.$route.name !== "lastfmCallback" + ); + }, + showNavbar() { + return this.$route.name !== "lastfmCallback"; + }, }, created() { if (this.isElectron) { diff --git a/src/api/lastfm.js b/src/api/lastfm.js new file mode 100644 index 0000000..ad48a2c --- /dev/null +++ b/src/api/lastfm.js @@ -0,0 +1,79 @@ +// Last.fm API documents 👉 https://www.last.fm/api + +import axios from "axios"; +import md5 from "crypto-js/md5"; + +const apiKey = process.env.VUE_APP_LASTFM_API_KEY; +const apiSharedSecret = process.env.VUE_APP_LASTFM_API_SHARED_SECRET; +const baseUrl = window.location.origin; +const url = "https://ws.audioscrobbler.com/2.0/"; + +const sign = (params) => { + const sortParamsKeys = Object.keys(params).sort(); + const sortedParams = sortParamsKeys.reduce((acc, key) => { + acc[key] = params[key]; + return acc; + }, {}); + let signature = ""; + for (const [key, value] of Object.entries(sortedParams)) { + signature += `${key}${value}`; + } + return md5(signature + apiSharedSecret).toString(); +}; + +export function auth() { + window.open( + `https://www.last.fm/api/auth/?api_key=${apiKey}&cb=${baseUrl}/#/lastfm/callback` + ); +} + +export function authGetSession(token) { + const signature = md5( + `api_key${apiKey}methodauth.getSessiontoken${token}${apiSharedSecret}` + ).toString(); + return axios({ + url, + method: "GET", + params: { + method: "auth.getSession", + format: "json", + api_key: apiKey, + api_sig: signature, + token, + }, + }); +} + +export function trackUpdateNowPlaying(params) { + params.api_key = apiKey; + params.method = "track.updateNowPlaying"; + params.sk = JSON.parse(localStorage.getItem("lastfm"))["key"]; + const signature = sign(params); + + return axios({ + url, + method: "POST", + params: { + ...params, + api_sig: signature, + format: "json", + }, + }); +} + +export function trackScrobble(params) { + params.api_key = apiKey; + params.method = "track.scrobble"; + params.sk = JSON.parse(localStorage.getItem("lastfm"))["key"]; + const signature = sign(params); + + return axios({ + url, + method: "POST", + params: { + ...params, + api_sig: signature, + format: "json", + }, + }); +} diff --git a/src/background.js b/src/background.js index 7f3e1fe..fca47dc 100644 --- a/src/background.js +++ b/src/background.js @@ -103,6 +103,7 @@ class Background { minHeight: 720, titleBarStyle: "hiddenInset", frame: process.platform !== "win32", + title: "YesPlayMusic", webPreferences: { webSecurity: false, nodeIntegration: true, @@ -186,6 +187,25 @@ class Background { this.window.webContents.on("new-window", function (e, url) { e.preventDefault(); + console.log("open url"); + const excludeHosts = ["www.last.fm"]; + const exclude = excludeHosts.find((host) => url.includes(host)); + if (exclude) { + const newWindow = new BrowserWindow({ + width: 800, + height: 600, + titleBarStyle: "default", + title: "YesPlayMusic", + webPreferences: { + webSecurity: false, + nodeIntegration: true, + enableRemoteModule: true, + contextIsolation: false, + }, + }); + newWindow.loadURL(url); + return; + } shell.openExternal(url); }); } diff --git a/src/router/index.js b/src/router/index.js index 8e77e72..2c6d268 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -123,6 +123,11 @@ const routes = [ name: "dailySongs", component: () => import("@/views/dailyTracks.vue"), }, + { + path: "/lastfm/callback", + name: "lastfmCallback", + component: () => import("@/views/lastfmCallback.vue"), + }, ]; const router = new VueRouter({ routes, @@ -160,7 +165,7 @@ router.beforeEach((to, from, next) => { router.afterEach((to) => { if ( to.matched.some((record) => !record.meta.keepAlive) && - !["settings", "dailySongs"].includes(to.name) + !["settings", "dailySongs", "lastfmCallback"].includes(to.name) ) { NProgress.start(); } diff --git a/src/store/mutations.js b/src/store/mutations.js index 2d4ed38..70ed9c6 100644 --- a/src/store/mutations.js +++ b/src/store/mutations.js @@ -45,4 +45,7 @@ export default { updateDailyTracks(state, dailyTracks) { state.dailyTracks = dailyTracks; }, + updateLastfm(state, session) { + state.lastfm = session; + }, }; diff --git a/src/store/state.js b/src/store/state.js index 6dbccc2..f5ccf33 100644 --- a/src/store/state.js +++ b/src/store/state.js @@ -35,6 +35,7 @@ export default { }, }, dailyTracks: [], + lastfm: JSON.parse(localStorage.getItem("lastfm")) || {}, player: JSON.parse(localStorage.getItem("player")), settings: JSON.parse(localStorage.getItem("settings")), data: JSON.parse(localStorage.getItem("data")), diff --git a/src/utils/Player.js b/src/utils/Player.js index b486c40..72fd4c3 100644 --- a/src/utils/Player.js +++ b/src/utils/Player.js @@ -9,6 +9,7 @@ import { getArtist } from "@/api/artist"; import { personalFM, fmTrash } from "@/api/others"; import store from "@/store"; import { isAccountLoggedIn } from "@/utils/auth"; +import { trackUpdateNowPlaying, trackScrobble } from "@/api/lastfm"; const electron = process.env.IS_ELECTRON === true ? window.require("electron") : null; @@ -160,16 +161,29 @@ export default class { this._shuffledList = shuffle(list); if (firstTrackID !== "first") this._shuffledList.unshift(firstTrackID); } - async _scrobble(complete = false) { - let time = this._howler.seek(); - if (complete) { - time = ~~(this._currentTrack.dt / 100); - } + async _scrobble(track, time, complete = false) { + console.log("scrobble"); + const trackDuration = ~~(track.dt / 1000); scrobble({ - id: this._currentTrack.id, + id: track.id, sourceid: this.playlistSource.id, - time, + time: complete ? trackDuration : time, }); + if ( + store.state.lastfm.key !== undefined && + (time >= trackDuration / 2 || time >= 240) + ) { + console.log({ currentTrack: track }); + const timestamp = ~~(new Date().getTime() / 1000) - time; + trackScrobble({ + artist: track.ar[0].name, + track: track.name, + timestamp, + album: track.al.name, + trackNumber: track.no, + duration: trackDuration, + }); + } } _playAudioSource(source, autoplay = true) { Howler.unload(); @@ -235,6 +249,7 @@ export default class { autoplay = true, ifUnplayableThen = "playNextTrack" ) { + if (autoplay) this._scrobble(this.currentTrack, this._howler.seek(), true); return getTrackDetail(id).then((data) => { let track = data.songs[0]; this._currentTrack = track; @@ -321,7 +336,6 @@ export default class { } } _nextTrackCallback() { - this._scrobble(true); if (this.repeatMode === "one") { this._replaceCurrentTrack(this._currentTrack.id); } else { @@ -410,6 +424,16 @@ export default class { this._playing = true; document.title = `${this._currentTrack.name} · ${this._currentTrack.ar[0].name} - YesPlayMusic`; this._playDiscordPresence(this._currentTrack, this.seek()); + if (store.state.lastfm.key !== undefined) { + console.log({ currentTrack: this.currentTrack }); + trackUpdateNowPlaying({ + artist: this.currentTrack.ar[0].name, + track: this.currentTrack.name, + album: this.currentTrack.al.name, + trackNumber: this.currentTrack.no, + duration: ~~(this.currentTrack.dt / 1000), + }); + } } playOrPause() { if (this._howler.playing()) { diff --git a/src/views/lastfmCallback.vue b/src/views/lastfmCallback.vue new file mode 100644 index 0000000..981fd40 --- /dev/null +++ b/src/views/lastfmCallback.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/src/views/settings.vue b/src/views/settings.vue index d1776aa..22ba0ee 100644 --- a/src/views/settings.vue +++ b/src/views/settings.vue @@ -197,6 +197,25 @@ + +
+
+
+ {{ + isLastfmConnected + ? `已连接到 Last.fm (${lastfm.name})` + : "连接 Last.fm " + }}
+
+
+ + +
+
+
@@ -282,6 +301,7 @@