feat: username and password scrobbling for last.fm
This commit is contained in:
parent
3974ec7551
commit
43be45b76f
3 changed files with 289 additions and 5 deletions
163
index.html
163
index.html
|
|
@ -2595,6 +2595,169 @@
|
||||||
<div id="lastfm-controls">
|
<div id="lastfm-controls">
|
||||||
<button id="lastfm-connect-btn" class="btn-secondary">Connect Last.fm</button>
|
<button id="lastfm-connect-btn" class="btn-secondary">Connect Last.fm</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="lastfm-credential-auth" style="display: none; margin-top: 12px">
|
||||||
|
<div id="lastfm-credential-form" style="display: none">
|
||||||
|
<p style="margin: 0 0 8px 0; font-size: 0.85rem; color: var(--muted)">
|
||||||
|
Enter your Last.fm credentials:
|
||||||
|
</p>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="lastfm-username"
|
||||||
|
placeholder="Username"
|
||||||
|
style="
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--background);
|
||||||
|
color: var(--foreground);
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="lastfm-password"
|
||||||
|
placeholder="Password"
|
||||||
|
style="
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--background);
|
||||||
|
color: var(--foreground);
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<div style="display: flex; gap: 8px">
|
||||||
|
<button
|
||||||
|
id="lastfm-login-credentials"
|
||||||
|
class="btn-secondary"
|
||||||
|
style="padding: 6px 12px; font-size: 0.85rem; flex: 1"
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
id="lastfm-use-oauth"
|
||||||
|
class="btn-secondary"
|
||||||
|
style="padding: 6px 12px; font-size: 0.85rem; flex: 1"
|
||||||
|
>
|
||||||
|
Use OAuth Instead
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="lastfm-credential-auth" style="display: none; margin-top: 12px">
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 8px">
|
||||||
|
<div id="lastfm-credential-toggle-container">
|
||||||
|
<button
|
||||||
|
id="lastfm-show-credential-auth"
|
||||||
|
class="btn-secondary"
|
||||||
|
style="padding: 6px 12px; font-size: 0.85rem; width: 100%"
|
||||||
|
>
|
||||||
|
Login with Username/Password
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="lastfm-credential-form" style="display: none">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="lastfm-username"
|
||||||
|
placeholder="Last.fm Username"
|
||||||
|
style="
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--background);
|
||||||
|
color: var(--foreground);
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="lastfm-password"
|
||||||
|
placeholder="Last.fm Password"
|
||||||
|
style="
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--background);
|
||||||
|
color: var(--foreground);
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<div style="display: flex; gap: 8px">
|
||||||
|
<button
|
||||||
|
id="lastfm-login-credentials"
|
||||||
|
class="btn-secondary"
|
||||||
|
style="padding: 6px 12px; font-size: 0.85rem; flex: 1"
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
id="lastfm-use-oauth"
|
||||||
|
class="btn-secondary"
|
||||||
|
style="padding: 6px 12px; font-size: 0.85rem; flex: 1"
|
||||||
|
>
|
||||||
|
Use OAuth
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="lastfm-credential-auth" style="margin-top: 12px; display: none">
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 8px">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="lastfm-username"
|
||||||
|
placeholder="Last.fm Username"
|
||||||
|
style="
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--background);
|
||||||
|
color: var(--foreground);
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="lastfm-password"
|
||||||
|
placeholder="Last.fm Password"
|
||||||
|
style="
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--background);
|
||||||
|
color: var(--foreground);
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<div style="display: flex; gap: 8px; margin-top: 4px">
|
||||||
|
<button
|
||||||
|
id="lastfm-login-credentials"
|
||||||
|
class="btn-secondary"
|
||||||
|
style="padding: 6px 12px; font-size: 0.85rem"
|
||||||
|
>
|
||||||
|
Login with Credentials
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
id="lastfm-use-oauth"
|
||||||
|
class="btn-secondary"
|
||||||
|
style="padding: 6px 12px; font-size: 0.85rem"
|
||||||
|
>
|
||||||
|
Use OAuth Instead
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; gap: 8px; margin-top: 4px">
|
||||||
|
<button
|
||||||
|
id="lastfm-show-credential-auth"
|
||||||
|
class="btn-secondary"
|
||||||
|
style="padding: 6px 12px; font-size: 0.85rem"
|
||||||
|
>
|
||||||
|
Login with Username/Password
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="setting-item" id="lastfm-toggle-setting" style="display: none">
|
<div class="setting-item" id="lastfm-toggle-setting" style="display: none">
|
||||||
|
|
|
||||||
49
js/lastfm.js
49
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) {
|
async updateNowPlaying(track) {
|
||||||
if (!this.isAuthenticated()) return;
|
if (!this.isAuthenticated()) return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,12 @@ export function initializeSettings(scrobbler, player, api, ui) {
|
||||||
const lastfmCustomApiSecret = document.getElementById('lastfm-custom-api-secret');
|
const lastfmCustomApiSecret = document.getElementById('lastfm-custom-api-secret');
|
||||||
const lastfmSaveCustomCreds = document.getElementById('lastfm-save-custom-creds');
|
const lastfmSaveCustomCreds = document.getElementById('lastfm-save-custom-creds');
|
||||||
const lastfmClearCustomCreds = document.getElementById('lastfm-clear-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() {
|
function updateLastFMUI() {
|
||||||
if (scrobbler.lastfm.isAuthenticated()) {
|
if (scrobbler.lastfm.isAuthenticated()) {
|
||||||
|
|
@ -143,6 +149,7 @@ export function initializeSettings(scrobbler, player, api, ui) {
|
||||||
lastfmCustomCredsToggleSetting.style.display = 'flex';
|
lastfmCustomCredsToggleSetting.style.display = 'flex';
|
||||||
lastfmCustomCredsToggle.checked = lastFMStorage.useCustomCredentials();
|
lastfmCustomCredsToggle.checked = lastFMStorage.useCustomCredentials();
|
||||||
updateCustomCredsUI();
|
updateCustomCredsUI();
|
||||||
|
hideCredentialAuth();
|
||||||
} else {
|
} else {
|
||||||
lastfmStatus.textContent = 'Connect your Last.fm account to scrobble tracks';
|
lastfmStatus.textContent = 'Connect your Last.fm account to scrobble tracks';
|
||||||
lastfmConnectBtn.textContent = 'Connect Last.fm';
|
lastfmConnectBtn.textContent = 'Connect Last.fm';
|
||||||
|
|
@ -151,9 +158,25 @@ export function initializeSettings(scrobbler, player, api, ui) {
|
||||||
lastfmLoveSetting.style.display = 'none';
|
lastfmLoveSetting.style.display = 'none';
|
||||||
lastfmCustomCredsToggleSetting.style.display = 'none';
|
lastfmCustomCredsToggleSetting.style.display = 'none';
|
||||||
lastfmCustomCredsSetting.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() {
|
function updateCustomCredsUI() {
|
||||||
const useCustom = lastFMStorage.useCustomCredentials();
|
const useCustom = lastFMStorage.useCustomCredentials();
|
||||||
lastfmCustomCredsSetting.style.display = useCustom ? 'flex' : 'none';
|
lastfmCustomCredsSetting.style.display = useCustom ? 'flex' : 'none';
|
||||||
|
|
@ -197,17 +220,22 @@ export function initializeSettings(scrobbler, player, api, ui) {
|
||||||
lastfmConnectBtn.textContent = 'Waiting for authorization...';
|
lastfmConnectBtn.textContent = 'Waiting for authorization...';
|
||||||
|
|
||||||
let attempts = 0;
|
let attempts = 0;
|
||||||
const maxAttempts = 30;
|
const maxAttempts = 5;
|
||||||
|
|
||||||
const checkAuth = setInterval(async () => {
|
const checkAuth = setInterval(async () => {
|
||||||
attempts++;
|
attempts++;
|
||||||
|
|
||||||
if (attempts > maxAttempts) {
|
if (attempts > maxAttempts) {
|
||||||
clearInterval(checkAuth);
|
clearInterval(checkAuth);
|
||||||
|
if (authWindow && !authWindow.closed) authWindow.close();
|
||||||
lastfmConnectBtn.textContent = 'Connect Last.fm';
|
lastfmConnectBtn.textContent = 'Connect Last.fm';
|
||||||
lastfmConnectBtn.disabled = false;
|
lastfmConnectBtn.disabled = false;
|
||||||
if (authWindow && !authWindow.closed) authWindow.close();
|
// Ask user if they want to use credentials instead
|
||||||
alert('Authorization timed out. Please try again.');
|
if (
|
||||||
|
confirm('Authorization timed out. Would you like to login with username and password instead?')
|
||||||
|
) {
|
||||||
|
showCredentialAuth();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -229,10 +257,13 @@ export function initializeSettings(scrobbler, player, api, ui) {
|
||||||
}, 2000);
|
}, 2000);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Last.fm connection failed:', 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.textContent = 'Connect Last.fm';
|
||||||
lastfmConnectBtn.disabled = false;
|
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
|
// Global Scrobble Settings
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue