diff --git a/index.html b/index.html
index b31800f..0d1ad67 100644
--- a/index.html
+++ b/index.html
@@ -2595,6 +2595,169 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/js/lastfm.js b/js/lastfm.js
index 378a2d9..75bfdb6 100644
--- a/js/lastfm.js
+++ b/js/lastfm.js
@@ -190,6 +190,55 @@ export class LastFMScrobbler {
}
}
+ async authenticateWithCredentials(username, password) {
+ try {
+ const params = {
+ username: username,
+ password: password,
+ api_key: this.API_KEY,
+ method: 'auth.getMobileSession',
+ };
+
+ const signature = await this.generateSignature(params);
+
+ const formData = new URLSearchParams({
+ username: username,
+ password: password,
+ api_key: this.API_KEY,
+ method: 'auth.getMobileSession',
+ api_sig: signature,
+ format: 'json',
+ });
+
+ const response = await fetch(this.API_URL, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ body: formData,
+ });
+
+ const data = await response.json();
+
+ if (data.error) {
+ throw new Error(data.message || 'Last.fm authentication error');
+ }
+
+ if (data.session) {
+ this.saveSession(data.session.key, data.session.name);
+ return {
+ success: true,
+ username: data.session.name,
+ };
+ }
+
+ throw new Error('No session returned');
+ } catch (error) {
+ console.error('Mobile authentication failed:', error);
+ throw error;
+ }
+ }
+
async updateNowPlaying(track) {
if (!this.isAuthenticated()) return;
diff --git a/js/settings.js b/js/settings.js
index 02d246e..05f229b 100644
--- a/js/settings.js
+++ b/js/settings.js
@@ -130,6 +130,12 @@ export function initializeSettings(scrobbler, player, api, ui) {
const lastfmCustomApiSecret = document.getElementById('lastfm-custom-api-secret');
const lastfmSaveCustomCreds = document.getElementById('lastfm-save-custom-creds');
const lastfmClearCustomCreds = document.getElementById('lastfm-clear-custom-creds');
+ const lastfmCredentialAuth = document.getElementById('lastfm-credential-auth');
+ const lastfmCredentialForm = document.getElementById('lastfm-credential-form');
+ const lastfmUsernameInput = document.getElementById('lastfm-username');
+ const lastfmPasswordInput = document.getElementById('lastfm-password');
+ const lastfmLoginCredentialsBtn = document.getElementById('lastfm-login-credentials');
+ const lastfmUseOAuthBtn = document.getElementById('lastfm-use-oauth');
function updateLastFMUI() {
if (scrobbler.lastfm.isAuthenticated()) {
@@ -143,6 +149,7 @@ export function initializeSettings(scrobbler, player, api, ui) {
lastfmCustomCredsToggleSetting.style.display = 'flex';
lastfmCustomCredsToggle.checked = lastFMStorage.useCustomCredentials();
updateCustomCredsUI();
+ hideCredentialAuth();
} else {
lastfmStatus.textContent = 'Connect your Last.fm account to scrobble tracks';
lastfmConnectBtn.textContent = 'Connect Last.fm';
@@ -151,9 +158,25 @@ export function initializeSettings(scrobbler, player, api, ui) {
lastfmLoveSetting.style.display = 'none';
lastfmCustomCredsToggleSetting.style.display = 'none';
lastfmCustomCredsSetting.style.display = 'none';
+ // Hide credential auth by default - only show on OAuth failure
+ hideCredentialAuth();
}
}
+ function showCredentialAuth() {
+ if (lastfmCredentialAuth) lastfmCredentialAuth.style.display = 'block';
+ if (lastfmCredentialForm) lastfmCredentialForm.style.display = 'block';
+ // Focus on username field
+ if (lastfmUsernameInput) lastfmUsernameInput.focus();
+ }
+
+ function hideCredentialAuth() {
+ if (lastfmCredentialAuth) lastfmCredentialAuth.style.display = 'none';
+ if (lastfmCredentialForm) lastfmCredentialForm.style.display = 'none';
+ if (lastfmUsernameInput) lastfmUsernameInput.value = '';
+ if (lastfmPasswordInput) lastfmPasswordInput.value = '';
+ }
+
function updateCustomCredsUI() {
const useCustom = lastFMStorage.useCustomCredentials();
lastfmCustomCredsSetting.style.display = useCustom ? 'flex' : 'none';
@@ -197,17 +220,22 @@ export function initializeSettings(scrobbler, player, api, ui) {
lastfmConnectBtn.textContent = 'Waiting for authorization...';
let attempts = 0;
- const maxAttempts = 30;
+ const maxAttempts = 5;
const checkAuth = setInterval(async () => {
attempts++;
if (attempts > maxAttempts) {
clearInterval(checkAuth);
+ if (authWindow && !authWindow.closed) authWindow.close();
lastfmConnectBtn.textContent = 'Connect Last.fm';
lastfmConnectBtn.disabled = false;
- if (authWindow && !authWindow.closed) authWindow.close();
- alert('Authorization timed out. Please try again.');
+ // Ask user if they want to use credentials instead
+ if (
+ confirm('Authorization timed out. Would you like to login with username and password instead?')
+ ) {
+ showCredentialAuth();
+ }
return;
}
@@ -229,10 +257,13 @@ export function initializeSettings(scrobbler, player, api, ui) {
}, 2000);
} catch (error) {
console.error('Last.fm connection failed:', error);
- alert('Failed to connect to Last.fm: ' + error.message);
+ if (authWindow && !authWindow.closed) authWindow.close();
lastfmConnectBtn.textContent = 'Connect Last.fm';
lastfmConnectBtn.disabled = false;
- if (authWindow && !authWindow.closed) authWindow.close();
+ // Ask user if they want to use credentials instead
+ if (confirm('Failed to connect to Last.fm. Would you like to login with username and password instead?')) {
+ showCredentialAuth();
+ }
}
});
@@ -321,6 +352,47 @@ export function initializeSettings(scrobbler, player, api, ui) {
});
}
+ // Last.fm Credential Auth - Login with credentials
+ if (lastfmLoginCredentialsBtn) {
+ lastfmLoginCredentialsBtn.addEventListener('click', async () => {
+ const username = lastfmUsernameInput?.value?.trim();
+ const password = lastfmPasswordInput?.value;
+
+ if (!username || !password) {
+ alert('Please enter both username and password.');
+ return;
+ }
+
+ lastfmLoginCredentialsBtn.disabled = true;
+ lastfmLoginCredentialsBtn.textContent = 'Logging in...';
+
+ try {
+ const result = await scrobbler.lastfm.authenticateWithCredentials(username, password);
+ if (result.success) {
+ lastFMStorage.setEnabled(true);
+ lastfmToggle.checked = true;
+ updateLastFMUI();
+ // Clear password for security
+ if (lastfmPasswordInput) lastfmPasswordInput.value = '';
+ alert(`Successfully connected to Last.fm as ${result.username}!`);
+ }
+ } catch (error) {
+ console.error('Last.fm credential login failed:', error);
+ alert('Failed to login: ' + error.message);
+ } finally {
+ lastfmLoginCredentialsBtn.disabled = false;
+ lastfmLoginCredentialsBtn.textContent = 'Login';
+ }
+ });
+ }
+
+ // Last.fm Credential Auth - Switch back to OAuth
+ if (lastfmUseOAuthBtn) {
+ lastfmUseOAuthBtn.addEventListener('click', () => {
+ hideCredentialAuth();
+ });
+ }
+
// ========================================
// Global Scrobble Settings
// ========================================