mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-19 00:56:16 +00:00
feat: add Spotify and Amazon credential fields to Settings UI
- Add SpotifyClientID/Secret/RedirectURI and AmazonClientID/Secret/RedirectURI fields to datastore.Settings for persistent storage - Server: add amazonClientID/Secret/RedirectURI fields, SetAmazonConfig, GetSpotifyConfig/GetAmazonConfig, ReinitSpotifyService/ReinitAmazonService, and applyMusicServiceCredentials (called under lock from HandleUpdateSettings) - GET /setup/settings: expose credential fields; mask secrets as "***" when set - POST /setup/settings: apply credential updates and reinitialize services live - applyPersistedSettings: fill in music credentials from settings.json when not set via CLI/env (CLI takes precedence) - Settings tab: replace read-only Spotify status with editable Client ID / Secret / Redirect URI inputs for both Spotify and Amazon; save via existing Save button - script.js: populate and collect the six new fields in fetchSettings/updateSettings Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
ca7f8d5453
commit
147a1a8490
@@ -275,8 +275,7 @@ func main() {
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "spotify-redirect-uri",
|
||||
Usage: "Spotify OAuth redirect URI",
|
||||
Value: "ueberboese-login://spotify",
|
||||
Usage: "Spotify OAuth redirect URI (defaults to <server-url>/mgmt/spotify/callback)",
|
||||
EnvVars: []string{"SPOTIFY_REDIRECT_URI"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
@@ -301,8 +300,7 @@ func main() {
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "amazon-redirect-uri",
|
||||
Usage: "Amazon LWA OAuth redirect URI",
|
||||
Value: "ueberboese-login://amazon",
|
||||
Usage: "Amazon LWA OAuth redirect URI (defaults to <server-url>/mgmt/amazon/callback)",
|
||||
EnvVars: []string{"AMAZON_REDIRECT_URI"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
@@ -402,6 +400,7 @@ func main() {
|
||||
server.SetMirrorSettings(persisted.MirrorEnabled, persisted.MirrorEndpoints, persisted.SkipMirrorEndpoints, persisted.PreferredSource)
|
||||
server.SetInternalPaths(persisted.InternalPaths)
|
||||
server.SetSpotifyConfig(config.spotifyClientID, config.spotifyClientSecret, config.spotifyRedirectURI)
|
||||
server.SetAmazonConfig(config.amazonClientID, config.amazonClientSecret, config.amazonRedirectURI)
|
||||
server.SetMgmtConfig(config.mgmtUsername, config.mgmtPassword)
|
||||
|
||||
initMusicServices(config, server)
|
||||
@@ -738,9 +737,40 @@ func applyPersistedSettings(ds *datastore.DataStore, config *serviceConfig) data
|
||||
config.preferredSource = persisted.PreferredSource
|
||||
config.internalPaths = persisted.InternalPaths
|
||||
|
||||
// CLI/env args take precedence; only apply persisted credentials when not set via CLI.
|
||||
applyPersistedMusicServiceCredentials(config, persisted)
|
||||
|
||||
return persisted
|
||||
}
|
||||
|
||||
// applyPersistedMusicServiceCredentials fills in music service credentials from persisted
|
||||
// settings when they have not been supplied via CLI flags or environment variables.
|
||||
func applyPersistedMusicServiceCredentials(config *serviceConfig, persisted datastore.Settings) {
|
||||
if config.spotifyClientID == "" {
|
||||
config.spotifyClientID = persisted.SpotifyClientID
|
||||
}
|
||||
|
||||
if config.spotifyClientSecret == "" {
|
||||
config.spotifyClientSecret = persisted.SpotifyClientSecret
|
||||
}
|
||||
|
||||
if config.spotifyRedirectURI == "" {
|
||||
config.spotifyRedirectURI = persisted.SpotifyRedirectURI
|
||||
}
|
||||
|
||||
if config.amazonClientID == "" {
|
||||
config.amazonClientID = persisted.AmazonClientID
|
||||
}
|
||||
|
||||
if config.amazonClientSecret == "" {
|
||||
config.amazonClientSecret = persisted.AmazonClientSecret
|
||||
}
|
||||
|
||||
if config.amazonRedirectURI == "" {
|
||||
config.amazonRedirectURI = persisted.AmazonRedirectURI
|
||||
}
|
||||
}
|
||||
|
||||
func createDefaultSettings(ds *datastore.DataStore, config serviceConfig) datastore.Settings {
|
||||
settings := datastore.Settings{
|
||||
ServerURL: config.serverURL,
|
||||
|
||||
@@ -1718,6 +1718,12 @@ type Settings struct {
|
||||
PreferredSource string `json:"preferred_source,omitempty"`
|
||||
InternalPaths []string `json:"internal_paths,omitempty"`
|
||||
Shortcuts map[string]int `json:"shortcuts,omitempty"`
|
||||
SpotifyClientID string `json:"spotify_client_id,omitempty"`
|
||||
SpotifyClientSecret string `json:"spotify_client_secret,omitempty"`
|
||||
SpotifyRedirectURI string `json:"spotify_redirect_uri,omitempty"`
|
||||
AmazonClientID string `json:"amazon_client_id,omitempty"`
|
||||
AmazonClientSecret string `json:"amazon_client_secret,omitempty"`
|
||||
AmazonRedirectURI string `json:"amazon_redirect_uri,omitempty"`
|
||||
}
|
||||
|
||||
// GetSettings retrieves the global service settings.
|
||||
|
||||
@@ -161,10 +161,26 @@ func (s *Server) HandleGetSettings(w http.ResponseWriter, _ *http.Request) {
|
||||
redact, logBody, record := s.proxyRedact, s.proxyLogBody, s.recordEnabled
|
||||
shortcuts := s.shortcuts
|
||||
spotifyConfigured := s.spotifyService != nil
|
||||
spotifyClientID := s.spotifyClientID
|
||||
spotifyClientSecret := s.spotifyClientSecret
|
||||
spotifyRedirectURI := s.spotifyRedirectURI
|
||||
amazonConfigured := s.amazonService != nil
|
||||
amazonClientID := s.amazonClientID
|
||||
amazonClientSecret := s.amazonClientSecret
|
||||
amazonRedirectURI := s.amazonRedirectURI
|
||||
s.mu.RUnlock()
|
||||
|
||||
dnsRunning, actualBind := s.GetDNSRunning()
|
||||
|
||||
// Mask secrets: return "***" if set so the UI can show "configured" without exposing the value.
|
||||
if spotifyClientSecret != "" {
|
||||
spotifyClientSecret = "***"
|
||||
}
|
||||
|
||||
if amazonClientSecret != "" {
|
||||
amazonClientSecret = "***"
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"server_url": serverURL,
|
||||
"https_server_url": httpsServerURL,
|
||||
@@ -185,6 +201,13 @@ func (s *Server) HandleGetSettings(w http.ResponseWriter, _ *http.Request) {
|
||||
"record_interactions": record,
|
||||
"shortcuts": shortcuts,
|
||||
"spotify_configured": spotifyConfigured,
|
||||
"spotify_client_id": spotifyClientID,
|
||||
"spotify_client_secret": spotifyClientSecret,
|
||||
"spotify_redirect_uri": spotifyRedirectURI,
|
||||
"amazon_configured": amazonConfigured,
|
||||
"amazon_client_id": amazonClientID,
|
||||
"amazon_client_secret": amazonClientSecret,
|
||||
"amazon_redirect_uri": amazonRedirectURI,
|
||||
}); err != nil {
|
||||
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -206,6 +229,12 @@ func (s *Server) HandleUpdateSettings(w http.ResponseWriter, r *http.Request) {
|
||||
PreferredSource string `json:"preferred_source"`
|
||||
InternalPaths []string `json:"internal_paths"`
|
||||
Shortcuts map[string]int `json:"shortcuts"`
|
||||
SpotifyClientID string `json:"spotify_client_id"`
|
||||
SpotifyClientSecret string `json:"spotify_client_secret"`
|
||||
SpotifyRedirectURI string `json:"spotify_redirect_uri"`
|
||||
AmazonClientID string `json:"amazon_client_id"`
|
||||
AmazonClientSecret string `json:"amazon_client_secret"`
|
||||
AmazonRedirectURI string `json:"amazon_redirect_uri"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&settings); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
@@ -263,6 +292,12 @@ func (s *Server) HandleUpdateSettings(w http.ResponseWriter, r *http.Request) {
|
||||
s.sm.ServerURL = settings.ServerURL
|
||||
}
|
||||
|
||||
// Update music service credentials (empty or "***" means "unchanged").
|
||||
s.applyMusicServiceCredentials(
|
||||
settings.SpotifyClientID, settings.SpotifyClientSecret, settings.SpotifyRedirectURI,
|
||||
settings.AmazonClientID, settings.AmazonClientSecret, settings.AmazonRedirectURI,
|
||||
)
|
||||
|
||||
// Persist to datastore
|
||||
// Access fields directly since we already hold the lock
|
||||
currentRedact := s.proxyRedact
|
||||
@@ -288,16 +323,32 @@ func (s *Server) HandleUpdateSettings(w http.ResponseWriter, r *http.Request) {
|
||||
PreferredSource: s.preferredSource,
|
||||
InternalPaths: s.internalPaths,
|
||||
Shortcuts: s.shortcuts,
|
||||
SpotifyClientID: s.spotifyClientID,
|
||||
SpotifyClientSecret: s.spotifyClientSecret,
|
||||
SpotifyRedirectURI: s.spotifyRedirectURI,
|
||||
AmazonClientID: s.amazonClientID,
|
||||
AmazonClientSecret: s.amazonClientSecret,
|
||||
AmazonRedirectURI: s.amazonRedirectURI,
|
||||
})
|
||||
|
||||
dnsEnabled := s.dnsEnabled
|
||||
dnsUpstreamStr := strings.Join(s.dnsUpstream, ",")
|
||||
dnsBindAddr := s.dnsBindAddr
|
||||
reinitSpotify := s.spotifyClientID != ""
|
||||
reinitAmazon := s.amazonClientID != ""
|
||||
|
||||
s.mu.Unlock()
|
||||
|
||||
s.SetDNSSettings(dnsEnabled, dnsUpstreamStr, dnsBindAddr)
|
||||
|
||||
if reinitSpotify {
|
||||
s.ReinitSpotifyService()
|
||||
}
|
||||
|
||||
if reinitAmazon {
|
||||
s.ReinitAmazonService()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to save settings: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
||||
@@ -57,6 +57,9 @@ type Server struct {
|
||||
spotifyClientSecret string
|
||||
spotifyRedirectURI string
|
||||
spotifyService *spotify.Service
|
||||
amazonClientID string
|
||||
amazonClientSecret string
|
||||
amazonRedirectURI string
|
||||
amazonService *amazon.Service
|
||||
}
|
||||
|
||||
@@ -313,6 +316,100 @@ func (s *Server) SetSpotifyConfig(clientID, clientSecret, redirectURI string) {
|
||||
s.spotifyRedirectURI = redirectURI
|
||||
}
|
||||
|
||||
// SetAmazonConfig sets the Amazon LWA OAuth configuration.
|
||||
func (s *Server) SetAmazonConfig(clientID, clientSecret, redirectURI string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
s.amazonClientID = clientID
|
||||
s.amazonClientSecret = clientSecret
|
||||
s.amazonRedirectURI = redirectURI
|
||||
}
|
||||
|
||||
// GetSpotifyConfig returns the current Spotify OAuth configuration.
|
||||
func (s *Server) GetSpotifyConfig() (clientID, clientSecret, redirectURI string) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
return s.spotifyClientID, s.spotifyClientSecret, s.spotifyRedirectURI
|
||||
}
|
||||
|
||||
// GetAmazonConfig returns the current Amazon LWA OAuth configuration.
|
||||
func (s *Server) GetAmazonConfig() (clientID, clientSecret, redirectURI string) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
return s.amazonClientID, s.amazonClientSecret, s.amazonRedirectURI
|
||||
}
|
||||
|
||||
// applyMusicServiceCredentials updates music service credential fields on the server.
|
||||
// Must be called with s.mu held. Empty string or "***" (the masked GET value) means "unchanged".
|
||||
func (s *Server) applyMusicServiceCredentials(spotifyID, spotifySecret, spotifyURI, amazonID, amazonSecret, amazonURI string) {
|
||||
if spotifyID != "" {
|
||||
s.spotifyClientID = spotifyID
|
||||
}
|
||||
|
||||
if spotifySecret != "" && spotifySecret != "***" {
|
||||
s.spotifyClientSecret = spotifySecret
|
||||
}
|
||||
|
||||
if spotifyURI != "" {
|
||||
s.spotifyRedirectURI = spotifyURI
|
||||
}
|
||||
|
||||
if amazonID != "" {
|
||||
s.amazonClientID = amazonID
|
||||
}
|
||||
|
||||
if amazonSecret != "" && amazonSecret != "***" {
|
||||
s.amazonClientSecret = amazonSecret
|
||||
}
|
||||
|
||||
if amazonURI != "" {
|
||||
s.amazonRedirectURI = amazonURI
|
||||
}
|
||||
}
|
||||
|
||||
// ReinitSpotifyService creates a new Spotify service from current config and replaces the running one.
|
||||
func (s *Server) ReinitSpotifyService() {
|
||||
clientID, clientSecret, redirectURI := s.GetSpotifyConfig()
|
||||
if clientID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if redirectURI == "" {
|
||||
redirectURI = s.serverURL + "/mgmt/spotify/callback"
|
||||
}
|
||||
|
||||
svc := spotify.NewSpotifyService(clientID, clientSecret, redirectURI, s.ds.DataDir)
|
||||
if err := svc.Load(); err != nil {
|
||||
log.Printf("[Spotify] Failed to load accounts during reinit: %v", err)
|
||||
}
|
||||
|
||||
s.SetSpotifyService(svc)
|
||||
log.Printf("[Spotify] Service reinitialized")
|
||||
}
|
||||
|
||||
// ReinitAmazonService creates a new Amazon service from current config and replaces the running one.
|
||||
func (s *Server) ReinitAmazonService() {
|
||||
clientID, clientSecret, redirectURI := s.GetAmazonConfig()
|
||||
if clientID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if redirectURI == "" {
|
||||
redirectURI = s.serverURL + "/mgmt/amazon/callback"
|
||||
}
|
||||
|
||||
svc := amazon.NewAmazonService(clientID, clientSecret, redirectURI, s.ds.DataDir)
|
||||
if err := svc.Load(); err != nil {
|
||||
log.Printf("[Amazon] Failed to load accounts during reinit: %v", err)
|
||||
}
|
||||
|
||||
s.SetAmazonService(svc)
|
||||
log.Printf("[Amazon] Service reinitialized")
|
||||
}
|
||||
|
||||
// SetMgmtConfig sets the management API authentication credentials.
|
||||
func (s *Server) SetMgmtConfig(username, password string) {
|
||||
s.mu.Lock()
|
||||
|
||||
@@ -264,11 +264,38 @@
|
||||
</div>
|
||||
<div style="margin-bottom: 20px">
|
||||
<strong>Spotify Integration:</strong>
|
||||
<div
|
||||
id="spotify-config-status"
|
||||
style="margin-top: 5px; font-size: 0.9em"
|
||||
>
|
||||
Checking configuration...
|
||||
<div style="margin-top: 8px; margin-left: 20px">
|
||||
<div style="margin-bottom: 8px">
|
||||
<label for="spotify-client-id" style="display: inline-block; width: 130px">Client ID:</label>
|
||||
<input type="text" id="spotify-client-id" placeholder="32-char hex string" style="width: 280px"/>
|
||||
</div>
|
||||
<div style="margin-bottom: 8px">
|
||||
<label for="spotify-client-secret" style="display: inline-block; width: 130px">Client Secret:</label>
|
||||
<input type="password" id="spotify-client-secret" placeholder="(leave blank to keep existing)" style="width: 280px"/>
|
||||
</div>
|
||||
<div style="margin-bottom: 8px">
|
||||
<label for="spotify-redirect-uri" style="display: inline-block; width: 130px">Redirect URI:</label>
|
||||
<input type="text" id="spotify-redirect-uri" placeholder="<your-service-url>/mgmt/spotify/callback" style="width: 280px"/>
|
||||
</div>
|
||||
<div id="spotify-config-status" style="margin-top: 5px; font-size: 0.9em">Checking configuration...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-bottom: 20px">
|
||||
<strong>Amazon Music Integration:</strong>
|
||||
<div style="margin-top: 8px; margin-left: 20px">
|
||||
<div style="margin-bottom: 8px">
|
||||
<label for="amazon-client-id" style="display: inline-block; width: 130px">Client ID:</label>
|
||||
<input type="text" id="amazon-client-id" placeholder="amzn1.application-oa2-client..." style="width: 280px"/>
|
||||
</div>
|
||||
<div style="margin-bottom: 8px">
|
||||
<label for="amazon-client-secret" style="display: inline-block; width: 130px">Client Secret:</label>
|
||||
<input type="password" id="amazon-client-secret" placeholder="(leave blank to keep existing)" style="width: 280px"/>
|
||||
</div>
|
||||
<div style="margin-bottom: 8px">
|
||||
<label for="amazon-redirect-uri" style="display: inline-block; width: 130px">Redirect URI:</label>
|
||||
<input type="text" id="amazon-redirect-uri" placeholder="<your-service-url>/mgmt/amazon/callback" style="width: 280px"/>
|
||||
</div>
|
||||
<div id="amazon-config-status" style="margin-top: 5px; font-size: 0.9em">Checking configuration...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-bottom: 20px">
|
||||
@@ -1485,6 +1512,21 @@
|
||||
<div id="spotify-reg-status" style="margin-top: 10px; font-size: 0.9em;"></div>
|
||||
</div>
|
||||
|
||||
<div id="amazon-registration-container" class="summary-box" style="margin-top: 20px;">
|
||||
<h3>Amazon Music Integration</h3>
|
||||
<p style="font-size: 0.9em; color: #555;">
|
||||
Register a new Amazon Music source for this local account. This mimics the official SoundTouch app flow:
|
||||
</p>
|
||||
<ol style="font-size: 0.85em; color: #555; margin-bottom: 15px;">
|
||||
<li>Exchange OAuth code for an Amazon LWA token.</li>
|
||||
<li>Register the source in the local Marge cloud profile.</li>
|
||||
</ol>
|
||||
<button id="connect-amazon-account-btn" onclick="connectAmazonToAccount()" style="background: #ff9900; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer;">
|
||||
Connect Amazon Music to this Account
|
||||
</button>
|
||||
<div id="amazon-reg-status" style="margin-top: 10px; font-size: 0.9em;"></div>
|
||||
</div>
|
||||
|
||||
<div id="account-devices-container">
|
||||
<h3>Connected Devices</h3>
|
||||
<div id="account-devices-list">Select an account to view devices.</div>
|
||||
|
||||
@@ -165,12 +165,43 @@ async function fetchSettings() {
|
||||
document.getElementById("internal-paths").value = settings.internal_paths.join("\n");
|
||||
}
|
||||
|
||||
// Spotify credential fields
|
||||
if (settings.spotify_client_id !== undefined) {
|
||||
document.getElementById("spotify-client-id").value = settings.spotify_client_id || "";
|
||||
}
|
||||
// Secret is masked to "***" by the backend when set; leave the password input blank
|
||||
// so the placeholder "(leave blank to keep existing)" is shown.
|
||||
document.getElementById("spotify-client-secret").value = "";
|
||||
if (settings.spotify_redirect_uri !== undefined) {
|
||||
document.getElementById("spotify-redirect-uri").value = settings.spotify_redirect_uri || "";
|
||||
}
|
||||
const spotifyStatus = document.getElementById("spotify-config-status");
|
||||
if (spotifyStatus) {
|
||||
if (settings.spotify_configured) {
|
||||
spotifyStatus.innerHTML = '<span style="color: green;">✅ Configured</span> (Client ID present)';
|
||||
spotifyStatus.innerHTML = '<span style="color: green;">✅ Active</span>';
|
||||
} else if (settings.spotify_client_id) {
|
||||
spotifyStatus.innerHTML = '<span style="color: orange;">⚠ Credentials saved — restart or re-save to activate</span>';
|
||||
} else {
|
||||
spotifyStatus.innerHTML = '<span style="color: #666;">❌ Not Configured</span><br>' + '<span style="font-size: 0.85em; color: #888;">To enable Spotify, provide <code>SPOTIFY_CLIENT_ID</code> and <code>SPOTIFY_CLIENT_SECRET</code> to the server.</span>';
|
||||
spotifyStatus.innerHTML = '<span style="color: #666;">❌ Not configured</span>';
|
||||
}
|
||||
}
|
||||
|
||||
// Amazon credential fields
|
||||
if (settings.amazon_client_id !== undefined) {
|
||||
document.getElementById("amazon-client-id").value = settings.amazon_client_id || "";
|
||||
}
|
||||
document.getElementById("amazon-client-secret").value = "";
|
||||
if (settings.amazon_redirect_uri !== undefined) {
|
||||
document.getElementById("amazon-redirect-uri").value = settings.amazon_redirect_uri || "";
|
||||
}
|
||||
const amazonStatus = document.getElementById("amazon-config-status");
|
||||
if (amazonStatus) {
|
||||
if (settings.amazon_configured) {
|
||||
amazonStatus.innerHTML = '<span style="color: green;">✅ Active</span>';
|
||||
} else if (settings.amazon_client_id) {
|
||||
amazonStatus.innerHTML = '<span style="color: orange;">⚠ Credentials saved — restart or re-save to activate</span>';
|
||||
} else {
|
||||
amazonStatus.innerHTML = '<span style="color: #666;">❌ Not configured</span>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,6 +264,12 @@ async function updateSettings() {
|
||||
.value.split("\n")
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s !== ""),
|
||||
spotify_client_id: document.getElementById("spotify-client-id").value,
|
||||
spotify_client_secret: document.getElementById("spotify-client-secret").value,
|
||||
spotify_redirect_uri: document.getElementById("spotify-redirect-uri").value,
|
||||
amazon_client_id: document.getElementById("amazon-client-id").value,
|
||||
amazon_client_secret: document.getElementById("amazon-client-secret").value,
|
||||
amazon_redirect_uri: document.getElementById("amazon-redirect-uri").value,
|
||||
};
|
||||
const status = document.getElementById("settings-status");
|
||||
status.innerText = "Saving...";
|
||||
@@ -502,8 +539,10 @@ async function fetchAccountDetails(accountId) {
|
||||
const metadataEl = document.getElementById("account-metadata");
|
||||
const devicesEl = document.getElementById("account-devices-list");
|
||||
const regStatus = document.getElementById("spotify-reg-status");
|
||||
const amazonRegStatus = document.getElementById("amazon-reg-status");
|
||||
|
||||
if (regStatus) regStatus.innerText = "";
|
||||
if (amazonRegStatus) amazonRegStatus.innerText = "";
|
||||
if (metadataEl) metadataEl.innerHTML = "Loading...";
|
||||
if (devicesEl) devicesEl.innerHTML = "Loading devices...";
|
||||
|
||||
@@ -819,6 +858,47 @@ async function connectSpotifyToAccount() {
|
||||
}
|
||||
}
|
||||
|
||||
async function connectAmazonToAccount() {
|
||||
const selector = document.getElementById("account-selector");
|
||||
const accountId = selector ? selector.value : "default";
|
||||
const statusEl = document.getElementById("amazon-reg-status");
|
||||
|
||||
if (statusEl) statusEl.innerHTML = "Initializing Amazon Music authorization...";
|
||||
|
||||
try {
|
||||
const response = await fetch(`/mgmt/amazon/init?account=${encodeURIComponent(accountId)}`, {
|
||||
method: "POST"
|
||||
});
|
||||
if (!response.ok) {
|
||||
const err = await response.text();
|
||||
throw new Error(err || response.statusText);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const redirectUrl = data.redirectUrl;
|
||||
|
||||
if (statusEl) {
|
||||
statusEl.innerHTML = `Amazon Music authorization window opened. <br/>If it didn't open, <a href="${redirectUrl}" target="_blank">click here to authorize</a>.`;
|
||||
}
|
||||
|
||||
window.open(redirectUrl, "AmazonAuth", "width=600,height=800");
|
||||
|
||||
let pollCount = 0;
|
||||
const interval = setInterval(async () => {
|
||||
pollCount++;
|
||||
if (pollCount > 60) {
|
||||
clearInterval(interval);
|
||||
return;
|
||||
}
|
||||
await fetchAccountDetails(accountId);
|
||||
}, 5000);
|
||||
|
||||
} catch (error) {
|
||||
if (statusEl) statusEl.innerHTML = `<span style="color:red">Error: ${error.message}</span>`;
|
||||
console.error("Amazon link failed", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchInteractionStats() {
|
||||
console.log("Fetching interaction stats...");
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user