Authenticating with Last.fm, please wait...
+ + + diff --git a/html/options.html b/html/options.html new file mode 100644 index 0000000..ef0255c --- /dev/null +++ b/html/options.html @@ -0,0 +1,20 @@ + + +
');
-
- bp.lastfm_api.is_track_loved(bp.player.song.title,
- bp.player.song.artist,
- function(result) {
- $("#love-button").html('');
-
- if(result) {
- $("#love-button a").attr({ title: "Unlove this song"})
- .click(on_unlove)
- .addClass("loved");
-
- }
- else {
- $("#love-button a").attr({ title: "Love this song" })
- .click(on_love)
- .addClass("notloved");
- }
- });
+function render_love_button(player) {
+ $('#love-button').html('
');
+ chrome.runtime.sendMessage(
+ { type: 'is_track_loved', track: player.song.title, artist: player.song.artist },
+ function(result) {
+ $('#love-button').html('');
+ if (result) {
+ $('#love-button a')
+ .attr({ title: 'Unlove this song' })
+ .click(function() { on_unlove(player); })
+ .addClass('loved');
+ } else {
+ $('#love-button a')
+ .attr({ title: 'Love this song' })
+ .click(function() { on_love(player); })
+ .addClass('notloved');
+ }
+ }
+ );
}
-/* Event handlers */
+/* ---- Controls ---- */
+
+function toggle_play() {
+ find_play_tab(function(tab) {
+ if (!tab) return;
+ chrome.tabs.sendMessage(tab.id, { cmd: 'tgl' }, function(player) {
+ if (player) toggle_play_btn(player);
+ });
+ });
+}
+
+function prev_song() {
+ find_play_tab(function(tab) {
+ if (!tab) return;
+ chrome.tabs.sendMessage(tab.id, { cmd: 'prv' }, function(player) {
+ if (player) {
+ player.is_playing = true;
+ update_song_info(player);
+ }
+ });
+ });
+}
+
+function next_song() {
+ find_play_tab(function(tab) {
+ if (!tab) return;
+ chrome.tabs.sendMessage(tab.id, { cmd: 'nxt' }, function(player) {
+ if (player) {
+ player.is_playing = true;
+ update_song_info(player);
+ }
+ });
+ });
+}
+
+/* ---- Event handlers ---- */
-/**
- * Turn on/off scrobbling link was clicked
- */
function on_toggle_scrobble() {
- bp.toggle_scrobble();
- render_scrobble_link();
+ chrome.runtime.sendMessage({ type: 'toggle_scrobble' }, function(resp) {
+ if (resp) _state.scrobble = resp.scrobble;
+ render_scrobble_link();
+ });
}
-/**
- * Authentication link was clicked
- */
function on_auth() {
- bp.start_web_auth();
+ chrome.runtime.sendMessage({ type: 'start_web_auth' });
window.close();
}
-/**
- * Logout link was clicked
- */
function on_logout() {
- bp.clear_session();
- render_auth_link();
+ chrome.runtime.sendMessage({ type: 'clear_session' }, function() {
+ _state.session = {};
+ render_auth_link();
+ });
}
-/**
- * Love button was clicked
- */
-function on_love() {
- bp.lastfm_api.love_track(bp.player.song.title, bp.player.song.artist,
+function on_love(player) {
+ $('#love-button').html('
');
+ chrome.runtime.sendMessage(
+ { type: 'love_track', track: player.song.title, artist: player.song.artist },
function(result) {
- if(!result.error) {
- render_love_button();
- }
- else {
- if(result.error == 9) {
- // Session expired
- bp.clear_session();
+ if (!result || !result.error) {
+ render_love_button(player);
+ } else {
+ if (result.error === 9) {
+ chrome.runtime.sendMessage({ type: 'clear_session' });
+ _state.session = {};
render_auth_link();
}
-
- chrome.browserAction.setIcon({
- 'path': SETTINGS.error_icon });
}
- });
-
- $("#love-button").html('
');
+ }
+ );
}
-/**
- * Unlove button was clicked
- */
-function on_unlove() {
- bp.lastfm_api.unlove_track(bp.player.song.title, bp.player.song.artist,
+function on_unlove(player) {
+ $('#love-button').html('
');
+ chrome.runtime.sendMessage(
+ { type: 'unlove_track', track: player.song.title, artist: player.song.artist },
function(result) {
- if(!result.error) {
- render_love_button();
- }
- else {
- if(result.error == 9) {
- // Session expired
- bp.clear_session();
+ if (!result || !result.error) {
+ render_love_button(player);
+ } else {
+ if (result.error === 9) {
+ chrome.runtime.sendMessage({ type: 'clear_session' });
+ _state.session = {};
render_auth_link();
}
-
- chrome.browserAction.setIcon({
- 'path': SETTINGS.error_icon });
}
- });
+ }
+ );
+}
- $("#love-button").html('
');
+function show_alert() {
+ $('#alert').removeClass('hidden');
+ $('#extns_link').click(function() {
+ chrome.runtime.sendMessage({ type: 'open_extensions_page' });
+ });
+ $('#dismiss_alert').click(function() {
+ $('#alert').addClass('hidden');
+ chrome.storage.local.set({ seen_alert: '1' });
+ });
+}
+
+/* ---- Inline copy of find_play_tab (util.js not loaded in popup) ---- */
+function find_play_tab(callback) {
+ chrome.tabs.query({ url: '*://music.youtube.com/*' }, function(tabs) {
+ if (tabs.length > 0) {
+ callback(tabs[0]);
+ } else {
+ chrome.tabs.query({ url: '*://play.google.com/music/listen*' }, function(tabs2) {
+ callback(tabs2.length > 0 ? tabs2[0] : null);
+ });
+ }
+ });
}
diff --git a/js/settings.js b/js/settings.js
new file mode 100644
index 0000000..16eb96c
--- /dev/null
+++ b/js/settings.js
@@ -0,0 +1,54 @@
+/**
+ * settings.js
+ * Extension settings — MV3 compatible (chrome.storage.local instead of localStorage)
+ */
+
+var SETTINGS = {
+ api_key: 'd00dce85051b7dbcbfcc165eaebfc6d2',
+ api_secret: 'bdfcae3563763ece1b6d3dcdd56a7ab8',
+
+ callback_file: 'html/lastfm_callback.html',
+
+ main_icon: chrome.runtime.getURL('img/main-icon.png'),
+ playing_icon: chrome.runtime.getURL('img/main-icon-playing.png'),
+ paused_icon: chrome.runtime.getURL('img/main-icon-paused.png'),
+ error_icon: chrome.runtime.getURL('img/main-icon-error.png'),
+ scrobbling_stopped_icon: chrome.runtime.getURL('img/main-icon-scrobbling-stopped.png'),
+
+ scrobble_point: 0.7,
+ scrobble_interval: 420, // 7 minutes
+ max_scrobbles: Number.POSITIVE_INFINITY,
+
+ refresh_interval: 2,
+
+ gmusic_ads_metadata: {
+ title: "We'll be right back",
+ artist: 'Subscribe to go ad-free'
+ },
+
+ // Defaults (overwritten by loadSettings)
+ scrobble: true,
+ logs_enabled: false
+};
+
+/**
+ * Load persisted settings from chrome.storage.local.
+ * Returns a Promise that resolves once SETTINGS has been updated.
+ */
+function loadSettings() {
+ return new Promise(function(resolve) {
+ chrome.storage.local.get(
+ ['session_key', 'session_name', 'max_scrobbles', 'logs_enabled', 'scrobble'],
+ function(items) {
+ if (items.max_scrobbles !== undefined) {
+ var parsed = parseInt(items.max_scrobbles);
+ if (!isNaN(parsed)) SETTINGS.max_scrobbles = parsed;
+ }
+ SETTINGS.logs_enabled = items.logs_enabled === true;
+ // scrobble defaults to true; only false if explicitly stored as false
+ SETTINGS.scrobble = items.scrobble !== false;
+ resolve(items);
+ }
+ );
+ });
+}
\ No newline at end of file
diff --git a/js/util.js b/js/util.js
new file mode 100644
index 0000000..e83ceb8
--- /dev/null
+++ b/js/util.js
@@ -0,0 +1,26 @@
+/**
+ * util.js — Various utility functions
+ */
+
+function find_play_tab(callback) {
+ chrome.tabs.query({ url: '*://music.youtube.com/*' }, function(tabs) {
+ if (tabs.length > 0) {
+ callback(tabs[0]);
+ } else {
+ chrome.tabs.query({ url: '*://play.google.com/music/listen*' }, function(tabs2) {
+ callback(tabs2.length > 0 ? tabs2[0] : null);
+ });
+ }
+ });
+}
+
+function open_play_tab() {
+ find_play_tab(function(tab) {
+ if (tab) {
+ // MV3: use 'active' + 'highlighted' instead of deprecated 'selected'
+ chrome.tabs.update(tab.id, { active: true, highlighted: true });
+ } else {
+ chrome.tabs.create({ url: 'https://music.youtube.com', active: true });
+ }
+ });
+}
diff --git a/js/ytm_contentscript.js b/js/ytm_contentscript.js
new file mode 100644
index 0000000..d6adcd1
--- /dev/null
+++ b/js/ytm_contentscript.js
@@ -0,0 +1,115 @@
+/**
+ * ytm_contentscript.js — YouTube Music content script (MV3)
+ */
+
+// Default settings used in content script context
+var CS_SETTINGS = {
+ refresh_interval: 2
+};
+
+// ---------------------------------------------------------------------------
+// Player / Parser classes
+// ---------------------------------------------------------------------------
+
+function Player(parser) {
+ this.has_song = parser._get_has_song();
+ this.is_playing = parser._get_is_playing();
+ this.song = {
+ position: parser._get_song_position(),
+ time: parser._get_song_time(),
+ title: parser._get_song_title(),
+ artist: parser._get_song_artist(),
+ album_artist: parser._get_album_artist(),
+ album: parser._get_song_album(),
+ cover: parser._get_song_cover()
+ };
+}
+
+var YtMusicParser = function() {};
+
+YtMusicParser.prototype._get_has_song = function() {
+ return $('yt-formatted-string.title.ytmusic-player-bar').text().length > 0;
+};
+
+YtMusicParser.prototype._get_is_playing = function() {
+ var songTitle = this._get_song_title();
+ return songTitle.length > 0 && window.document.title.startsWith(songTitle);
+};
+
+YtMusicParser.prototype._get_song_position = function() {
+ var _time = $('span.time-info').text().split('/')[0];
+ _time = $.trim(_time).split(':');
+ if (_time.length === 2) return parseInt(_time[0]) * 60 + parseInt(_time[1]);
+ if (_time.length === 3) return parseInt(_time[0]) * 3600 + parseInt(_time[1]) * 60 + parseInt(_time[2]);
+ return null;
+};
+
+YtMusicParser.prototype._get_song_time = function() {
+ var _time = $('span.time-info').text().split('/')[1];
+ _time = $.trim(_time).split(':');
+ if (_time.length === 2) return parseInt(_time[0]) * 60 + parseInt(_time[1]);
+ if (_time.length === 3) return parseInt(_time[0]) * 3600 + parseInt(_time[1]) * 60 + parseInt(_time[2]);
+ return null;
+};
+
+YtMusicParser.prototype._get_song_title = function() {
+ return $('yt-formatted-string.title.ytmusic-player-bar').text();
+};
+
+YtMusicParser.prototype._get_song_artist = function() {
+ return $('span.subtitle.ytmusic-player-bar>yt-formatted-string>a').first().text();
+};
+
+YtMusicParser.prototype._get_album_artist = function() {
+ return $('span.subtitle.ytmusic-player-bar>yt-formatted-string>a').first().text();
+};
+
+YtMusicParser.prototype._get_song_cover = function() {
+ var albumImg = $('div.thumbnail-image-wrapper.ytmusic-player-bar>img').attr('src');
+ return albumImg || null;
+};
+
+YtMusicParser.prototype._get_song_album = function() {
+ return $('span.subtitle.style-scope.ytmusic-player-bar>yt-formatted-string>a').last().text();
+};
+
+// ---------------------------------------------------------------------------
+// Poll via sendMessage — wakes the service worker on demand each interval
+// ---------------------------------------------------------------------------
+
+chrome.storage.local.get('refresh_interval', function(items) {
+ var interval = (items.refresh_interval || CS_SETTINGS.refresh_interval) * 1000;
+ window.setInterval(function() {
+ chrome.runtime.sendMessage(
+ { type: 'player_state', state: new Player(new YtMusicParser()) },
+ function() { void chrome.runtime.lastError; }
+ );
+ }, interval);
+});
+
+// ---------------------------------------------------------------------------
+// Player control listeners
+// ---------------------------------------------------------------------------
+
+chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
+ var btn;
+ if (msg.cmd === 'tgl') {
+ btn = $('#play-pause-button')[0];
+ if (btn) btn.click();
+ setTimeout(function() { sendResponse(new Player(new YtMusicParser())); }, 100);
+ return true;
+ }
+ if (msg.cmd === 'prv') {
+ btn = $('div.left-controls-buttons>.previous-button')[0];
+ if (btn) btn.click();
+ setTimeout(function() { sendResponse(new Player(new YtMusicParser())); }, 100);
+ return true;
+ }
+ if (msg.cmd === 'nxt') {
+ btn = $('div.left-controls-buttons>.next-button')[0];
+ if (btn) btn.click();
+ setTimeout(function() { sendResponse(new Player(new YtMusicParser())); }, 100);
+ return true;
+ }
+ return false;
+});
diff --git a/lastfm_callback.html b/lastfm_callback.html
deleted file mode 100644
index b36954d..0000000
--- a/lastfm_callback.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
\ No newline at end of file
diff --git a/manifest.json b/manifest.json
index 00f2ef6..161cd75 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,55 +1,81 @@
{
- "permissions" : [
+ "name" : "YouTube Music Last.fm Scrobbler",
+ "description" : "Scrobbles songs from YouTube Music or Google Play to Last.fm",
+ "version" : "3.0.1",
+ "manifest_version": 3,
+ "host_permissions" : [
"http://ws.audioscrobbler.com/",
- "http://play.google.com/",
- "https://play.google.com/"
+ "https://music.youtube.com/"
+ ],
+ "permissions": [
+ "storage"
],
- "version" : "1.2.8",
- "manifest_version": 2,
"background": {
- "page": "background.html",
- "persistent": true
+ "service_worker": "js/background.js"
},
- "name" : "Google Play Scrobbler",
"icons" : {
"128" : "img/icon128.png",
- "64" : "img/icon64.png",
- "16" : "img/icon16.png",
- "48" : "img/icon48.png"
+ "64" : "img/icon64.png",
+ "16" : "img/icon16.png",
+ "48" : "img/icon48.png"
},
- "description" : "Scrobbles songs from Google Play to Last.fm",
- "browser_action" : {
- "default_popup" : "popup.html",
- "default_icon" : "img/main-icon.png"
+ "action" : {
+ "default_popup" : "html/popup.html",
+ "default_icon" : "img/main-icon.png"
},
"content_scripts" : [
{
"matches" : [
- "http://play.google.com/music/listen*",
- "https://play.google.com/music/listen*"
+ "*://music.youtube.com/*"
],
"js" : [
- "js/inject.js"
+ "js/jquery-1.7.min.js",
+ "js/ytm_contentscript.js"
]
- },
+ }
+ ],
+ "options_page": "html/options.html",
+ "web_accessible_resources": [
{
- "matches" : [
- "http://play.google.com/music/listen*",
- "https://play.google.com/music/listen*"
+ "resources": [
+ "html/lastfm_callback.html"
],
- "js" : [
- "js/contentscript.js"
+ "matches": [
+ "*://*/*"
]
+ }
+ ],
+ "commands": {
+ "toggle_play": {
+ "suggested_key": {
+ "default": "MediaPlayPause",
+ "linux": "Ctrl+Shift+8"
+ },
+ "description": "Toggle play/pause",
+ "global": true
},
- {
- "run_at" : "document_start",
- "matches" : [
- "http://play.google.com/music/listen*",
- "https://play.google.com/music/listen*"
- ],
- "js" : [
- "js/jquery-1.7.min.js"
- ]
+ "prev_song": {
+ "suggested_key": {
+ "default": "MediaPrevTrack",
+ "linux": "Ctrl+Shift+7"
+ },
+ "description": "Previous song",
+ "global": true
+ },
+ "next_song": {
+ "suggested_key": {
+ "default": "MediaNextTrack",
+ "linux": "Ctrl+Shift+9"
+ },
+ "description": "Next song",
+ "global": true
+ },
+ "goto_play_tab": {
+ "suggested_key": {
+ "default": "Ctrl+Shift+0"
+ },
+ "description": "Go to YT Music",
+ "global": true
}
- ]
+ }
}
diff --git a/popup.html b/popup.html
deleted file mode 100644
index 3818a5f..0000000
--- a/popup.html
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-