From 7b256b33676cf54becdff127ef0c1c120b881ca4 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Mon, 23 Jun 2025 21:53:40 +0530 Subject: [PATCH 01/48] add few tracks in main.tf --- main.tf | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/main.tf b/main.tf index 8264910..f6032dc 100644 --- a/main.tf +++ b/main.tf @@ -41,6 +41,25 @@ data "spotify_track" "A_View_To_Kill" { data "spotify_track" "Veridis_Quo" { url = "https://open.spotify.com/track/2LD2gT7gwAurzdQDQtILds?si=392db9f1e95849e6" } + +data "spotify_track" "instant_crush" { + url = "https://open.spotify.com/track/2cGxRwrMyEAp8dEbuZaVv6?si=0e20066520e04bef" +} + +data "spotify_track" "nightcall" { + url = "https://open.spotify.com/track/0U0ldCRmgCqhVvD6ksG63j?si=ca6db3e2a5584f04" +} + +data "spotify_track" "lady_hear_me_tonight" { + url = "https://open.spotify.com/track/49X0LAl6faAusYq02PRAY6?si=9059c63e0984402b" +} + +data "spotify_track" "supermassive_black_hole" { + url = "https://open.spotify.com/track/3lPr8ghNDBLc2uZovNyLs9?si=6dac397517d3427f" +} + + + resource "spotify_playlist" "playlist" { name = "The CodeJam Playlist" description = "Wishing you make the nicest, most Randomly Accessible Memories this CodeR̶A̶M̶Jam :)" @@ -53,6 +72,10 @@ resource "spotify_playlist" "playlist" { data.spotify_search_track.Parcels.tracks[*].id, data.spotify_track.Zenith.id, data.spotify_track.A_View_To_Kill.id, - data.spotify_track.Veridis_Quo.id + data.spotify_track.Veridis_Quo.id, + data.spotify_track.instant_crush.id, + data.spotify_track.nightcall.id, + data.spotify_track.lady_hear_me_tonight.id, + data.spotify_track.supermassive_black_hole.id ]) } From 56db040b71d436bbce135a7f6bb00d779ecd5c02 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Mon, 23 Jun 2025 22:57:10 +0530 Subject: [PATCH 02/48] change main.tf --- main.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/main.tf b/main.tf index f6032dc..1ef250a 100644 --- a/main.tf +++ b/main.tf @@ -73,6 +73,7 @@ resource "spotify_playlist" "playlist" { data.spotify_track.Zenith.id, data.spotify_track.A_View_To_Kill.id, data.spotify_track.Veridis_Quo.id, + // added tracks data.spotify_track.instant_crush.id, data.spotify_track.nightcall.id, data.spotify_track.lady_hear_me_tonight.id, From 80a4711c9b433730192b58119c1e6f847cfaf631 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Mon, 23 Jun 2025 23:13:11 +0530 Subject: [PATCH 03/48] Improve Puppeteer stability for slow login flow --- .github/workflows/tf-apply.yaml | 40 ++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index 02a253f..f173f4a 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -99,6 +99,7 @@ on: push: branches: - main + jobs: spotify-auth-and-apply: runs-on: ubuntu-latest @@ -147,7 +148,7 @@ jobs: run: | node - < Date: Mon, 23 Jun 2025 23:24:07 +0530 Subject: [PATCH 04/48] Improve Puppeteer flow --- .github/workflows/tf-apply.yaml | 78 ++++++++++++++++----------------- 1 file changed, 38 insertions(+), 40 deletions(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index f173f4a..f40f281 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -95,6 +95,7 @@ # terraform apply -var="SPOTIFY_API_KEY=${{ env.SPOTIFY_API_KEY }}" -auto-approve name: Update playlist with Spotify Auth + on: push: branches: @@ -125,9 +126,14 @@ jobs: spotify_auth_proxy > proxy_output.log 2>&1 & PROXY_PID=$! echo "PROXY_PID=$PROXY_PID" >> $GITHUB_ENV - while ! (grep -q "Auth URL:" proxy_output.log && grep -q "APIKey:" proxy_output.log); do + echo "Waiting for Auth URL and API Key..." + for i in {1..30}; do + if grep -q "Auth URL:" proxy_output.log && grep -q "APIKey:" proxy_output.log; then break; fi sleep 1 done + if ! (grep -q "Auth URL:" proxy_output.log && grep -q "APIKey:" proxy_output.log); then + echo "❌ Failed to get Auth URL or API Key"; cat proxy_output.log; exit 1 + fi auth_url=$(grep "Auth URL:" proxy_output.log | awk '{print $3}') api_key=$(grep "APIKey:" proxy_output.log | awk '{print $2}') echo "AUTH_URL=$auth_url" >> $GITHUB_ENV @@ -146,7 +152,7 @@ jobs: SPOTIFY_USERNAME: ${{ secrets.SPOTIFY_USERNAME }} SPOTIFY_PASSWORD: ${{ secrets.SPOTIFY_PASSWORD }} run: | - node - < { const browser = await puppeteer.launch({ headless: true, - args: ['--no-sandbox', '--disable-setuid-sandbox', '--window-size=1920,1080'], - defaultViewport: { width: 1920, height: 1080 } + args: ['--no-sandbox', '--disable-setuid-sandbox'], + defaultViewport: { width: 1366, height: 768 } }); + const page = await browser.newPage(); - await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'); + await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'); try { - console.log("Navigating to Spotify Auth URL..."); - await page.goto('${{ env.AUTH_URL }}', { waitUntil: 'networkidle0', timeout: 60000 }); - await delay(3000); // extra delay for slow connections - - if (await page.$('#onetrust-accept-btn-handler')) { - console.log("Cookie banner detected. Accepting..."); - await page.click('#onetrust-accept-btn-handler'); + console.log("➡️ Opening Auth URL..."); + await page.goto(process.env.AUTH_URL, { waitUntil: 'networkidle2', timeout: 60000 }); + await delay(3000); + + const cookieAccept = await page.$('#onetrust-accept-btn-handler'); + if (cookieAccept) { + console.log("🍪 Accepting cookies..."); + await cookieAccept.click(); await delay(2000); } - console.log("Waiting for login form..."); - await page.waitForSelector('#login-username', { visible: true, timeout: 15000 }); - - console.log("Typing username..."); + console.log("⌨️ Typing login credentials..."); + await page.waitForSelector('#login-username', { timeout: 15000 }); await typeWithRandomDelay(page, '#login-username', process.env.SPOTIFY_USERNAME); - await delay(getRandomDelay(500, 1500)); - - console.log("Typing password..."); - await page.waitForSelector('#login-password', { visible: true, timeout: 15000 }); + await delay(getRandomDelay(500, 1000)); + await page.waitForSelector('#login-password', { timeout: 15000 }); await typeWithRandomDelay(page, '#login-password', process.env.SPOTIFY_PASSWORD); - await delay(getRandomDelay(1000, 2000)); - console.log("Clicking login button..."); await page.click('#login-button'); + await delay(3000); - const authAcceptSelector = '#auth-accept'; - const isAuthAcceptPresent = await waitForSelectorWithTimeout(page, authAcceptSelector, 20000); - - if (isAuthAcceptPresent) { - console.log('Permission request detected. Accepting...'); - await page.click(authAcceptSelector); - await page.waitForNavigation({ waitUntil: 'networkidle0', timeout: 30000 }); + const authAccept = await waitForSelectorWithTimeout(page, '#auth-accept', 20000); + if (authAccept) { + console.log("🔐 Accepting Spotify auth permissions..."); + await page.click('#auth-accept'); + await page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 30000 }); } - const content = await page.content(); - if (content.includes('Authorization successful') || content.includes('callback?code=')) { - console.log('Spotify authorization successful'); + const pageContent = await page.content(); + if (pageContent.includes('Authorization successful') || page.url().includes('callback?code=')) { + console.log('✅ Spotify authorization successful'); } else { - throw new Error('Authorization not successful'); + throw new Error('❌ Spotify authorization failed.'); } - } catch (error) { - console.error('Error during Spotify login or authorization:', error); + } catch (err) { + console.error("💥 Error during Spotify login:", err); process.exit(1); } finally { await browser.close(); From 9526e95710e625e09d72e8ca694ff1ae18412e24 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Mon, 23 Jun 2025 23:28:24 +0530 Subject: [PATCH 05/48] Improve Puppeteer flow-2 --- .github/workflows/tf-apply.yaml | 75 ++++++++++++++++----------------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index f40f281..105c1ea 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -147,37 +147,29 @@ jobs: - name: Install Puppeteer run: npm install puppeteer - - name: Perform Spotify login and authorization + - name: Perform Spotify login and authorization env: SPOTIFY_USERNAME: ${{ secrets.SPOTIFY_USERNAME }} SPOTIFY_PASSWORD: ${{ secrets.SPOTIFY_PASSWORD }} run: | node - <<'EOF' const puppeteer = require('puppeteer'); - - function getRandomDelay(min, max) { - return Math.floor(Math.random() * (max - min + 1) + min); - } + const fs = require('fs'); function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } + function getRandomDelay(min, max) { + return Math.floor(Math.random() * (max - min + 1) + min); + } + async function typeWithRandomDelay(page, selector, text) { for (const char of text) { await page.type(selector, char, { delay: getRandomDelay(50, 150) }); } } - async function waitForSelectorWithTimeout(page, selector, timeout) { - try { - await page.waitForSelector(selector, { visible: true, timeout }); - return true; - } catch (error) { - return false; - } - } - (async () => { const browser = await puppeteer.launch({ headless: true, @@ -189,43 +181,50 @@ jobs: await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'); try { - console.log("➡️ Opening Auth URL..."); - await page.goto(process.env.AUTH_URL, { waitUntil: 'networkidle2', timeout: 60000 }); - await delay(3000); - - const cookieAccept = await page.$('#onetrust-accept-btn-handler'); - if (cookieAccept) { - console.log("🍪 Accepting cookies..."); - await cookieAccept.click(); + console.log("➡️ Opening Spotify Auth URL..."); + await page.goto(process.env.AUTH_URL, { waitUntil: 'domcontentloaded', timeout: 60000 }); + await delay(5000); + + const cookieBtn = await page.$('#onetrust-accept-btn-handler'); + if (cookieBtn) { + console.log("🍪 Clicking cookie accept..."); + await cookieBtn.click(); await delay(2000); } - console.log("⌨️ Typing login credentials..."); - await page.waitForSelector('#login-username', { timeout: 15000 }); + console.log("⌨️ Typing credentials..."); + await page.waitForSelector('#login-username', { visible: true, timeout: 20000 }); await typeWithRandomDelay(page, '#login-username', process.env.SPOTIFY_USERNAME); - await delay(getRandomDelay(500, 1000)); - await page.waitForSelector('#login-password', { timeout: 15000 }); + + const passwordExists = await page.waitForSelector('#login-password', { visible: true, timeout: 20000 }).catch(() => null); + if (!passwordExists) throw new Error('❌ Password input field not found.'); + await typeWithRandomDelay(page, '#login-password', process.env.SPOTIFY_PASSWORD); - await delay(getRandomDelay(1000, 2000)); + await delay(1500); + await page.click('#login-button'); - await delay(3000); + await delay(5000); - const authAccept = await waitForSelectorWithTimeout(page, '#auth-accept', 20000); + const authAccept = await page.$('#auth-accept'); if (authAccept) { - console.log("🔐 Accepting Spotify auth permissions..."); - await page.click('#auth-accept'); - await page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 30000 }); + console.log("✅ Accepting Spotify authorization..."); + await authAccept.click(); + await page.waitForNavigation({ waitUntil: 'networkidle0', timeout: 30000 }); } - const pageContent = await page.content(); - if (pageContent.includes('Authorization successful') || page.url().includes('callback?code=')) { - console.log('✅ Spotify authorization successful'); + const currentURL = page.url(); + const content = await page.content(); + + if (content.includes('Authorization successful') || currentURL.includes('callback?code=')) { + console.log('🎉 Spotify authorization successful'); } else { - throw new Error('❌ Spotify authorization failed.'); + throw new Error('❌ Spotify login or authorization failed.'); } - } catch (err) { - console.error("💥 Error during Spotify login:", err); + } catch (error) { + console.error('💥 Error during Spotify login:', error.message); + await page.screenshot({ path: 'spotify_error.png', fullPage: true }); + fs.writeFileSync('error_debug.html', await page.content()); process.exit(1); } finally { await browser.close(); From 539e91cba1866125b2cf91ca8e2ff477816db193 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Mon, 23 Jun 2025 23:29:12 +0530 Subject: [PATCH 06/48] Improve Puppeteer flow-3 --- .github/workflows/tf-apply.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index 105c1ea..2edca07 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -147,7 +147,7 @@ jobs: - name: Install Puppeteer run: npm install puppeteer - - name: Perform Spotify login and authorization + - name: Perform Spotify login and authorization env: SPOTIFY_USERNAME: ${{ secrets.SPOTIFY_USERNAME }} SPOTIFY_PASSWORD: ${{ secrets.SPOTIFY_PASSWORD }} From 1abb21d5d1576ce1cc621616bd71081d2eeff286 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Mon, 23 Jun 2025 23:39:43 +0530 Subject: [PATCH 07/48] Improve Puppeteer flow-4 --- .github/workflows/tf-apply.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index 2edca07..a84f215 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -236,3 +236,13 @@ jobs: run: | terraform init terraform apply -var="SPOTIFY_API_KEY=${{ env.SPOTIFY_API_KEY }}" -auto-approve + + - name: Upload debug files (on failure) + if: failure() + uses: actions/upload-artifact@v3 + with: + name: spotify-debug + path: | + spotify_error.png + error_debug.html + From 98f08ab18c0cbd6a41dae08d08af515b506f04bb Mon Sep 17 00:00:00 2001 From: Gaurav Date: Mon, 23 Jun 2025 23:41:29 +0530 Subject: [PATCH 08/48] Improve Puppeteer flow-4.1 --- .github/workflows/tf-apply.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index a84f215..bf68670 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -239,7 +239,7 @@ jobs: - name: Upload debug files (on failure) if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: spotify-debug path: | From d0ff7ef35c536f3dcb2420f1c4fa4a7eb4f7cd51 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Mon, 23 Jun 2025 23:56:04 +0530 Subject: [PATCH 09/48] new workflow logic --- .github/workflows/tf-apply.yaml | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index bf68670..7622aaa 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -105,6 +105,7 @@ jobs: spotify-auth-and-apply: runs-on: ubuntu-latest name: Apply + steps: - name: Checkout repository uses: actions/checkout@v4 @@ -125,7 +126,6 @@ jobs: run: | spotify_auth_proxy > proxy_output.log 2>&1 & PROXY_PID=$! - echo "PROXY_PID=$PROXY_PID" >> $GITHUB_ENV echo "Waiting for Auth URL and API Key..." for i in {1..30}; do if grep -q "Auth URL:" proxy_output.log && grep -q "APIKey:" proxy_output.log; then break; fi @@ -192,18 +192,31 @@ jobs: await delay(2000); } - console.log("⌨️ Typing credentials..."); + console.log("⌨️ Typing email..."); await page.waitForSelector('#login-username', { visible: true, timeout: 20000 }); await typeWithRandomDelay(page, '#login-username', process.env.SPOTIFY_USERNAME); + await delay(1000); + + console.log("🔁 Clicking Continue..."); + await page.click('button[data-testid="login-button"]'); + await delay(4000); - const passwordExists = await page.waitForSelector('#login-password', { visible: true, timeout: 20000 }).catch(() => null); - if (!passwordExists) throw new Error('❌ Password input field not found.'); + console.log("🔓 Clicking 'Log in with a password'..."); + const passwordFallback = await page.$x("//span[contains(text(), 'Log in with a password')]"); + if (passwordFallback.length > 0) { + await passwordFallback[0].click(); + await delay(3000); + } else { + throw new Error("❌ 'Log in with a password' option not found"); + } + console.log("🔐 Typing password..."); + await page.waitForSelector('#login-password', { visible: true, timeout: 20000 }); await typeWithRandomDelay(page, '#login-password', process.env.SPOTIFY_PASSWORD); await delay(1500); await page.click('#login-button'); - await delay(5000); + await delay(6000); const authAccept = await page.$('#auth-accept'); if (authAccept) { @@ -245,4 +258,3 @@ jobs: path: | spotify_error.png error_debug.html - From ca786b755937650155f16e386ee61953966ecb83 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Tue, 24 Jun 2025 00:03:14 +0530 Subject: [PATCH 10/48] new workflow logic-1 --- .github/workflows/tf-apply.yaml | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index 7622aaa..8d03fef 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -199,22 +199,31 @@ jobs: console.log("🔁 Clicking Continue..."); await page.click('button[data-testid="login-button"]'); - await delay(4000); + await delay(5000); console.log("🔓 Clicking 'Log in with a password'..."); - const passwordFallback = await page.$x("//span[contains(text(), 'Log in with a password')]"); - if (passwordFallback.length > 0) { - await passwordFallback[0].click(); - await delay(3000); - } else { - throw new Error("❌ 'Log in with a password' option not found"); + const loginWithPasswordClicked = await page.evaluate(() => { + const allSpans = Array.from(document.querySelectorAll('span')); + const passwordSpan = allSpans.find(span => span.innerText?.toLowerCase().includes('log in with a password')); + if (passwordSpan) { + passwordSpan.click(); + return true; + } + return false; + }); + + if (!loginWithPasswordClicked) { + throw new Error("❌ 'Log in with a password' link not found"); } + await delay(4000); + console.log("🔐 Typing password..."); await page.waitForSelector('#login-password', { visible: true, timeout: 20000 }); await typeWithRandomDelay(page, '#login-password', process.env.SPOTIFY_PASSWORD); await delay(1500); + console.log("🚀 Submitting login..."); await page.click('#login-button'); await delay(6000); @@ -245,6 +254,7 @@ jobs: })(); EOF + - name: Run Terraform Apply run: | terraform init From de53bad931a3883ba9da1f9f1d1bb2743fdaddc5 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Tue, 24 Jun 2025 00:20:19 +0530 Subject: [PATCH 11/48] new workflow logic-2 --- .github/workflows/tf-apply.yaml | 177 +++++++++++++++++++++----------- 1 file changed, 116 insertions(+), 61 deletions(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index 8d03fef..f41d442 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -93,7 +93,6 @@ # run: | # terraform init # terraform apply -var="SPOTIFY_API_KEY=${{ env.SPOTIFY_API_KEY }}" -auto-approve - name: Update playlist with Spotify Auth on: @@ -105,6 +104,7 @@ jobs: spotify-auth-and-apply: runs-on: ubuntu-latest name: Apply + timeout-minutes: 10 steps: - name: Checkout repository @@ -144,8 +144,10 @@ jobs: with: node-version: '18' - - name: Install Puppeteer - run: npm install puppeteer + - name: Install Puppeteer and dependencies + run: | + npm install puppeteer + npm install fs-extra - name: Perform Spotify login and authorization env: @@ -154,107 +156,160 @@ jobs: run: | node - <<'EOF' const puppeteer = require('puppeteer'); - const fs = require('fs'); + const fs = require('fs-extra'); - function delay(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); + // Utility functions + const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); + const randomDelay = (min, max) => Math.floor(Math.random() * (max - min + 1) + min); + + async function typeWithHumanDelay(page, selector, text) { + for (const char of text) { + await page.type(selector, char, { delay: randomDelay(30, 120) }); + if (Math.random() > 0.7) await delay(randomDelay(50, 200)); + } } - function getRandomDelay(min, max) { - return Math.floor(Math.random() * (max - min + 1) + min); + async function clickByText(page, text, elementType = 'button') { + const elements = await page.$$(elementType); + for (const element of elements) { + const elementText = await page.evaluate(el => el.textContent?.trim(), element); + if (elementText?.toLowerCase().includes(text.toLowerCase())) { + await element.click(); + return true; + } + } + return false; } - async function typeWithRandomDelay(page, selector, text) { - for (const char of text) { - await page.type(selector, char, { delay: getRandomDelay(50, 150) }); + async function safeClick(page, selector, timeout = 10000) { + await page.waitForSelector(selector, { visible: true, timeout }); + await delay(500); + const element = await page.$(selector); + if (element) { + await element.click(); + return true; } + return false; } (async () => { const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'], - defaultViewport: { width: 1366, height: 768 } + defaultViewport: { width: 1280, height: 800 } }); const page = await browser.newPage(); await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'); try { - console.log("➡️ Opening Spotify Auth URL..."); - await page.goto(process.env.AUTH_URL, { waitUntil: 'domcontentloaded', timeout: 60000 }); - await delay(5000); - - const cookieBtn = await page.$('#onetrust-accept-btn-handler'); - if (cookieBtn) { - console.log("🍪 Clicking cookie accept..."); - await cookieBtn.click(); - await delay(2000); + // Step 1: Open Auth URL + console.log("🌐 Navigating to Spotify Auth URL..."); + await page.goto(process.env.AUTH_URL, { waitUntil: 'networkidle2', timeout: 60000 }); + await delay(3000); + + // Handle cookies if present + try { + const cookieAccepted = await clickByText(page, 'accept', 'button') || + await safeClick(page, '#onetrust-accept-btn-handler'); + if (cookieAccepted) { + console.log("🍪 Accepted cookies"); + await delay(2000); + } + } catch (e) { + console.log("ℹ️ No cookie banner found"); } - console.log("⌨️ Typing email..."); - await page.waitForSelector('#login-username', { visible: true, timeout: 20000 }); - await typeWithRandomDelay(page, '#login-username', process.env.SPOTIFY_USERNAME); + // Step 2: Enter email and continue + console.log("📧 Entering email..."); + await page.waitForSelector('input[type="email"], #login-username', { visible: true, timeout: 15000 }); + await typeWithHumanDelay(page, 'input[type="email"], #login-username', process.env.SPOTIFY_USERNAME); await delay(1000); - console.log("🔁 Clicking Continue..."); - await page.click('button[data-testid="login-button"]'); - await delay(5000); + console.log("➡️ Clicking Continue..."); + const continueClicked = await clickByText(page, 'continue') || + await safeClick(page, 'button[data-testid="login-button"]'); + if (!continueClicked) throw new Error("Continue button not found"); + await delay(4000); - console.log("🔓 Clicking 'Log in with a password'..."); - const loginWithPasswordClicked = await page.evaluate(() => { - const allSpans = Array.from(document.querySelectorAll('span')); - const passwordSpan = allSpans.find(span => span.innerText?.toLowerCase().includes('log in with a password')); - if (passwordSpan) { - passwordSpan.click(); - return true; + // Step 3: Handle OTP/password choice + console.log("🔍 Checking for OTP/password choice..."); + try { + // Check if we're on OTP screen by looking for "6-digit code" text + const otpText = await page.evaluate(() => { + return Array.from(document.querySelectorAll('*')) + .find(el => el.textContent?.includes('6-digit code')); + }); + + if (otpText) { + console.log("🔢 Found OTP screen, switching to password login..."); + const passwordOptionClicked = await clickByText(page, 'log in with a password') || + await safeClick(page, 'button[data-encore-id="buttonTertiary"]'); + if (!passwordOptionClicked) { + throw new Error("Password login option not found on OTP screen"); + } + await delay(3000); } - return false; - }); - - if (!loginWithPasswordClicked) { - throw new Error("❌ 'Log in with a password' link not found"); + } catch (e) { + console.log("ℹ️ OTP screen not detected, proceeding with password entry"); } - await delay(4000); - - console.log("🔐 Typing password..."); - await page.waitForSelector('#login-password', { visible: true, timeout: 20000 }); - await typeWithRandomDelay(page, '#login-password', process.env.SPOTIFY_PASSWORD); + // Step 4: Enter password + console.log("🔑 Entering password..."); + await page.waitForSelector('input[type="password"], #login-password', { visible: true, timeout: 10000 }); + await typeWithHumanDelay(page, 'input[type="password"], #login-password', process.env.SPOTIFY_PASSWORD); await delay(1500); console.log("🚀 Submitting login..."); - await page.click('#login-button'); - await delay(6000); - - const authAccept = await page.$('#auth-accept'); - if (authAccept) { - console.log("✅ Accepting Spotify authorization..."); - await authAccept.click(); - await page.waitForNavigation({ waitUntil: 'networkidle0', timeout: 30000 }); + const loginClicked = await clickByText(page, 'log in') || + await safeClick(page, '#login-button'); + if (!loginClicked) throw new Error("Login button not found"); + await delay(5000); + + // Handle potential "Stay logged in" prompt + try { + const stayLoggedIn = await clickByText(page, 'stay logged in') || + await safeClick(page, '[data-testid="auth-accept"]'); + if (stayLoggedIn) { + console.log("🔘 Clicked 'Stay Logged In'"); + await delay(2000); + } + } catch (e) { + console.log("ℹ️ No 'Stay Logged In' prompt found"); } - const currentURL = page.url(); - const content = await page.content(); + // Step 5: Handle authorization + console.log("🔄 Checking for authorization screen..."); + try { + const authAccepted = await clickByText(page, 'agree') || + await safeClick(page, '#auth-accept'); + if (authAccepted) { + console.log("✅ Clicked 'Agree' on authorization screen"); + await page.waitForNavigation({ waitUntil: 'networkidle0', timeout: 15000 }); + } + } catch (e) { + console.log("ℹ️ No authorization screen detected"); + } - if (content.includes('Authorization successful') || currentURL.includes('callback?code=')) { - console.log('🎉 Spotify authorization successful'); + // Verification + const currentURL = page.url(); + if (currentURL.includes('callback?code=')) { + console.log('🎉 Spotify authorization successful!'); } else { - throw new Error('❌ Spotify login or authorization failed.'); + throw new Error(`Authorization failed. Current URL: ${currentURL}`); } } catch (error) { console.error('💥 Error during Spotify login:', error.message); await page.screenshot({ path: 'spotify_error.png', fullPage: true }); - fs.writeFileSync('error_debug.html', await page.content()); - process.exit(1); + await fs.writeFile('error_debug.html', await page.content()); + throw error; } finally { await browser.close(); } })(); EOF - - name: Run Terraform Apply run: | terraform init @@ -267,4 +322,4 @@ jobs: name: spotify-debug path: | spotify_error.png - error_debug.html + error_debug.html \ No newline at end of file From 3a9bbe6adce290eba8f434a315cf5e6bf8b0890d Mon Sep 17 00:00:00 2001 From: Gaurav Date: Tue, 24 Jun 2025 00:25:31 +0530 Subject: [PATCH 12/48] new workflow logic-3 --- .github/workflows/tf-apply.yaml | 62 ++++++++++++++++----------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index f41d442..8e5fe7b 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -158,10 +158,9 @@ jobs: const puppeteer = require('puppeteer'); const fs = require('fs-extra'); - // Utility functions const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); const randomDelay = (min, max) => Math.floor(Math.random() * (max - min + 1) + min); - + async function typeWithHumanDelay(page, selector, text) { for (const char of text) { await page.type(selector, char, { delay: randomDelay(30, 120) }); @@ -203,14 +202,13 @@ jobs: await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'); try { - // Step 1: Open Auth URL console.log("🌐 Navigating to Spotify Auth URL..."); await page.goto(process.env.AUTH_URL, { waitUntil: 'networkidle2', timeout: 60000 }); await delay(3000); - // Handle cookies if present + // Accept cookies try { - const cookieAccepted = await clickByText(page, 'accept', 'button') || + const cookieAccepted = await clickByText(page, 'accept') || await safeClick(page, '#onetrust-accept-btn-handler'); if (cookieAccepted) { console.log("🍪 Accepted cookies"); @@ -220,53 +218,53 @@ jobs: console.log("ℹ️ No cookie banner found"); } - // Step 2: Enter email and continue + // Email field console.log("📧 Entering email..."); await page.waitForSelector('input[type="email"], #login-username', { visible: true, timeout: 15000 }); await typeWithHumanDelay(page, 'input[type="email"], #login-username', process.env.SPOTIFY_USERNAME); await delay(1000); + // Click Continue console.log("➡️ Clicking Continue..."); const continueClicked = await clickByText(page, 'continue') || await safeClick(page, 'button[data-testid="login-button"]'); if (!continueClicked) throw new Error("Continue button not found"); await delay(4000); - // Step 3: Handle OTP/password choice + // OTP Screen Check console.log("🔍 Checking for OTP/password choice..."); - try { - // Check if we're on OTP screen by looking for "6-digit code" text - const otpText = await page.evaluate(() => { - return Array.from(document.querySelectorAll('*')) - .find(el => el.textContent?.includes('6-digit code')); - }); - - if (otpText) { - console.log("🔢 Found OTP screen, switching to password login..."); - const passwordOptionClicked = await clickByText(page, 'log in with a password') || - await safeClick(page, 'button[data-encore-id="buttonTertiary"]'); - if (!passwordOptionClicked) { - throw new Error("Password login option not found on OTP screen"); - } - await delay(3000); + const otpScreenExists = await page.evaluate(() => { + return [...document.querySelectorAll('*')].some(el => + el.textContent?.toLowerCase().includes('6-digit code') + ); + }); + + if (otpScreenExists) { + console.log("🔢 Found OTP screen, switching to password login..."); + const passwordOptionClicked = await clickByText(page, 'log in with a password') || + await safeClick(page, 'button[data-encore-id="buttonTertiary"]'); + if (!passwordOptionClicked) { + throw new Error("Password login option not found on OTP screen"); } - } catch (e) { - console.log("ℹ️ OTP screen not detected, proceeding with password entry"); + + console.log("⏳ Waiting for password field to load..."); + await page.waitForSelector('input[type="password"], #login-password', { visible: true, timeout: 15000 }); + await delay(3000); } - // Step 4: Enter password + // Enter password console.log("🔑 Entering password..."); - await page.waitForSelector('input[type="password"], #login-password', { visible: true, timeout: 10000 }); await typeWithHumanDelay(page, 'input[type="password"], #login-password', process.env.SPOTIFY_PASSWORD); - await delay(1500); + await delay(22000); + // Submit console.log("🚀 Submitting login..."); const loginClicked = await clickByText(page, 'log in') || await safeClick(page, '#login-button'); if (!loginClicked) throw new Error("Login button not found"); await delay(5000); - // Handle potential "Stay logged in" prompt + // Optional Stay Logged In try { const stayLoggedIn = await clickByText(page, 'stay logged in') || await safeClick(page, '[data-testid="auth-accept"]'); @@ -278,10 +276,10 @@ jobs: console.log("ℹ️ No 'Stay Logged In' prompt found"); } - // Step 5: Handle authorization + // Handle Authorization console.log("🔄 Checking for authorization screen..."); try { - const authAccepted = await clickByText(page, 'agree') || + const authAccepted = await clickByText(page, 'agree') || await safeClick(page, '#auth-accept'); if (authAccepted) { console.log("✅ Clicked 'Agree' on authorization screen"); @@ -291,7 +289,7 @@ jobs: console.log("ℹ️ No authorization screen detected"); } - // Verification + // Final verification const currentURL = page.url(); if (currentURL.includes('callback?code=')) { console.log('🎉 Spotify authorization successful!'); @@ -322,4 +320,4 @@ jobs: name: spotify-debug path: | spotify_error.png - error_debug.html \ No newline at end of file + error_debug.html From cee9d859ce5b3115c083d397a146526aceaa51b5 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Tue, 24 Jun 2025 00:32:19 +0530 Subject: [PATCH 13/48] new workflow logic-4 --- .github/workflows/tf-apply.yaml | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index 8e5fe7b..6c2038d 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -93,6 +93,9 @@ # run: | # terraform init # terraform apply -var="SPOTIFY_API_KEY=${{ env.SPOTIFY_API_KEY }}" -auto-approve + + + name: Update playlist with Spotify Auth on: @@ -258,11 +261,20 @@ jobs: await delay(22000); // Submit - console.log("🚀 Submitting login..."); - const loginClicked = await clickByText(page, 'log in') || - await safeClick(page, '#login-button'); + console.log("🚀 Submitting login..."); + let loginClicked = false; + try { + loginClicked = await clickByText(page, 'log in') || + await safeClick(page, 'button[type="submit"]') || + await safeClick(page, '[data-testid="login-button"]') || + await safeClick(page, '#login-button'); + } catch (e) { + console.log("⚠️ Fallback: Login button not clicked using known selectors"); + } + if (!loginClicked) throw new Error("Login button not found"); - await delay(5000); + await delay(5000); + // Optional Stay Logged In try { From 93dc984b74d65f61a62fcf850cc1f89f514ccb87 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Tue, 24 Jun 2025 00:39:30 +0530 Subject: [PATCH 14/48] new workflow logic-5 --- .github/workflows/tf-apply.yaml | 110 +++++++++++++------------------- 1 file changed, 44 insertions(+), 66 deletions(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index 6c2038d..d6f68af 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -149,8 +149,7 @@ jobs: - name: Install Puppeteer and dependencies run: | - npm install puppeteer - npm install fs-extra + npm install puppeteer fs-extra - name: Perform Spotify login and authorization env: @@ -161,7 +160,7 @@ jobs: const puppeteer = require('puppeteer'); const fs = require('fs-extra'); - const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); + const delay = ms => new Promise(res => setTimeout(res, ms)); const randomDelay = (min, max) => Math.floor(Math.random() * (max - min + 1) + min); async function typeWithHumanDelay(page, selector, text) { @@ -174,8 +173,8 @@ jobs: async function clickByText(page, text, elementType = 'button') { const elements = await page.$$(elementType); for (const element of elements) { - const elementText = await page.evaluate(el => el.textContent?.trim(), element); - if (elementText?.toLowerCase().includes(text.toLowerCase())) { + const content = await page.evaluate(el => el.textContent?.trim(), element); + if (content?.toLowerCase().includes(text.toLowerCase())) { await element.click(); return true; } @@ -184,13 +183,15 @@ jobs: } async function safeClick(page, selector, timeout = 10000) { - await page.waitForSelector(selector, { visible: true, timeout }); - await delay(500); - const element = await page.$(selector); - if (element) { - await element.click(); - return true; - } + try { + await page.waitForSelector(selector, { visible: true, timeout }); + await delay(500); + const element = await page.$(selector); + if (element) { + await element.click(); + return true; + } + } catch (e) {} return false; } @@ -202,106 +203,83 @@ jobs: }); const page = await browser.newPage(); - await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'); + await page.setUserAgent('Mozilla/5.0'); try { console.log("🌐 Navigating to Spotify Auth URL..."); await page.goto(process.env.AUTH_URL, { waitUntil: 'networkidle2', timeout: 60000 }); await delay(3000); - // Accept cookies try { - const cookieAccepted = await clickByText(page, 'accept') || - await safeClick(page, '#onetrust-accept-btn-handler'); - if (cookieAccepted) { - console.log("🍪 Accepted cookies"); - await delay(2000); - } - } catch (e) { - console.log("ℹ️ No cookie banner found"); - } + const cookieAccepted = await clickByText(page, 'accept') || + await safeClick(page, '#onetrust-accept-btn-handler'); + if (cookieAccepted) console.log("🍪 Accepted cookies"); + } catch { console.log("ℹ️ No cookie banner found"); } - // Email field console.log("📧 Entering email..."); await page.waitForSelector('input[type="email"], #login-username', { visible: true, timeout: 15000 }); await typeWithHumanDelay(page, 'input[type="email"], #login-username', process.env.SPOTIFY_USERNAME); await delay(1000); - // Click Continue console.log("➡️ Clicking Continue..."); - const continueClicked = await clickByText(page, 'continue') || + const continueClicked = await clickByText(page, 'continue') || await safeClick(page, 'button[data-testid="login-button"]'); if (!continueClicked) throw new Error("Continue button not found"); await delay(4000); - // OTP Screen Check console.log("🔍 Checking for OTP/password choice..."); const otpScreenExists = await page.evaluate(() => { - return [...document.querySelectorAll('*')].some(el => - el.textContent?.toLowerCase().includes('6-digit code') - ); + return Array.from(document.querySelectorAll('*')).some(el => + el.textContent?.toLowerCase().includes('6-digit code')); }); if (otpScreenExists) { console.log("🔢 Found OTP screen, switching to password login..."); const passwordOptionClicked = await clickByText(page, 'log in with a password') || await safeClick(page, 'button[data-encore-id="buttonTertiary"]'); - if (!passwordOptionClicked) { - throw new Error("Password login option not found on OTP screen"); - } - - console.log("⏳ Waiting for password field to load..."); + if (!passwordOptionClicked) throw new Error("Password login option not found"); await page.waitForSelector('input[type="password"], #login-password', { visible: true, timeout: 15000 }); await delay(3000); } - // Enter password console.log("🔑 Entering password..."); await typeWithHumanDelay(page, 'input[type="password"], #login-password', process.env.SPOTIFY_PASSWORD); - await delay(22000); - - // Submit - console.log("🚀 Submitting login..."); - let loginClicked = false; - try { - loginClicked = await clickByText(page, 'log in') || - await safeClick(page, 'button[type="submit"]') || - await safeClick(page, '[data-testid="login-button"]') || - await safeClick(page, '#login-button'); - } catch (e) { - console.log("⚠️ Fallback: Login button not clicked using known selectors"); - } + await delay(2000); - if (!loginClicked) throw new Error("Login button not found"); - await delay(5000); + console.log("🚀 Submitting login..."); + let loginClicked = false; + try { + loginClicked = await clickByText(page, 'log in') || + await clickByText(page, 'Log in') || + await safeClick(page, 'button[type="submit"]') || + await safeClick(page, '[data-testid="login-button"]') || + await safeClick(page, '#login-button') || + await safeClick(page, 'button[data-encore-id="buttonPrimary"]'); + if (!loginClicked) { + const allBtns = await page.$$eval('button', btns => btns.map(b => b.textContent?.trim())); + console.log("🔍 Available buttons:", allBtns); + } + } catch { console.log("⚠️ Fallback: Login button not found in known selectors"); } + if (!loginClicked) throw new Error("Login button not found"); + await delay(5000); - // Optional Stay Logged In try { - const stayLoggedIn = await clickByText(page, 'stay logged in') || + const stayLoggedIn = await clickByText(page, 'stay logged in') || await safeClick(page, '[data-testid="auth-accept"]'); - if (stayLoggedIn) { - console.log("🔘 Clicked 'Stay Logged In'"); - await delay(2000); - } - } catch (e) { - console.log("ℹ️ No 'Stay Logged In' prompt found"); - } + if (stayLoggedIn) console.log("🔘 Clicked 'Stay Logged In'"); + } catch {} - // Handle Authorization console.log("🔄 Checking for authorization screen..."); try { const authAccepted = await clickByText(page, 'agree') || await safeClick(page, '#auth-accept'); if (authAccepted) { - console.log("✅ Clicked 'Agree' on authorization screen"); + console.log("✅ Clicked 'Agree'"); await page.waitForNavigation({ waitUntil: 'networkidle0', timeout: 15000 }); } - } catch (e) { - console.log("ℹ️ No authorization screen detected"); - } + } catch {} - // Final verification const currentURL = page.url(); if (currentURL.includes('callback?code=')) { console.log('🎉 Spotify authorization successful!'); From 8e0aa8a670452338c0ce96a47a6d7b0501b2f770 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Tue, 24 Jun 2025 09:03:45 +0530 Subject: [PATCH 15/48] change workflow --- .github/workflows/tf-apply.yaml | 117 ++++---------------------------- 1 file changed, 12 insertions(+), 105 deletions(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index d6f68af..c2cceee 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -1,101 +1,3 @@ -# name: Update playlist with Spotify Auth -# on: -# push: -# branches: -# - main -# jobs: -# spotify-auth-and-apply: -# runs-on: ubuntu-latest -# name: Apply -# steps: -# - name: Checkout repository -# uses: actions/checkout@v4 - -# - name: Setup Go -# uses: actions/setup-go@v5 -# with: -# go-version: '1.18.0' - -# - name: Install spotify_auth_proxy -# run: go install github.com/conradludgate/terraform-provider-spotify/spotify_auth_proxy@latest - -# - name: Run spotify_auth_proxy and capture Auth URL -# env: -# SPOTIFY_CLIENT_ID: ${{ secrets.SPOTIFY_CLIENT_ID }} -# SPOTIFY_CLIENT_SECRET: ${{ secrets.SPOTIFY_CLIENT_SECRET }} -# SPOTIFY_CLIENT_REDIRECT_URI: ${{ secrets.SPOTIFY_CLIENT_REDIRECT_URI }} -# run: | -# spotify_auth_proxy > proxy_output.log 2>&1 & -# PROXY_PID=$! -# echo "PROXY_PID=$PROXY_PID" >> $GITHUB_ENV - -# # Wait for the Auth URL and API Key to appear in the log -# while ! (grep -q "Auth URL:" proxy_output.log && grep -q "APIKey:" proxy_output.log); do -# sleep 1 -# done - -# auth_url=$(grep "Auth URL:" proxy_output.log | awk '{print $3}') -# api_key=$(grep "APIKey:" proxy_output.log | awk '{print $2}') -# echo "AUTH_URL=$auth_url" >> $GITHUB_ENV -# echo "SPOTIFY_API_KEY=$api_key" >> $GITHUB_ENV - - -# - name: Setup Node.js -# uses: actions/setup-node@v3 -# with: -# node-version: '18' - -# - name: Install Puppeteer -# run: npm install puppeteer - -# - name: Perform Spotify login -# env: -# SPOTIFY_USERNAME: ${{ secrets.SPOTIFY_USERNAME }} -# SPOTIFY_PASSWORD: ${{ secrets.SPOTIFY_PASSWORD }} -# run: | -# node - < { -# const browser = await puppeteer.launch({ headless: true }); -# const page = await browser.newPage(); - -# try { -# await page.goto('${{ env.AUTH_URL }}'); - -# // Wait for login form and fill in credentials -# await page.waitForSelector('#login-username'); -# await page.type('#login-username', process.env.SPOTIFY_USERNAME); -# await page.type('#login-password', process.env.SPOTIFY_PASSWORD); - -# // Submit the form -# await page.click('#login-button'); - -# // Wait for redirect and authorization -# await page.waitForNavigation({ waitUntil: 'networkidle0' }); - -# // Check for successful authorization -# const content = await page.content(); -# if (content.includes('Authorization successful')) { -# console.log('Spotify authorization successful'); -# } else { -# throw new Error('Authorization not successful'); -# } -# } catch (error) { -# console.error('Error during Spotify login:', error); -# process.exit(1); -# } finally { -# await browser.close(); -# } -# })(); -# EOF -# - name: Run Terraform Apply -# run: | -# terraform init -# terraform apply -var="SPOTIFY_API_KEY=${{ env.SPOTIFY_API_KEY }}" -auto-approve - - - name: Update playlist with Spotify Auth on: @@ -235,7 +137,7 @@ jobs: if (otpScreenExists) { console.log("🔢 Found OTP screen, switching to password login..."); - const passwordOptionClicked = await clickByText(page, 'log in with a password') || + const passwordOptionClicked = await clickByText(page, 'Log in with a password') || await safeClick(page, 'button[data-encore-id="buttonTertiary"]'); if (!passwordOptionClicked) throw new Error("Password login option not found"); await page.waitForSelector('input[type="password"], #login-password', { visible: true, timeout: 15000 }); @@ -249,12 +151,17 @@ jobs: console.log("🚀 Submitting login..."); let loginClicked = false; try { - loginClicked = await clickByText(page, 'log in') || - await clickByText(page, 'Log in') || - await safeClick(page, 'button[type="submit"]') || - await safeClick(page, '[data-testid="login-button"]') || - await safeClick(page, '#login-button') || - await safeClick(page, 'button[data-encore-id="buttonPrimary"]'); + loginClicked = await safeClick(page, '#login-button') || + await safeClick(page, 'button[data-testid="login-button"]') || + await safeClick(page, 'button[data-encore-id="buttonPrimary"]') || + await safeClick(page, 'button[type="submit"]') || + await clickByText(page, 'Log In') || + await clickByText(page, 'log in'); + + console.log("🌐 Current page URL:", page.url()); + const buttons = await page.$$eval('button', btns => btns.map(b => b.textContent?.trim())); + console.log("🔍 Buttons detected:", buttons); + if (!loginClicked) { const allBtns = await page.$$eval('button', btns => btns.map(b => b.textContent?.trim())); console.log("🔍 Available buttons:", allBtns); From 00fcb7ea8ffa56db4162de88cce3314d2121ee10 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Tue, 24 Jun 2025 09:19:20 +0530 Subject: [PATCH 16/48] change workflow-1 --- .github/workflows/tf-apply.yaml | 117 +++++++++++++------------------- 1 file changed, 47 insertions(+), 70 deletions(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index c2cceee..0fab96d 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -49,9 +49,8 @@ jobs: with: node-version: '18' - - name: Install Puppeteer and dependencies - run: | - npm install puppeteer fs-extra + - name: Install Puppeteer and fs-extra + run: npm install puppeteer fs-extra - name: Perform Spotify login and authorization env: @@ -67,16 +66,16 @@ jobs: async function typeWithHumanDelay(page, selector, text) { for (const char of text) { - await page.type(selector, char, { delay: randomDelay(30, 120) }); - if (Math.random() > 0.7) await delay(randomDelay(50, 200)); + await page.type(selector, char, { delay: randomDelay(30, 100) }); + if (Math.random() > 0.8) await delay(randomDelay(50, 150)); } } async function clickByText(page, text, elementType = 'button') { const elements = await page.$$(elementType); for (const element of elements) { - const content = await page.evaluate(el => el.textContent?.trim(), element); - if (content?.toLowerCase().includes(text.toLowerCase())) { + const elementText = await page.evaluate(el => el.textContent?.trim(), element); + if (elementText?.toLowerCase() === text.toLowerCase()) { await element.click(); return true; } @@ -93,7 +92,7 @@ jobs: await element.click(); return true; } - } catch (e) {} + } catch {} return false; } @@ -112,11 +111,11 @@ jobs: await page.goto(process.env.AUTH_URL, { waitUntil: 'networkidle2', timeout: 60000 }); await delay(3000); - try { - const cookieAccepted = await clickByText(page, 'accept') || - await safeClick(page, '#onetrust-accept-btn-handler'); - if (cookieAccepted) console.log("🍪 Accepted cookies"); - } catch { console.log("ℹ️ No cookie banner found"); } + // Check if redirected to Google login by mistake + const url = page.url(); + if (url.includes('accounts.google.com')) { + throw new Error("❌ Redirected to Google login instead of Spotify login page."); + } console.log("📧 Entering email..."); await page.waitForSelector('input[type="email"], #login-username', { visible: true, timeout: 15000 }); @@ -124,76 +123,54 @@ jobs: await delay(1000); console.log("➡️ Clicking Continue..."); - const continueClicked = await clickByText(page, 'continue') || - await safeClick(page, 'button[data-testid="login-button"]'); - if (!continueClicked) throw new Error("Continue button not found"); + const continueClicked = await clickByText(page, 'Continue') || + await safeClick(page, 'button[data-testid="login-button"]') || + await safeClick(page, 'button[type="submit"]'); + if (!continueClicked) throw new Error("❌ Continue button not found."); await delay(4000); - console.log("🔍 Checking for OTP/password choice..."); - const otpScreenExists = await page.evaluate(() => { - return Array.from(document.querySelectorAll('*')).some(el => - el.textContent?.toLowerCase().includes('6-digit code')); - }); - - if (otpScreenExists) { - console.log("🔢 Found OTP screen, switching to password login..."); - const passwordOptionClicked = await clickByText(page, 'Log in with a password') || - await safeClick(page, 'button[data-encore-id="buttonTertiary"]'); - if (!passwordOptionClicked) throw new Error("Password login option not found"); - await page.waitForSelector('input[type="password"], #login-password', { visible: true, timeout: 15000 }); + console.log("🔍 Checking for OTP screen..."); + const otpDetected = await page.evaluate(() => + Array.from(document.querySelectorAll('*')).some(el => + el.textContent?.toLowerCase().includes('6-digit code') + ) + ); + + if (otpDetected) { + console.log("🔁 Clicking 'Log in with a password'..."); + const clicked = await clickByText(page, 'Log in with a password') || + await safeClick(page, 'button[data-encore-id="buttonTertiary"]'); + if (!clicked) throw new Error("❌ Could not find 'Log in with a password' option."); await delay(3000); } console.log("🔑 Entering password..."); - await typeWithHumanDelay(page, 'input[type="password"], #login-password', process.env.SPOTIFY_PASSWORD); - await delay(2000); + await page.waitForSelector('input[type="password"]', { visible: true, timeout: 10000 }); + await typeWithHumanDelay(page, 'input[type="password"]', process.env.SPOTIFY_PASSWORD); + await delay(1500); console.log("🚀 Submitting login..."); - let loginClicked = false; - try { - loginClicked = await safeClick(page, '#login-button') || - await safeClick(page, 'button[data-testid="login-button"]') || - await safeClick(page, 'button[data-encore-id="buttonPrimary"]') || - await safeClick(page, 'button[type="submit"]') || - await clickByText(page, 'Log In') || - await clickByText(page, 'log in'); - - console.log("🌐 Current page URL:", page.url()); - const buttons = await page.$$eval('button', btns => btns.map(b => b.textContent?.trim())); - console.log("🔍 Buttons detected:", buttons); - - if (!loginClicked) { - const allBtns = await page.$$eval('button', btns => btns.map(b => b.textContent?.trim())); - console.log("🔍 Available buttons:", allBtns); - } - } catch { console.log("⚠️ Fallback: Login button not found in known selectors"); } - - if (!loginClicked) throw new Error("Login button not found"); + const loginClicked = await safeClick(page, '#login-button') || + await safeClick(page, 'button[data-testid="login-button"]') || + await safeClick(page, 'button[data-encore-id="buttonPrimary"]') || + await clickByText(page, 'Log In'); + if (!loginClicked) throw new Error("❌ Login button not found."); await delay(5000); - try { - const stayLoggedIn = await clickByText(page, 'stay logged in') || - await safeClick(page, '[data-testid="auth-accept"]'); - if (stayLoggedIn) console.log("🔘 Clicked 'Stay Logged In'"); - } catch {} - console.log("🔄 Checking for authorization screen..."); - try { - const authAccepted = await clickByText(page, 'agree') || - await safeClick(page, '#auth-accept'); - if (authAccepted) { - console.log("✅ Clicked 'Agree'"); - await page.waitForNavigation({ waitUntil: 'networkidle0', timeout: 15000 }); - } - } catch {} - - const currentURL = page.url(); - if (currentURL.includes('callback?code=')) { - console.log('🎉 Spotify authorization successful!'); - } else { - throw new Error(`Authorization failed. Current URL: ${currentURL}`); + const authClicked = await safeClick(page, '#auth-accept') || await clickByText(page, 'Agree'); + if (authClicked) { + console.log("✅ Authorization accepted."); + await page.waitForNavigation({ waitUntil: 'networkidle0', timeout: 15000 }); } + const finalURL = page.url(); + if (!finalURL.includes('callback?code=')) { + throw new Error(`❌ Authorization failed. Final URL: ${finalURL}`); + } + + console.log("🎉 Spotify authorization successful!"); + } catch (error) { console.error('💥 Error during Spotify login:', error.message); await page.screenshot({ path: 'spotify_error.png', fullPage: true }); From 532ac60b261f2f2876be1eaa7e6e8179584fcf74 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Tue, 24 Jun 2025 09:32:15 +0530 Subject: [PATCH 17/48] change workflow-2 --- .github/workflows/tf-apply.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index 0fab96d..f77b4be 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -29,7 +29,7 @@ jobs: SPOTIFY_CLIENT_SECRET: ${{ secrets.SPOTIFY_CLIENT_SECRET }} SPOTIFY_CLIENT_REDIRECT_URI: ${{ secrets.SPOTIFY_CLIENT_REDIRECT_URI }} run: | - spotify_auth_proxy > proxy_output.log 2>&1 & + spotify_auth_proxy --port 8080 --redirect_uri http://127.0.0.1:8080/callback > proxy_output.log 2>&1 & PROXY_PID=$! echo "Waiting for Auth URL and API Key..." for i in {1..30}; do @@ -37,7 +37,7 @@ jobs: sleep 1 done if ! (grep -q "Auth URL:" proxy_output.log && grep -q "APIKey:" proxy_output.log); then - echo "❌ Failed to get Auth URL or API Key"; cat proxy_output.log; exit 1 + echo "❌ Failed to get Auth URL or API Key"; cat proxy_output.log; exit 1 fi auth_url=$(grep "Auth URL:" proxy_output.log | awk '{print $3}') api_key=$(grep "APIKey:" proxy_output.log | awk '{print $2}') From d422cba7ab306e28c93d29c6d5eb96a4b40e2c94 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Tue, 24 Jun 2025 09:43:21 +0530 Subject: [PATCH 18/48] change workflow-3 --- .github/workflows/tf-apply.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index f77b4be..6f39d98 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -29,7 +29,7 @@ jobs: SPOTIFY_CLIENT_SECRET: ${{ secrets.SPOTIFY_CLIENT_SECRET }} SPOTIFY_CLIENT_REDIRECT_URI: ${{ secrets.SPOTIFY_CLIENT_REDIRECT_URI }} run: | - spotify_auth_proxy --port 8080 --redirect_uri http://127.0.0.1:8080/callback > proxy_output.log 2>&1 & + spotify_auth_proxy --port 8080 --redirect_uri $SPOTIFY_CLIENT_REDIRECT_URI > proxy_output.log 2>&1 & PROXY_PID=$! echo "Waiting for Auth URL and API Key..." for i in {1..30}; do @@ -37,7 +37,7 @@ jobs: sleep 1 done if ! (grep -q "Auth URL:" proxy_output.log && grep -q "APIKey:" proxy_output.log); then - echo "❌ Failed to get Auth URL or API Key"; cat proxy_output.log; exit 1 + echo "❌ Failed to get Auth URL or API Key"; cat proxy_output.log; exit 1 fi auth_url=$(grep "Auth URL:" proxy_output.log | awk '{print $3}') api_key=$(grep "APIKey:" proxy_output.log | awk '{print $2}') @@ -56,6 +56,7 @@ jobs: env: SPOTIFY_USERNAME: ${{ secrets.SPOTIFY_USERNAME }} SPOTIFY_PASSWORD: ${{ secrets.SPOTIFY_PASSWORD }} + AUTH_URL: ${{ env.AUTH_URL }} run: | node - <<'EOF' const puppeteer = require('puppeteer'); @@ -194,4 +195,4 @@ jobs: name: spotify-debug path: | spotify_error.png - error_debug.html + error_debug.html \ No newline at end of file From 1cf2848bbbb9c916e7b61f691942eb204434b42a Mon Sep 17 00:00:00 2001 From: Gaurav Date: Tue, 24 Jun 2025 09:51:25 +0530 Subject: [PATCH 19/48] change workflow-4 --- .github/workflows/tf-apply.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index 6f39d98..e0a6ceb 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -29,7 +29,7 @@ jobs: SPOTIFY_CLIENT_SECRET: ${{ secrets.SPOTIFY_CLIENT_SECRET }} SPOTIFY_CLIENT_REDIRECT_URI: ${{ secrets.SPOTIFY_CLIENT_REDIRECT_URI }} run: | - spotify_auth_proxy --port 8080 --redirect_uri $SPOTIFY_CLIENT_REDIRECT_URI > proxy_output.log 2>&1 & + spotify_auth_proxy --port 8080 --redirect_uri http://127.0.0.1:8080/callback > proxy_output.log 2>&1 & PROXY_PID=$! echo "Waiting for Auth URL and API Key..." for i in {1..30}; do From 4e0446d1f2d92d828d4dea34668386fde248d5e5 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Tue, 24 Jun 2025 10:00:23 +0530 Subject: [PATCH 20/48] change workflow-4 --- .github/workflows/tf-apply.yaml | 250 ++++++++++++++++++-------------- 1 file changed, 144 insertions(+), 106 deletions(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index e0a6ceb..48223e4 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -1,15 +1,14 @@ -name: Update playlist with Spotify Auth +name: Spotify Playlist Updater on: push: - branches: - - main + branches: [ main ] jobs: - spotify-auth-and-apply: + spotify-auth-and-update: runs-on: ubuntu-latest - name: Apply - timeout-minutes: 10 + name: Authenticate and Update + timeout-minutes: 15 steps: - name: Checkout repository @@ -18,65 +17,87 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: '1.18.0' + go-version: '1.21' - name: Install spotify_auth_proxy run: go install github.com/conradludgate/terraform-provider-spotify/spotify_auth_proxy@latest - - name: Run spotify_auth_proxy and capture Auth URL + - name: Run auth proxy env: SPOTIFY_CLIENT_ID: ${{ secrets.SPOTIFY_CLIENT_ID }} SPOTIFY_CLIENT_SECRET: ${{ secrets.SPOTIFY_CLIENT_SECRET }} - SPOTIFY_CLIENT_REDIRECT_URI: ${{ secrets.SPOTIFY_CLIENT_REDIRECT_URI }} + SPOTIFY_REDIRECT_URI: ${{ secrets.SPOTIFY_REDIRECT_URI }} run: | - spotify_auth_proxy --port 8080 --redirect_uri http://127.0.0.1:8080/callback > proxy_output.log 2>&1 & - PROXY_PID=$! - echo "Waiting for Auth URL and API Key..." + spotify_auth_proxy --port 8080 > proxy.log 2>&1 & + echo "Waiting for auth URL..." for i in {1..30}; do - if grep -q "Auth URL:" proxy_output.log && grep -q "APIKey:" proxy_output.log; then break; fi + if grep -q "Auth URL:" proxy.log; then break; fi sleep 1 done - if ! (grep -q "Auth URL:" proxy_output.log && grep -q "APIKey:" proxy_output.log); then - echo "❌ Failed to get Auth URL or API Key"; cat proxy_output.log; exit 1 - fi - auth_url=$(grep "Auth URL:" proxy_output.log | awk '{print $3}') - api_key=$(grep "APIKey:" proxy_output.log | awk '{print $2}') + auth_url=$(grep "Auth URL:" proxy.log | awk '{print $3}') + api_key=$(grep "APIKey:" proxy.log | awk '{print $2}') echo "AUTH_URL=$auth_url" >> $GITHUB_ENV echo "SPOTIFY_API_KEY=$api_key" >> $GITHUB_ENV + if [ -z "$auth_url" ] || [ -z "$api_key" ]; then + echo "::error::Failed to get auth credentials" + cat proxy.log + exit 1 + fi - name: Setup Node.js uses: actions/setup-node@v3 with: - node-version: '18' + node-version: '20' - - name: Install Puppeteer and fs-extra - run: npm install puppeteer fs-extra + - name: Install dependencies + run: npm install puppeteer@21 fs-extra@11 - - name: Perform Spotify login and authorization + - name: Perform Spotify authentication env: SPOTIFY_USERNAME: ${{ secrets.SPOTIFY_USERNAME }} SPOTIFY_PASSWORD: ${{ secrets.SPOTIFY_PASSWORD }} - AUTH_URL: ${{ env.AUTH_URL }} run: | node - <<'EOF' const puppeteer = require('puppeteer'); const fs = require('fs-extra'); - const delay = ms => new Promise(res => setTimeout(res, ms)); - const randomDelay = (min, max) => Math.floor(Math.random() * (max - min + 1) + min); + // Configuration + const MAX_RETRIES = 3; + const TIMEOUT = 30000; + const DEBUG = true; + + // Utilities + const delay = ms => new Promise(r => setTimeout(r, ms)); + const randomDelay = () => Math.floor(Math.random() * 500) + 500; + const debugLog = (...args) => DEBUG && console.log('[DEBUG]', ...args); + + async function handleErrors(page, error) { + console.error('❌ Error:', error.message); + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + await page.screenshot({ path: `error-${timestamp}.png`, fullPage: true }); + await fs.writeFile(`error-${timestamp}.html`, await page.content()); + throw error; + } - async function typeWithHumanDelay(page, selector, text) { - for (const char of text) { - await page.type(selector, char, { delay: randomDelay(30, 100) }); - if (Math.random() > 0.8) await delay(randomDelay(50, 150)); + async function safeClick(page, selector, timeout = 10000) { + try { + debugLog(`Looking for: ${selector}`); + await page.waitForSelector(selector, { visible: true, timeout }); + await delay(randomDelay()); + await page.click(selector); + return true; + } catch (e) { + debugLog(`Click failed for ${selector}: ${e.message}`); + return false; } } - async function clickByText(page, text, elementType = 'button') { - const elements = await page.$$(elementType); + async function clickByText(page, text, tag = '*') { + const elements = await page.$$(tag); for (const element of elements) { const elementText = await page.evaluate(el => el.textContent?.trim(), element); - if (elementText?.toLowerCase() === text.toLowerCase()) { + if (elementText?.toLowerCase().includes(text.toLowerCase())) { + await delay(randomDelay()); await element.click(); return true; } @@ -84,115 +105,132 @@ jobs: return false; } - async function safeClick(page, selector, timeout = 10000) { - try { - await page.waitForSelector(selector, { visible: true, timeout }); - await delay(500); - const element = await page.$(selector); - if (element) { - await element.click(); - return true; - } - } catch {} - return false; + async function typeWithDelay(page, selector, text) { + await page.focus(selector); + await delay(randomDelay()); + for (const char of text) { + await page.keyboard.type(char, { delay: Math.random() * 50 + 50 }); + if (Math.random() > 0.9) await delay(randomDelay()); + } } - (async () => { + async function run() { const browser = await puppeteer.launch({ - headless: true, + headless: 'new', args: ['--no-sandbox', '--disable-setuid-sandbox'], defaultViewport: { width: 1280, height: 800 } }); const page = await browser.newPage(); - await page.setUserAgent('Mozilla/5.0'); + page.setDefaultTimeout(TIMEOUT); try { - console.log("🌐 Navigating to Spotify Auth URL..."); - await page.goto(process.env.AUTH_URL, { waitUntil: 'networkidle2', timeout: 60000 }); - await delay(3000); + // Step 1: Initial navigation + console.log('🌐 Navigating to Spotify auth page...'); + await page.goto(process.env.AUTH_URL, { waitUntil: 'networkidle2' }); + + // Check if already authorized + if (page.url().includes('callback?code=')) { + console.log('✅ Already authenticated'); + return; + } - // Check if redirected to Google login by mistake - const url = page.url(); - if (url.includes('accounts.google.com')) { - throw new Error("❌ Redirected to Google login instead of Spotify login page."); + // Step 2: Handle cookies + if (await safeClick(page, '#onetrust-accept-btn-handler') || + await clickByText(page, 'accept cookies')) { + console.log('🍪 Accepted cookies'); + await delay(2000); } - console.log("📧 Entering email..."); - await page.waitForSelector('input[type="email"], #login-username', { visible: true, timeout: 15000 }); - await typeWithHumanDelay(page, 'input[type="email"], #login-username', process.env.SPOTIFY_USERNAME); - await delay(1000); - - console.log("➡️ Clicking Continue..."); - const continueClicked = await clickByText(page, 'Continue') || - await safeClick(page, 'button[data-testid="login-button"]') || - await safeClick(page, 'button[type="submit"]'); - if (!continueClicked) throw new Error("❌ Continue button not found."); - await delay(4000); - - console.log("🔍 Checking for OTP screen..."); - const otpDetected = await page.evaluate(() => - Array.from(document.querySelectorAll('*')).some(el => - el.textContent?.toLowerCase().includes('6-digit code') - ) + // Step 3: Email entry + console.log('📧 Entering email...'); + await typeWithDelay(page, 'input[type="email"], #login-username', process.env.SPOTIFY_USERNAME); + + if (!(await safeClick(page, 'button[data-testid="login-button"]') || + await clickByText(page, 'continue'))) { + throw new Error('Continue button not found'); + } + await delay(3000); + + // Step 4: Handle OTP/password choice + const needsPassword = await page.evaluate(() => + Array.from(document.querySelectorAll('*')) + .some(el => el.textContent?.includes('6-digit code')) ); - if (otpDetected) { - console.log("🔁 Clicking 'Log in with a password'..."); - const clicked = await clickByText(page, 'Log in with a password') || - await safeClick(page, 'button[data-encore-id="buttonTertiary"]'); - if (!clicked) throw new Error("❌ Could not find 'Log in with a password' option."); + if (needsPassword) { + console.log('🔄 Switching to password login...'); + if (!(await clickByText(page, 'log in with a password') || + await safeClick(page, 'button[data-encore-id="buttonTertiary"]'))) { + throw new Error('Password login option not found'); + } await delay(3000); } - console.log("🔑 Entering password..."); - await page.waitForSelector('input[type="password"]', { visible: true, timeout: 10000 }); - await typeWithHumanDelay(page, 'input[type="password"]', process.env.SPOTIFY_PASSWORD); - await delay(1500); - - console.log("🚀 Submitting login..."); - const loginClicked = await safeClick(page, '#login-button') || - await safeClick(page, 'button[data-testid="login-button"]') || - await safeClick(page, 'button[data-encore-id="buttonPrimary"]') || - await clickByText(page, 'Log In'); - if (!loginClicked) throw new Error("❌ Login button not found."); + // Step 5: Password entry + console.log('🔑 Entering password...'); + await typeWithDelay(page, 'input[type="password"], #login-password', process.env.SPOTIFY_PASSWORD); + + if (!(await safeClick(page, 'button[data-testid="login-button"]') || + await clickByText(page, 'log in'))) { + throw new Error('Login button not found'); + } await delay(5000); - console.log("🔄 Checking for authorization screen..."); - const authClicked = await safeClick(page, '#auth-accept') || await clickByText(page, 'Agree'); - if (authClicked) { - console.log("✅ Authorization accepted."); - await page.waitForNavigation({ waitUntil: 'networkidle0', timeout: 15000 }); + // Step 6: Handle authorization + let attempts = 0; + while (attempts < MAX_RETRIES && !page.url().includes('callback?code=')) { + attempts++; + console.log(`🔄 Authorization attempt ${attempts}/${MAX_RETRIES}`); + + if (await safeClick(page, '#auth-accept') || + await clickByText(page, 'agree')) { + console.log('✅ Clicked authorization button'); + await delay(3000); + } + + // Check for errors + const error = await page.evaluate(() => { + const errorEl = document.querySelector('[role="alert"], .error'); + return errorEl?.textContent?.trim(); + }); + + if (error) throw new Error(`Spotify error: ${error}`); + + await delay(2000); } - const finalURL = page.url(); - if (!finalURL.includes('callback?code=')) { - throw new Error(`❌ Authorization failed. Final URL: ${finalURL}`); + // Final verification + if (!page.url().includes('callback?code=')) { + throw new Error(`Authorization failed after ${MAX_RETRIES} attempts`); } - console.log("🎉 Spotify authorization successful!"); + console.log('🎉 Successfully authenticated with Spotify!'); } catch (error) { - console.error('💥 Error during Spotify login:', error.message); - await page.screenshot({ path: 'spotify_error.png', fullPage: true }); - await fs.writeFile('error_debug.html', await page.content()); - throw error; + await handleErrors(page, error); } finally { await browser.close(); } - })(); + } + + run().catch(e => { + console.error('Unhandled error:', e); + process.exit(1); + }); EOF - - name: Run Terraform Apply + - name: Run Terraform run: | terraform init - terraform apply -var="SPOTIFY_API_KEY=${{ env.SPOTIFY_API_KEY }}" -auto-approve + terraform apply -var="spotify_api_key=${{ env.SPOTIFY_API_KEY }}" -auto-approve - - name: Upload debug files (on failure) + - name: Upload debug artifacts if: failure() uses: actions/upload-artifact@v4 with: - name: spotify-debug + name: auth-debug path: | - spotify_error.png - error_debug.html \ No newline at end of file + error-*.png + error-*.html + proxy.log \ No newline at end of file From 83b50ea0e1fcccba85228c6f3515e00a04437239 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Tue, 24 Jun 2025 10:05:53 +0530 Subject: [PATCH 21/48] change all --- .github/workflows/tf-apply.yaml | 277 +++++++++++++++++++------------- 1 file changed, 165 insertions(+), 112 deletions(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index 48223e4..fb862c9 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -50,7 +50,7 @@ jobs: node-version: '20' - name: Install dependencies - run: npm install puppeteer@21 fs-extra@11 + run: npm install puppeteer@22.8.2 fs-extra@11 - name: Perform Spotify authentication env: @@ -62,50 +62,84 @@ jobs: const fs = require('fs-extra'); // Configuration - const MAX_RETRIES = 3; - const TIMEOUT = 30000; - const DEBUG = true; + const config = { + maxRetries: 3, + timeout: 30000, + debug: true, + userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36' + }; // Utilities const delay = ms => new Promise(r => setTimeout(r, ms)); const randomDelay = () => Math.floor(Math.random() * 500) + 500; - const debugLog = (...args) => DEBUG && console.log('[DEBUG]', ...args); + const debugLog = (...args) => config.debug && console.log('[DEBUG]', ...args); - async function handleErrors(page, error) { - console.error('❌ Error:', error.message); - const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); - await page.screenshot({ path: `error-${timestamp}.png`, fullPage: true }); - await fs.writeFile(`error-${timestamp}.html`, await page.content()); - throw error; + class AuthError extends Error { + constructor(message, screenshotPath, htmlPath) { + super(message); + this.screenshotPath = screenshotPath; + this.htmlPath = htmlPath; + } } - async function safeClick(page, selector, timeout = 10000) { - try { - debugLog(`Looking for: ${selector}`); - await page.waitForSelector(selector, { visible: true, timeout }); - await delay(randomDelay()); - await page.click(selector); - return true; - } catch (e) { - debugLog(`Click failed for ${selector}: ${e.message}`); - return false; - } + async function saveDebugArtifacts(page, prefix) { + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const screenshotPath = `${prefix}-${timestamp}.png`; + const htmlPath = `${prefix}-${timestamp}.html`; + + await page.screenshot({ path: screenshotPath, fullPage: true }); + await fs.writeFile(htmlPath, await page.content()); + + return { screenshotPath, htmlPath }; } - async function clickByText(page, text, tag = '*') { - const elements = await page.$$(tag); - for (const element of elements) { - const elementText = await page.evaluate(el => el.textContent?.trim(), element); - if (elementText?.toLowerCase().includes(text.toLowerCase())) { + async function safeClick(page, selectors, timeout = 10000) { + const selectorList = Array.isArray(selectors) ? selectors : [selectors]; + + for (const selector of selectorList) { + try { + debugLog(`Attempting to click: ${selector}`); + await page.waitForSelector(selector, { visible: true, timeout }); await delay(randomDelay()); - await element.click(); + await page.click(selector); return true; + } catch (e) { + debugLog(`Click failed for ${selector}: ${e.message}`); } } return false; } - async function typeWithDelay(page, selector, text) { + async function clickByText(page, text, tag = '*', timeout = 5000) { + try { + await page.waitForFunction( + (text, tag) => { + const elements = Array.from(document.querySelectorAll(tag)); + return elements.some(el => + el.textContent?.trim().toLowerCase().includes(text.toLowerCase()) + ); + }, + { timeout }, + text, + tag + ); + + const elements = await page.$$(tag); + for (const element of elements) { + const elementText = await page.evaluate(el => el.textContent?.trim(), element); + if (elementText?.toLowerCase().includes(text.toLowerCase())) { + await delay(randomDelay()); + await element.click(); + return true; + } + } + } catch (e) { + debugLog(`Text click failed for "${text}": ${e.message}`); + } + return false; + } + + async function typeWithHumanDelay(page, selector, text) { await page.focus(selector); await delay(randomDelay()); for (const char of text) { @@ -114,108 +148,127 @@ jobs: } } - async function run() { - const browser = await puppeteer.launch({ - headless: 'new', - args: ['--no-sandbox', '--disable-setuid-sandbox'], - defaultViewport: { width: 1280, height: 800 } - }); + async function handleSpotifyAuth(page) { + // Step 1: Initial navigation + console.log('🌐 Navigating to Spotify auth page...'); + await page.goto(process.env.AUTH_URL, { waitUntil: 'networkidle2' }); - const page = await browser.newPage(); - page.setDefaultTimeout(TIMEOUT); + // Check if already authorized + if (page.url().includes('callback?code=')) { + console.log('✅ Already authenticated'); + return true; + } - try { - // Step 1: Initial navigation - console.log('🌐 Navigating to Spotify auth page...'); - await page.goto(process.env.AUTH_URL, { waitUntil: 'networkidle2' }); - - // Check if already authorized - if (page.url().includes('callback?code=')) { - console.log('✅ Already authenticated'); - return; - } + // Step 2: Handle cookies + if (await safeClick(page, ['#onetrust-accept-btn-handler', '.ot-sdk-row button']) || + await clickByText(page, 'accept cookies')) { + console.log('🍪 Accepted cookies'); + await delay(2000); + } - // Step 2: Handle cookies - if (await safeClick(page, '#onetrust-accept-btn-handler') || - await clickByText(page, 'accept cookies')) { - console.log('🍪 Accepted cookies'); - await delay(2000); - } + // Step 3: Email entry + console.log('📧 Entering email...'); + await typeWithHumanDelay(page, 'input[type="email"], #login-username', process.env.SPOTIFY_USERNAME); + + if (!(await safeClick(page, ['button[data-testid="login-button"]', 'button[type="submit"]']) || + await clickByText(page, 'continue'))) { + throw new AuthError('Continue button not found', ...await saveDebugArtifacts(page, 'continue-error')); + } + await delay(3000); - // Step 3: Email entry - console.log('📧 Entering email...'); - await typeWithDelay(page, 'input[type="email"], #login-username', process.env.SPOTIFY_USERNAME); - - if (!(await safeClick(page, 'button[data-testid="login-button"]') || - await clickByText(page, 'continue'))) { - throw new Error('Continue button not found'); + // Step 4: Handle OTP/password choice + const needsPassword = await page.evaluate(() => + Array.from(document.querySelectorAll('*')) + .some(el => el.textContent?.includes('6-digit code')) + ); + + if (needsPassword) { + console.log('🔄 Switching to password login...'); + if (!(await clickByText(page, 'log in with a password') || + await safeClick(page, ['button[data-encore-id="buttonTertiary"]', '.auth-footer button']))) { + throw new AuthError('Password login option not found', ...await saveDebugArtifacts(page, 'password-switch-error')); } await delay(3000); + } - // Step 4: Handle OTP/password choice - const needsPassword = await page.evaluate(() => - Array.from(document.querySelectorAll('*')) - .some(el => el.textContent?.includes('6-digit code')) - ); + // Step 5: Password entry + console.log('🔑 Entering password...'); + await typeWithHumanDelay(page, 'input[type="password"], #login-password', process.env.SPOTIFY_PASSWORD); + + if (!(await safeClick(page, ['button[data-testid="login-button"]', '#login-button']) || + await clickByText(page, 'log in'))) { + throw new AuthError('Login button not found', ...await saveDebugArtifacts(page, 'login-error')); + } + await delay(5000); - if (needsPassword) { - console.log('🔄 Switching to password login...'); - if (!(await clickByText(page, 'log in with a password') || - await safeClick(page, 'button[data-encore-id="buttonTertiary"]'))) { - throw new Error('Password login option not found'); - } + // Step 6: Handle authorization + let attempts = 0; + while (attempts < config.maxRetries && !page.url().includes('callback?code=')) { + attempts++; + console.log(`🔄 Authorization attempt ${attempts}/${config.maxRetries}`); + + if (await safeClick(page, ['#auth-accept', 'button[data-testid="auth-accept"]']) || + await clickByText(page, 'agree')) { + console.log('✅ Clicked authorization button'); await delay(3000); } - - // Step 5: Password entry - console.log('🔑 Entering password...'); - await typeWithDelay(page, 'input[type="password"], #login-password', process.env.SPOTIFY_PASSWORD); - if (!(await safeClick(page, 'button[data-testid="login-button"]') || - await clickByText(page, 'log in'))) { - throw new Error('Login button not found'); - } - await delay(5000); - - // Step 6: Handle authorization - let attempts = 0; - while (attempts < MAX_RETRIES && !page.url().includes('callback?code=')) { - attempts++; - console.log(`🔄 Authorization attempt ${attempts}/${MAX_RETRIES}`); - - if (await safeClick(page, '#auth-accept') || - await clickByText(page, 'agree')) { - console.log('✅ Clicked authorization button'); - await delay(3000); - } - - // Check for errors - const error = await page.evaluate(() => { - const errorEl = document.querySelector('[role="alert"], .error'); - return errorEl?.textContent?.trim(); - }); - - if (error) throw new Error(`Spotify error: ${error}`); - - await delay(2000); + // Check for errors + const error = await page.evaluate(() => { + const errorEl = document.querySelector('[role="alert"], .error, .error-message'); + return errorEl?.textContent?.trim(); + }); + + if (error) { + throw new AuthError(`Spotify error: ${error}`, ...await saveDebugArtifacts(page, 'auth-error')); } + + await delay(2000); + } - // Final verification - if (!page.url().includes('callback?code=')) { - throw new Error(`Authorization failed after ${MAX_RETRIES} attempts`); + return page.url().includes('callback?code='); + } + + async function run() { + const browser = await puppeteer.launch({ + headless: 'new', + args: [ + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-dev-shm-usage', + '--disable-accelerated-2d-canvas', + '--disable-gpu' + ], + defaultViewport: { width: 1280, height: 800 } + }); + + const page = await browser.newPage(); + await page.setUserAgent(config.userAgent); + page.setDefaultTimeout(config.timeout); + + try { + const success = await handleSpotifyAuth(page); + if (!success) { + throw new AuthError( + `Authorization failed after ${config.maxRetries} attempts`, + ...await saveDebugArtifacts(page, 'final-error') + ); } console.log('🎉 Successfully authenticated with Spotify!'); - - } catch (error) { - await handleErrors(page, error); } finally { await browser.close(); } } - run().catch(e => { - console.error('Unhandled error:', e); + run().catch(async (error) => { + console.error('❌ Authentication failed:', error.message); + if (error.screenshotPath) { + console.log(`📸 Screenshot saved to: ${error.screenshotPath}`); + } + if (error.htmlPath) { + console.log(`📄 HTML saved to: ${error.htmlPath}`); + } process.exit(1); }); EOF @@ -231,6 +284,6 @@ jobs: with: name: auth-debug path: | - error-*.png - error-*.html + *.png + *.html proxy.log \ No newline at end of file From 7ad7fb42d78a4db48a799e531a59026caf5bc6be Mon Sep 17 00:00:00 2001 From: Gaurav Date: Tue, 24 Jun 2025 12:04:34 +0530 Subject: [PATCH 22/48] all fix (maybe) --- .github/workflows/tf-apply.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index fb862c9..32c1c55 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -19,8 +19,9 @@ jobs: with: go-version: '1.21' - - name: Install spotify_auth_proxy - run: go install github.com/conradludgate/terraform-provider-spotify/spotify_auth_proxy@latest + - name: Install patched spotify_auth_proxy (temporary from fork) + run: | + go install github.com/gauravslnk/terraform-provider-spotify/spotify_auth_proxy@fix/base-url-127 - name: Run auth proxy env: From e6ffc89603a2af10996dacff1ed176a47f482176 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Tue, 24 Jun 2025 12:07:15 +0530 Subject: [PATCH 23/48] all fix (maybe)-minor change --- .github/workflows/tf-apply.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index 32c1c55..935ba35 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -19,9 +19,9 @@ jobs: with: go-version: '1.21' - - name: Install patched spotify_auth_proxy (temporary from fork) + - name: Install patched spotify_auth_proxy (using commit hash) run: | - go install github.com/gauravslnk/terraform-provider-spotify/spotify_auth_proxy@fix/base-url-127 + go install github.com/gauravslnk/terraform-provider-spotify/spotify_auth_proxy@cd066e9e0c5f0d4df8da3ed58f8571eee3fce251 - name: Run auth proxy env: From 67abe2d5473247755d19ac7393cfe16c3ea1a973 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Tue, 24 Jun 2025 12:10:15 +0530 Subject: [PATCH 24/48] all fix (maybe)-minor change-1 --- .github/workflows/tf-apply.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index 935ba35..f1af65a 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -19,9 +19,11 @@ jobs: with: go-version: '1.21' - - name: Install patched spotify_auth_proxy (using commit hash) + - name: Install patched spotify_auth_proxy from fork (with GOPRIVATE) + env: + GOPRIVATE: github.com/conradludgate/terraform-provider-spotify run: | - go install github.com/gauravslnk/terraform-provider-spotify/spotify_auth_proxy@cd066e9e0c5f0d4df8da3ed58f8571eee3fce251 + go install github.com/conradludgate/terraform-provider-spotify/spotify_auth_proxy@cd066e9e0c5f0d4df8da3ed58f8571eee3fce251 - name: Run auth proxy env: From b08bf293b09c41aae7905aea50a7de85dcc922fc Mon Sep 17 00:00:00 2001 From: Gaurav Date: Tue, 24 Jun 2025 12:32:16 +0530 Subject: [PATCH 25/48] all fix (maybe)-minor change-2 --- .github/workflows/tf-apply.yaml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index f1af65a..a74537b 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -19,11 +19,8 @@ jobs: with: go-version: '1.21' - - name: Install patched spotify_auth_proxy from fork (with GOPRIVATE) - env: - GOPRIVATE: github.com/conradludgate/terraform-provider-spotify - run: | - go install github.com/conradludgate/terraform-provider-spotify/spotify_auth_proxy@cd066e9e0c5f0d4df8da3ed58f8571eee3fce251 + - name: Install patched spotify_auth_proxy + run: go install github.com/gauravsInk/terraform-provider-spotify/spotify_auth_proxy#fix/base-url-127 - name: Run auth proxy env: From 39b63e960bde169454e865aa4a2dec13d04eedcb Mon Sep 17 00:00:00 2001 From: Gaurav Date: Tue, 24 Jun 2025 12:35:16 +0530 Subject: [PATCH 26/48] all fix (maybe)-minor change-3 --- .github/workflows/tf-apply.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index a74537b..6a4f8b7 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -20,7 +20,7 @@ jobs: go-version: '1.21' - name: Install patched spotify_auth_proxy - run: go install github.com/gauravsInk/terraform-provider-spotify/spotify_auth_proxy#fix/base-url-127 + run: go install github.com/gauravslnk/terraform-provider-spotify/tree/fix/base-url-127 - name: Run auth proxy env: From db395130c74ac96f1d35ac2d5337bd73ed6c8c8b Mon Sep 17 00:00:00 2001 From: Gaurav Date: Tue, 24 Jun 2025 12:36:31 +0530 Subject: [PATCH 27/48] all fix (maybe)-minor change-3.1 --- .github/workflows/tf-apply.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index 6a4f8b7..ccd0fdf 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -20,7 +20,7 @@ jobs: go-version: '1.21' - name: Install patched spotify_auth_proxy - run: go install github.com/gauravslnk/terraform-provider-spotify/tree/fix/base-url-127 + run: go install github.com/gauravslnk/terraform-provider-spotify/tree/fix/base-url-127@latest - name: Run auth proxy env: From 6a6547f43ecf302288a9339a04fad8cde945a76d Mon Sep 17 00:00:00 2001 From: Gaurav Date: Tue, 24 Jun 2025 12:38:30 +0530 Subject: [PATCH 28/48] all fix (maybe)-minor change-3.2 --- .github/workflows/tf-apply.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index ccd0fdf..efecfba 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -20,7 +20,7 @@ jobs: go-version: '1.21' - name: Install patched spotify_auth_proxy - run: go install github.com/gauravslnk/terraform-provider-spotify/tree/fix/base-url-127@latest + run: go install github.com/gauravslnk/terraform-provider-spotify/tree/fix/base-url-127/spotify_auth_proxy@latest - name: Run auth proxy env: From ecb83198ad33cd1c1bdcd6ee5090395dfe5bdb6c Mon Sep 17 00:00:00 2001 From: Gaurav Date: Tue, 24 Jun 2025 12:44:09 +0530 Subject: [PATCH 29/48] all fix (maybe)-minor change-3.2 --- .github/workflows/tf-apply.yaml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index efecfba..c6f295d 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -20,7 +20,23 @@ jobs: go-version: '1.21' - name: Install patched spotify_auth_proxy - run: go install github.com/gauravslnk/terraform-provider-spotify/tree/fix/base-url-127/spotify_auth_proxy@latest + run: | + # Clean GOPATH if needed + export GOPATH=$(go env GOPATH) + export PATH=$GOPATH/bin:$PATH + + # Clone specific branch + git clone -b fix/base-url-127 https://github.com/gauravslnk/terraform-provider-spotify + cd terraform-provider-spotify/spotify_auth_proxy + + # Build and install + go build -o $GOPATH/bin/spotify_auth_proxy . + + # Verify installation + if ! command -v spotify_auth_proxy &> /dev/null; then + echo "::error::Installation failed" + exit 1 + fi - name: Run auth proxy env: From b1531d53941a0521a5759cf1c67b3259215eaf72 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Tue, 24 Jun 2025 12:56:34 +0530 Subject: [PATCH 30/48] one more time --- .github/workflows/tf-apply.yaml | 309 ++++++++++---------------------- 1 file changed, 95 insertions(+), 214 deletions(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index c6f295d..fe87290 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -1,14 +1,15 @@ -name: Spotify Playlist Updater +name: Update playlist with Spotify Auth on: push: - branches: [ main ] + branches: + - main jobs: - spotify-auth-and-update: + spotify-auth-and-apply: runs-on: ubuntu-latest - name: Authenticate and Update - timeout-minutes: 15 + name: Apply + timeout-minutes: 10 steps: - name: Checkout repository @@ -21,18 +22,10 @@ jobs: - name: Install patched spotify_auth_proxy run: | - # Clean GOPATH if needed - export GOPATH=$(go env GOPATH) - export PATH=$GOPATH/bin:$PATH - - # Clone specific branch + echo "$HOME/go/bin" >> $GITHUB_PATH git clone -b fix/base-url-127 https://github.com/gauravslnk/terraform-provider-spotify cd terraform-provider-spotify/spotify_auth_proxy - - # Build and install - go build -o $GOPATH/bin/spotify_auth_proxy . - - # Verify installation + go build -o $HOME/go/bin/spotify_auth_proxy . if ! command -v spotify_auth_proxy &> /dev/null; then echo "::error::Installation failed" exit 1 @@ -68,7 +61,7 @@ jobs: - name: Install dependencies run: npm install puppeteer@22.8.2 fs-extra@11 - - name: Perform Spotify authentication + - name: Perform Spotify login and authorization env: SPOTIFY_USERNAME: ${{ secrets.SPOTIFY_USERNAME }} SPOTIFY_PASSWORD: ${{ secrets.SPOTIFY_PASSWORD }} @@ -76,230 +69,118 @@ jobs: node - <<'EOF' const puppeteer = require('puppeteer'); const fs = require('fs-extra'); - - // Configuration - const config = { - maxRetries: 3, - timeout: 30000, - debug: true, - userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36' - }; - - // Utilities - const delay = ms => new Promise(r => setTimeout(r, ms)); - const randomDelay = () => Math.floor(Math.random() * 500) + 500; - const debugLog = (...args) => config.debug && console.log('[DEBUG]', ...args); - - class AuthError extends Error { - constructor(message, screenshotPath, htmlPath) { - super(message); - this.screenshotPath = screenshotPath; - this.htmlPath = htmlPath; + const delay = ms => new Promise(res => setTimeout(res, ms)); + const randomDelay = (min, max) => Math.floor(Math.random() * (max - min + 1) + min); + async function typeWithHumanDelay(page, selector, text) { + for (const char of text) { + await page.type(selector, char, { delay: randomDelay(30, 100) }); + if (Math.random() > 0.8) await delay(randomDelay(50, 150)); } } - - async function saveDebugArtifacts(page, prefix) { - const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); - const screenshotPath = `${prefix}-${timestamp}.png`; - const htmlPath = `${prefix}-${timestamp}.html`; - - await page.screenshot({ path: screenshotPath, fullPage: true }); - await fs.writeFile(htmlPath, await page.content()); - - return { screenshotPath, htmlPath }; - } - - async function safeClick(page, selectors, timeout = 10000) { - const selectorList = Array.isArray(selectors) ? selectors : [selectors]; - - for (const selector of selectorList) { - try { - debugLog(`Attempting to click: ${selector}`); - await page.waitForSelector(selector, { visible: true, timeout }); - await delay(randomDelay()); - await page.click(selector); + async function clickByText(page, text, elementType = 'button') { + const elements = await page.$$(elementType); + for (const element of elements) { + const elementText = await page.evaluate(el => el.textContent?.trim(), element); + if (elementText?.toLowerCase() === text.toLowerCase()) { + await element.click(); return true; - } catch (e) { - debugLog(`Click failed for ${selector}: ${e.message}`); } } return false; } - - async function clickByText(page, text, tag = '*', timeout = 5000) { + async function safeClick(page, selector, timeout = 10000) { try { - await page.waitForFunction( - (text, tag) => { - const elements = Array.from(document.querySelectorAll(tag)); - return elements.some(el => - el.textContent?.trim().toLowerCase().includes(text.toLowerCase()) - ); - }, - { timeout }, - text, - tag - ); - - const elements = await page.$$(tag); - for (const element of elements) { - const elementText = await page.evaluate(el => el.textContent?.trim(), element); - if (elementText?.toLowerCase().includes(text.toLowerCase())) { - await delay(randomDelay()); - await element.click(); - return true; - } + await page.waitForSelector(selector, { visible: true, timeout }); + await delay(500); + const element = await page.$(selector); + if (element) { + await element.click(); + return true; } - } catch (e) { - debugLog(`Text click failed for "${text}": ${e.message}`); - } + } catch {} return false; } - - async function typeWithHumanDelay(page, selector, text) { - await page.focus(selector); - await delay(randomDelay()); - for (const char of text) { - await page.keyboard.type(char, { delay: Math.random() * 50 + 50 }); - if (Math.random() > 0.9) await delay(randomDelay()); - } - } - - async function handleSpotifyAuth(page) { - // Step 1: Initial navigation - console.log('🌐 Navigating to Spotify auth page...'); - await page.goto(process.env.AUTH_URL, { waitUntil: 'networkidle2' }); - - // Check if already authorized - if (page.url().includes('callback?code=')) { - console.log('✅ Already authenticated'); - return true; - } - - // Step 2: Handle cookies - if (await safeClick(page, ['#onetrust-accept-btn-handler', '.ot-sdk-row button']) || - await clickByText(page, 'accept cookies')) { - console.log('🍪 Accepted cookies'); - await delay(2000); - } - - // Step 3: Email entry - console.log('📧 Entering email...'); - await typeWithHumanDelay(page, 'input[type="email"], #login-username', process.env.SPOTIFY_USERNAME); - - if (!(await safeClick(page, ['button[data-testid="login-button"]', 'button[type="submit"]']) || - await clickByText(page, 'continue'))) { - throw new AuthError('Continue button not found', ...await saveDebugArtifacts(page, 'continue-error')); - } - await delay(3000); - - // Step 4: Handle OTP/password choice - const needsPassword = await page.evaluate(() => - Array.from(document.querySelectorAll('*')) - .some(el => el.textContent?.includes('6-digit code')) - ); - - if (needsPassword) { - console.log('🔄 Switching to password login...'); - if (!(await clickByText(page, 'log in with a password') || - await safeClick(page, ['button[data-encore-id="buttonTertiary"]', '.auth-footer button']))) { - throw new AuthError('Password login option not found', ...await saveDebugArtifacts(page, 'password-switch-error')); - } - await delay(3000); - } - - // Step 5: Password entry - console.log('🔑 Entering password...'); - await typeWithHumanDelay(page, 'input[type="password"], #login-password', process.env.SPOTIFY_PASSWORD); - - if (!(await safeClick(page, ['button[data-testid="login-button"]', '#login-button']) || - await clickByText(page, 'log in'))) { - throw new AuthError('Login button not found', ...await saveDebugArtifacts(page, 'login-error')); - } - await delay(5000); - - // Step 6: Handle authorization - let attempts = 0; - while (attempts < config.maxRetries && !page.url().includes('callback?code=')) { - attempts++; - console.log(`🔄 Authorization attempt ${attempts}/${config.maxRetries}`); - - if (await safeClick(page, ['#auth-accept', 'button[data-testid="auth-accept"]']) || - await clickByText(page, 'agree')) { - console.log('✅ Clicked authorization button'); - await delay(3000); - } - - // Check for errors - const error = await page.evaluate(() => { - const errorEl = document.querySelector('[role="alert"], .error, .error-message'); - return errorEl?.textContent?.trim(); - }); - - if (error) { - throw new AuthError(`Spotify error: ${error}`, ...await saveDebugArtifacts(page, 'auth-error')); - } - - await delay(2000); - } - - return page.url().includes('callback?code='); - } - - async function run() { + (async () => { const browser = await puppeteer.launch({ - headless: 'new', - args: [ - '--no-sandbox', - '--disable-setuid-sandbox', - '--disable-dev-shm-usage', - '--disable-accelerated-2d-canvas', - '--disable-gpu' - ], + headless: true, + args: ['--no-sandbox', '--disable-setuid-sandbox'], defaultViewport: { width: 1280, height: 800 } }); - const page = await browser.newPage(); - await page.setUserAgent(config.userAgent); - page.setDefaultTimeout(config.timeout); - + await page.setUserAgent('Mozilla/5.0'); try { - const success = await handleSpotifyAuth(page); - if (!success) { - throw new AuthError( - `Authorization failed after ${config.maxRetries} attempts`, - ...await saveDebugArtifacts(page, 'final-error') - ); + console.log("🌐 Navigating to Spotify Auth URL..."); + await page.goto(process.env.AUTH_URL, { waitUntil: 'networkidle2', timeout: 60000 }); + console.log("📍 Current page:", page.url()); + await delay(3000); + if (page.url().includes('accounts.google.com')) { + throw new Error("❌ Redirected to Google login instead of Spotify login."); } - - console.log('🎉 Successfully authenticated with Spotify!'); + console.log("📧 Entering email..."); + await page.waitForSelector('input[type="email"], #login-username', { visible: true, timeout: 15000 }); + await typeWithHumanDelay(page, 'input[type="email"], #login-username', process.env.SPOTIFY_USERNAME); + await delay(1000); + console.log("➡️ Clicking Continue..."); + const continueClicked = await clickByText(page, 'Continue') || + await safeClick(page, 'button[data-testid="login-button"]') || + await safeClick(page, 'button[type="submit"]'); + if (!continueClicked) throw new Error("❌ Continue button not found."); + await delay(4000); + const otpDetected = await page.evaluate(() => + Array.from(document.querySelectorAll('*')).some(el => + el.textContent?.toLowerCase().includes('6-digit code') + ) + ); + if (otpDetected) { + console.log("🔁 Clicking 'Log in with a password'..."); + const clicked = await clickByText(page, 'Log in with a password') || + await safeClick(page, 'button[data-encore-id="buttonTertiary"]'); + if (!clicked) throw new Error("❌ Could not find 'Log in with a password' option."); + await delay(3000); + } + console.log("🔑 Entering password..."); + await page.waitForSelector('input[type="password"]', { visible: true, timeout: 10000 }); + await typeWithHumanDelay(page, 'input[type="password"]', process.env.SPOTIFY_PASSWORD); + await delay(1500); + console.log("🚀 Submitting login..."); + const loginClicked = await safeClick(page, '#login-button') || + await safeClick(page, 'button[data-testid="login-button"]') || + await safeClick(page, 'button[data-encore-id="buttonPrimary"]') || + await clickByText(page, 'Log In'); + if (!loginClicked) throw new Error("❌ Login button not found."); + await delay(5000); + const authClicked = await safeClick(page, '#auth-accept') || await clickByText(page, 'Agree'); + if (authClicked) { + console.log("✅ Authorization accepted."); + await page.waitForNavigation({ waitUntil: 'networkidle0', timeout: 15000 }); + } + const finalURL = page.url(); + if (!finalURL.includes('callback?code=')) { + throw new Error(`❌ Authorization failed. Final URL: ${finalURL}`); + } + console.log("🎉 Spotify authorization successful!"); + } catch (error) { + console.error('💥 Error during Spotify login:', error.message); + await page.screenshot({ path: 'spotify_error.png', fullPage: true }); + await fs.writeFile('error_debug.html', await page.content()); + throw error; } finally { await browser.close(); } - } - - run().catch(async (error) => { - console.error('❌ Authentication failed:', error.message); - if (error.screenshotPath) { - console.log(`📸 Screenshot saved to: ${error.screenshotPath}`); - } - if (error.htmlPath) { - console.log(`📄 HTML saved to: ${error.htmlPath}`); - } - process.exit(1); - }); + })(); EOF - - name: Run Terraform + - name: Run Terraform Apply run: | terraform init - terraform apply -var="spotify_api_key=${{ env.SPOTIFY_API_KEY }}" -auto-approve + terraform apply -var="SPOTIFY_API_KEY=${{ env.SPOTIFY_API_KEY }}" -auto-approve - - name: Upload debug artifacts + - name: Upload debug files (on failure) if: failure() uses: actions/upload-artifact@v4 with: - name: auth-debug + name: spotify-debug path: | - *.png - *.html - proxy.log \ No newline at end of file + spotify_error.png + error_debug.html + proxy.log From 029e52772b5158163d95afeb6eb491590d9b7094 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Tue, 24 Jun 2025 13:07:17 +0530 Subject: [PATCH 31/48] new one --- .github/workflows/tf-apply.yaml | 81 +++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index fe87290..cbf39b4 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -8,8 +8,8 @@ on: jobs: spotify-auth-and-apply: runs-on: ubuntu-latest - name: Apply - timeout-minutes: 10 + name: Authenticate and Apply + timeout-minutes: 15 steps: - name: Checkout repository @@ -20,7 +20,7 @@ jobs: with: go-version: '1.21' - - name: Install patched spotify_auth_proxy + - name: Install patched spotify_auth_proxy from fork run: | echo "$HOME/go/bin" >> $GITHUB_PATH git clone -b fix/base-url-127 https://github.com/gauravslnk/terraform-provider-spotify @@ -31,11 +31,12 @@ jobs: exit 1 fi - - name: Run auth proxy + - name: Run spotify_auth_proxy env: SPOTIFY_CLIENT_ID: ${{ secrets.SPOTIFY_CLIENT_ID }} SPOTIFY_CLIENT_SECRET: ${{ secrets.SPOTIFY_CLIENT_SECRET }} SPOTIFY_REDIRECT_URI: ${{ secrets.SPOTIFY_REDIRECT_URI }} + SPOTIFY_PROXY_BASE_URI: http://127.0.0.1:8080 run: | spotify_auth_proxy --port 8080 > proxy.log 2>&1 & echo "Waiting for auth URL..." @@ -58,11 +59,12 @@ jobs: with: node-version: '20' - - name: Install dependencies + - name: Install Node dependencies run: npm install puppeteer@22.8.2 fs-extra@11 - name: Perform Spotify login and authorization env: + AUTH_URL: ${{ env.AUTH_URL }} SPOTIFY_USERNAME: ${{ secrets.SPOTIFY_USERNAME }} SPOTIFY_PASSWORD: ${{ secrets.SPOTIFY_PASSWORD }} run: | @@ -77,12 +79,12 @@ jobs: if (Math.random() > 0.8) await delay(randomDelay(50, 150)); } } - async function clickByText(page, text, elementType = 'button') { - const elements = await page.$$(elementType); - for (const element of elements) { - const elementText = await page.evaluate(el => el.textContent?.trim(), element); - if (elementText?.toLowerCase() === text.toLowerCase()) { - await element.click(); + async function clickByText(page, text, tag = '*') { + const elements = await page.$$(tag); + for (const el of elements) { + const content = await page.evaluate(el => el.textContent?.trim(), el); + if (content?.toLowerCase() === text.toLowerCase()) { + await el.click(); return true; } } @@ -100,6 +102,7 @@ jobs: } catch {} return false; } + (async () => { const browser = await puppeteer.launch({ headless: true, @@ -108,74 +111,86 @@ jobs: }); const page = await browser.newPage(); await page.setUserAgent('Mozilla/5.0'); + try { console.log("🌐 Navigating to Spotify Auth URL..."); await page.goto(process.env.AUTH_URL, { waitUntil: 'networkidle2', timeout: 60000 }); console.log("📍 Current page:", page.url()); - await delay(3000); + if (page.url().includes('accounts.google.com')) { throw new Error("❌ Redirected to Google login instead of Spotify login."); } + console.log("📧 Entering email..."); await page.waitForSelector('input[type="email"], #login-username', { visible: true, timeout: 15000 }); await typeWithHumanDelay(page, 'input[type="email"], #login-username', process.env.SPOTIFY_USERNAME); await delay(1000); + console.log("➡️ Clicking Continue..."); - const continueClicked = await clickByText(page, 'Continue') || - await safeClick(page, 'button[data-testid="login-button"]') || - await safeClick(page, 'button[type="submit"]'); - if (!continueClicked) throw new Error("❌ Continue button not found."); - await delay(4000); + const continued = await clickByText(page, 'Continue') || + await safeClick(page, 'button[data-testid="login-button"]') || + await safeClick(page, 'button[type="submit"]'); + if (!continued) throw new Error("❌ Continue button not found."); + + await delay(3000); + const otpDetected = await page.evaluate(() => Array.from(document.querySelectorAll('*')).some(el => el.textContent?.toLowerCase().includes('6-digit code') ) ); + if (otpDetected) { - console.log("🔁 Clicking 'Log in with a password'..."); + console.log("🔁 Switching to password login..."); const clicked = await clickByText(page, 'Log in with a password') || await safeClick(page, 'button[data-encore-id="buttonTertiary"]'); - if (!clicked) throw new Error("❌ Could not find 'Log in with a password' option."); + if (!clicked) throw new Error("❌ Could not find 'Log in with a password'."); await delay(3000); } + console.log("🔑 Entering password..."); await page.waitForSelector('input[type="password"]', { visible: true, timeout: 10000 }); await typeWithHumanDelay(page, 'input[type="password"]', process.env.SPOTIFY_PASSWORD); - await delay(1500); - console.log("🚀 Submitting login..."); - const loginClicked = await safeClick(page, '#login-button') || - await safeClick(page, 'button[data-testid="login-button"]') || - await safeClick(page, 'button[data-encore-id="buttonPrimary"]') || - await clickByText(page, 'Log In'); - if (!loginClicked) throw new Error("❌ Login button not found."); + await delay(1000); + + console.log("🚀 Logging in..."); + const loggedIn = await safeClick(page, '#login-button') || + await safeClick(page, 'button[data-testid="login-button"]') || + await safeClick(page, 'button[data-encore-id="buttonPrimary"]') || + await clickByText(page, 'Log In'); + if (!loggedIn) throw new Error("❌ Login button not found."); + await delay(5000); - const authClicked = await safeClick(page, '#auth-accept') || await clickByText(page, 'Agree'); - if (authClicked) { - console.log("✅ Authorization accepted."); + + const authorized = await safeClick(page, '#auth-accept') || await clickByText(page, 'Agree'); + if (authorized) { + console.log("✅ Accepted authorization."); await page.waitForNavigation({ waitUntil: 'networkidle0', timeout: 15000 }); } + const finalURL = page.url(); if (!finalURL.includes('callback?code=')) { throw new Error(`❌ Authorization failed. Final URL: ${finalURL}`); } + console.log("🎉 Spotify authorization successful!"); } catch (error) { - console.error('💥 Error during Spotify login:', error.message); + console.error("💥 Error during login:", error.message); await page.screenshot({ path: 'spotify_error.png', fullPage: true }); await fs.writeFile('error_debug.html', await page.content()); - throw error; + process.exit(1); } finally { await browser.close(); } })(); EOF - - name: Run Terraform Apply + - name: Run Terraform run: | terraform init terraform apply -var="SPOTIFY_API_KEY=${{ env.SPOTIFY_API_KEY }}" -auto-approve - - name: Upload debug files (on failure) + - name: Upload debug artifacts if: failure() uses: actions/upload-artifact@v4 with: From b8250f3264c52a5e5b6cee9f5ddadd38dfb8df76 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Tue, 24 Jun 2025 13:12:07 +0530 Subject: [PATCH 32/48] terraform setup --- .github/workflows/tf-apply.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index cbf39b4..81cd827 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -185,10 +185,10 @@ jobs: })(); EOF - - name: Run Terraform - run: | - terraform init - terraform apply -var="SPOTIFY_API_KEY=${{ env.SPOTIFY_API_KEY }}" -auto-approve + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: 1.12.2 - name: Upload debug artifacts if: failure() From 9281e15dd2f3ce47ec43e73f7e59f583c3bdb290 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Tue, 24 Jun 2025 13:29:46 +0530 Subject: [PATCH 33/48] final-check --- main.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/main.tf b/main.tf index 1ef250a..0b70400 100644 --- a/main.tf +++ b/main.tf @@ -30,6 +30,7 @@ data "spotify_search_track" "Parcels" { limit = 7 } + data "spotify_track" "Zenith" { url = "https://open.spotify.com/track/0qKX14YZHptDWiEN0CgxGz?si=174ddb3f25414e2c" } From 552ad4e0980ae32784cefe2960d8e902035c5ffb Mon Sep 17 00:00:00 2001 From: Gaurav Solanki Date: Wed, 25 Jun 2025 18:27:22 +0530 Subject: [PATCH 34/48] Update main.tf --- main.tf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main.tf b/main.tf index 0b70400..7136955 100644 --- a/main.tf +++ b/main.tf @@ -8,7 +8,8 @@ terraform { } provider "spotify" { - api_key = var.SPOTIFY_API_KEY + api_key = var.SPOTIFY_API_KEY + auth_server = "http://127.0.0.1:8080" } data "spotify_search_track" "RAM" { From 85b05e52c62614d04e5f0dc89c44e07703a5d57c Mon Sep 17 00:00:00 2001 From: Gaurav Solanki Date: Wed, 25 Jun 2025 18:36:17 +0530 Subject: [PATCH 35/48] Update tf-apply.yaml --- .github/workflows/tf-apply.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index 81cd827..ebcc2a2 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -190,6 +190,22 @@ jobs: with: terraform_version: 1.12.2 + - name: Terraform Init + run: terraform init + + - name: Terraform Validate + run: terraform validate + + - name: Terraform Plan + env: + SPOTIFY_API_KEY: ${{ env.SPOTIFY_API_KEY }} + run: terraform plan + + - name: Terraform Apply + env: + SPOTIFY_API_KEY: ${{ env.SPOTIFY_API_KEY }} + run: terraform apply -auto-approve + - name: Upload debug artifacts if: failure() uses: actions/upload-artifact@v4 From e78618ef3efe579f8cbb93adbf18bf7dc61f14c1 Mon Sep 17 00:00:00 2001 From: Gaurav Solanki Date: Wed, 25 Jun 2025 19:00:39 +0530 Subject: [PATCH 36/48] pass api dynamically --- .github/workflows/tf-apply.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index ebcc2a2..4e3ec25 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -199,12 +199,12 @@ jobs: - name: Terraform Plan env: SPOTIFY_API_KEY: ${{ env.SPOTIFY_API_KEY }} - run: terraform plan + run: terraform plan -var "SPOTIFY_API_KEY=${SPOTIFY_API_KEY}" - name: Terraform Apply env: SPOTIFY_API_KEY: ${{ env.SPOTIFY_API_KEY }} - run: terraform apply -auto-approve + run: terraform apply -auto-approve -var "SPOTIFY_API_KEY=${SPOTIFY_API_KEY}" - name: Upload debug artifacts if: failure() From de50a96decb693c9d2fa329ddc91c92f5fbd6e99 Mon Sep 17 00:00:00 2001 From: Gaurav Solanki Date: Wed, 25 Jun 2025 19:53:48 +0530 Subject: [PATCH 37/48] hope this works --- .github/workflows/tf-apply.yaml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index 4e3ec25..e5e4e76 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -185,6 +185,9 @@ jobs: })(); EOF + - name: Install jq + run: sudo apt-get update && sudo apt-get install -y jq + - name: Setup Terraform uses: hashicorp/setup-terraform@v3 with: @@ -193,6 +196,24 @@ jobs: - name: Terraform Init run: terraform init + - name: Clean Terraform State if Playlist Invalid + env: + SPOTIFY_API_KEY: ${{ env.SPOTIFY_API_KEY }} + run: | + if grep -q "spotify_playlist" terraform.tfstate; then + echo "Checking playlist ownership..." + PLAYLIST_ID=$(jq -r '.resources[] | select(.type=="spotify_playlist") | .instances[0].attributes.id' terraform.tfstate) + RESPONSE=$(curl -s -H "Authorization: Bearer $SPOTIFY_API_KEY" https://api.spotify.com/v1/playlists/$PLAYLIST_ID) + if echo "$RESPONSE" | grep -q "\"error\""; then + echo "❌ Playlist does not exist or does not belong to current user. Resetting state..." + rm -f terraform.tfstate terraform.tfstate.backup + else + echo "✅ Playlist exists and belongs to user. Continuing..." + fi + else + echo "ℹ️ No playlist in state. Continuing..." + fi + - name: Terraform Validate run: terraform validate From d9a0a8b84162b4b582085d7be70472fa98b56d4b Mon Sep 17 00:00:00 2001 From: Gourav Date: Fri, 4 Jul 2025 09:58:05 +0530 Subject: [PATCH 38/48] add song --- main.tf | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/main.tf b/main.tf index 7136955..75408ab 100644 --- a/main.tf +++ b/main.tf @@ -60,6 +60,10 @@ data "spotify_track" "supermassive_black_hole" { url = "https://open.spotify.com/track/3lPr8ghNDBLc2uZovNyLs9?si=6dac397517d3427f" } +data "spotify_track" "Get_Lucky" { + url = "https://open.spotify.com/track/69kOkLUCkxIZYexIgSG8rq?si=945ac1a66e3e4033" +} + resource "spotify_playlist" "playlist" { @@ -79,6 +83,7 @@ resource "spotify_playlist" "playlist" { data.spotify_track.instant_crush.id, data.spotify_track.nightcall.id, data.spotify_track.lady_hear_me_tonight.id, - data.spotify_track.supermassive_black_hole.id + data.spotify_track.supermassive_black_hole.id, + data.spotify_track.Get_Lucky.id ]) } From 1ca0e778db89e7fe520212e09e06620249c545e1 Mon Sep 17 00:00:00 2001 From: Gourav Date: Fri, 4 Jul 2025 11:05:00 +0530 Subject: [PATCH 39/48] add starlight --- main.tf | 7 ++++++- terraform.tfstate | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/main.tf b/main.tf index 75408ab..20fcda5 100644 --- a/main.tf +++ b/main.tf @@ -64,6 +64,10 @@ data "spotify_track" "Get_Lucky" { url = "https://open.spotify.com/track/69kOkLUCkxIZYexIgSG8rq?si=945ac1a66e3e4033" } +data "spotify_search" "starlight" { + url = "https://open.spotify.com/track/5luWJxS799LLp2e88RffUx?si=ac548be34f354912" +} + resource "spotify_playlist" "playlist" { @@ -84,6 +88,7 @@ resource "spotify_playlist" "playlist" { data.spotify_track.nightcall.id, data.spotify_track.lady_hear_me_tonight.id, data.spotify_track.supermassive_black_hole.id, - data.spotify_track.Get_Lucky.id + data.spotify_track.Get_Lucky.id, + data.spotify_search.starlight[*].id ]) } diff --git a/terraform.tfstate b/terraform.tfstate index 957cd2f..0eece5c 100644 --- a/terraform.tfstate +++ b/terraform.tfstate @@ -451,7 +451,7 @@ "schema_version": 0, "attributes": { "description": "Wishing you make the nicest, most Randomly Accessible Memories this CodeR̶A̶M̶Jam :)", - "id": "05jajLNgeqfSxYeIG1KABy", + "id": "2F8Qx9ioiQWDBDHx0vdMVW", "name": "The CodeJam Playlist", "public": true, "snapshot_id": "AAAAAf9HUgM79VVH4LEpfe0PP/VVPudk", From 8433669ed91044e36f1a1e20872b76eadaeb846c Mon Sep 17 00:00:00 2001 From: Gourav Date: Fri, 4 Jul 2025 11:09:25 +0530 Subject: [PATCH 40/48] add starlight --- main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.tf b/main.tf index 20fcda5..ba7312f 100644 --- a/main.tf +++ b/main.tf @@ -64,7 +64,7 @@ data "spotify_track" "Get_Lucky" { url = "https://open.spotify.com/track/69kOkLUCkxIZYexIgSG8rq?si=945ac1a66e3e4033" } -data "spotify_search" "starlight" { +data "spotify_track" "starlight" { url = "https://open.spotify.com/track/5luWJxS799LLp2e88RffUx?si=ac548be34f354912" } From 6f57181600f8fb1b75bfe79c7608e28b35aadbef Mon Sep 17 00:00:00 2001 From: Gourav Date: Fri, 4 Jul 2025 11:11:47 +0530 Subject: [PATCH 41/48] what is your problem --- main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.tf b/main.tf index ba7312f..7c8a1e9 100644 --- a/main.tf +++ b/main.tf @@ -89,6 +89,6 @@ resource "spotify_playlist" "playlist" { data.spotify_track.lady_hear_me_tonight.id, data.spotify_track.supermassive_black_hole.id, data.spotify_track.Get_Lucky.id, - data.spotify_search.starlight[*].id + data.spotify_track.starlight.id ]) } From d4d4384285ae450e473cff712d79b17c79eefeec Mon Sep 17 00:00:00 2001 From: Gourav Date: Fri, 4 Jul 2025 11:26:02 +0530 Subject: [PATCH 42/48] remove current tfstate --- terraform.tfstate | 514 --------------------------------------- terraform.tfstate.backup | 514 --------------------------------------- 2 files changed, 1028 deletions(-) delete mode 100644 terraform.tfstate delete mode 100644 terraform.tfstate.backup diff --git a/terraform.tfstate b/terraform.tfstate deleted file mode 100644 index 0eece5c..0000000 --- a/terraform.tfstate +++ /dev/null @@ -1,514 +0,0 @@ -{ - "version": 4, - "terraform_version": "1.9.1", - "serial": 2, - "lineage": "a371cb0b-d2cc-96ec-5077-6a90be280515", - "outputs": {}, - "resources": [ - { - "mode": "data", - "type": "spotify_search_track", - "name": "LImperatrice", - "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", - "instances": [ - { - "schema_version": 0, - "attributes": { - "album": null, - "artist": "L'Impératrice", - "explicit": true, - "id": "1720680020", - "limit": 10, - "name": null, - "tracks": [ - { - "album": "6qnJcNAInQ2CAtC4zWG8HT", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU", - "2HGAZDmVV3GAH4XFAZdvSG" - ], - "id": "49PyCCLOdJi0jHkGyyY2vv", - "name": "Sonate Pacifique" - }, - { - "album": "346ZDnGgJudDau17EEyWWA", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "2La21GqU4fKTQLcfLxTeoz", - "name": "Agitations tropicales" - }, - { - "album": "41Ht5x3AgpMVmoFoIzaUPO", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "7nZ9CzhiFRPhOQCn7eDSnn", - "name": "Vanille fraise" - }, - { - "album": "346ZDnGgJudDau17EEyWWA", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "6rbbDK18oqK3MxHGQhpmn2", - "name": "La lune" - }, - { - "album": "5pk9cqTDktuytdBNidkke4", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU", - "3lLHpTOJ11tWiUNGYN14gt" - ], - "id": "5LInOGDHqrgetnMlnvaDNq", - "name": "Everything Eventually Ends" - }, - { - "album": "7gE23KRzNbXeB6nZmQVqa3", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "3pr0y5h4MkBngy6gyozLDV", - "name": "Voodoo?" - }, - { - "album": "7gE23KRzNbXeB6nZmQVqa3", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "00mO8JKk2wQ7VqdPcY1r4O", - "name": "Peur des filles" - }, - { - "album": "346ZDnGgJudDau17EEyWWA", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "0UODoj6stYv542c7NB9MNd", - "name": "Le départ" - }, - { - "album": "1I2vgUWhcovQKIgQK0jHPB", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU", - "4NZvixzsSefsNiIqXn0NDe" - ], - "id": "6pLpm17TjgukQbWU5F6fwo", - "name": "Any Way (feat. Maggie Rogers)" - }, - { - "album": "7Fbph706oR8vaTPcYYvp9n", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "49uLH3xvQLd4I4pCQacGH8", - "name": "Love from the Other Side" - } - ], - "year": null - }, - "sensitive_attributes": [] - } - ] - }, - { - "mode": "data", - "type": "spotify_search_track", - "name": "Odyssee", - "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", - "instances": [ - { - "schema_version": 0, - "attributes": { - "album": "Odyssee EP", - "artist": null, - "explicit": true, - "id": "1720680020", - "limit": 10, - "name": null, - "tracks": [ - { - "album": "346ZDnGgJudDau17EEyWWA", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "3vDJUlhCEqZLyRQVbsWsEI", - "name": "Odyssée" - }, - { - "album": "346ZDnGgJudDau17EEyWWA", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "2La21GqU4fKTQLcfLxTeoz", - "name": "Agitations tropicales" - }, - { - "album": "346ZDnGgJudDau17EEyWWA", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "6rbbDK18oqK3MxHGQhpmn2", - "name": "La lune" - }, - { - "album": "346ZDnGgJudDau17EEyWWA", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "1dm6Sss1w9auE5TXM2zLvg", - "name": "Épilogue" - }, - { - "album": "346ZDnGgJudDau17EEyWWA", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "0UODoj6stYv542c7NB9MNd", - "name": "Le départ" - }, - { - "album": "346ZDnGgJudDau17EEyWWA", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "76fdtNvwn6hkOmBvgOom7L", - "name": "Parfum thérémine" - }, - { - "album": "4J8qpKJYBNSzT5rJseBtRj", - "artists": [ - "5YIaZ0ICxZyNEsATQRsxRk" - ], - "id": "1HFGQbPcif9c7e31GEYjW3", - "name": "L’épreuve" - }, - { - "album": "3WynrtGVOjSMhmJbF1c3N0", - "artists": [ - "1s5QJK3dJhK5QmlTYgEV7w" - ], - "id": "4Ht7gUwE3s8vqXDbegwn4Q", - "name": "Epilogue" - }, - { - "album": "0vU5QAZBmYfD6dkuAnalJy", - "artists": [ - "24WRvSDW3TaWsPvJ9JwDTx" - ], - "id": "6Zf5CXbvYc5o2Wi2Nv5hcw", - "name": "Epitaph" - }, - { - "album": "6Ul63eiW8REW6SCFmq8SDR", - "artists": [ - "6uTAGzcapoHq5Y4SMVPcZi" - ], - "id": "1PQiPJP7w21qTTp7zpihcF", - "name": "Epilog" - } - ], - "year": null - }, - "sensitive_attributes": [] - } - ] - }, - { - "mode": "data", - "type": "spotify_search_track", - "name": "Parcels", - "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", - "instances": [ - { - "schema_version": 0, - "attributes": { - "album": null, - "artist": "Parcels", - "explicit": true, - "id": "1720680020", - "limit": 8, - "name": null, - "tracks": [ - { - "album": "5v7PsESglCFeVcb7wNEWIW", - "artists": [ - "3oKRxpszQKUjjaHz388fVA" - ], - "id": "66tkDkPsznE5zIHNt4QkXB", - "name": "Tieduprightnow" - }, - { - "album": "0ugE0qpkFDNLYFmNiUOOKA", - "artists": [ - "3oKRxpszQKUjjaHz388fVA" - ], - "id": "0rdWkO7ncrh1lrJGDmLq9L", - "name": "Gamesofluck" - }, - { - "album": "0cIynhird3mHUUs71R7zsj", - "artists": [ - "3oKRxpszQKUjjaHz388fVA" - ], - "id": "0hhXziDUO0wNYPsstDQWN6", - "name": "Overnight" - }, - { - "album": "6tNAnVG8xrmMbINuWutrNy", - "artists": [ - "3oKRxpszQKUjjaHz388fVA" - ], - "id": "1qtiESAzfGMw3YqJvI97ki", - "name": "Lightenup" - }, - { - "album": "6tNAnVG8xrmMbINuWutrNy", - "artists": [ - "3oKRxpszQKUjjaHz388fVA" - ], - "id": "1Avnd5xLg7HmaNofmYk7RQ", - "name": "Withorwithout" - }, - { - "album": "6tNAnVG8xrmMbINuWutrNy", - "artists": [ - "3oKRxpszQKUjjaHz388fVA" - ], - "id": "2IFaUS63AIAr9P46W1lEwt", - "name": "IknowhowIfeel" - }, - { - "album": "4ptXy3D8sAopLdffBms1nG", - "artists": [ - "3oKRxpszQKUjjaHz388fVA" - ], - "id": "7z5R6cbZpB4njz080aA6u2", - "name": "Hideout" - }, - { - "album": "7Ih98kmIPJCsXcgrikcmoq", - "artists": [ - "7c5qu1gNlg8jWDzzmlp89O", - "3oKRxpszQKUjjaHz388fVA" - ], - "id": "3uvALUobqjZvTGAh6737Mb", - "name": "Stumble" - } - ], - "year": null - }, - "sensitive_attributes": [] - } - ] - }, - { - "mode": "data", - "type": "spotify_search_track", - "name": "RAM", - "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", - "instances": [ - { - "schema_version": 0, - "attributes": { - "album": "Random Access Memories (10th Anniversary Edition)", - "artist": null, - "explicit": true, - "id": "1720680020", - "limit": 8, - "name": null, - "tracks": [ - { - "album": "4mAhdh996uW5SnnFKXUmC0", - "artists": [ - "4tZwfgrHOc3mvqYlEYSvVi" - ], - "id": "6iZRqJjpbOVEv4mFTpqg1d", - "name": "Horizon (Japan CD)" - }, - { - "album": "4mAhdh996uW5SnnFKXUmC0", - "artists": [ - "4tZwfgrHOc3mvqYlEYSvVi" - ], - "id": "2eXJRM1lQJ591AStNA26Yx", - "name": "Giorgio by Moroder" - }, - { - "album": "4mAhdh996uW5SnnFKXUmC0", - "artists": [ - "4tZwfgrHOc3mvqYlEYSvVi", - "1rAv1GhTQ2rmG94p9lU3rB" - ], - "id": "0f8GgsD1lDtRBAS5GEDKgg", - "name": "Instant Crush (feat. Julian Casablancas)" - }, - { - "album": "4mAhdh996uW5SnnFKXUmC0", - "artists": [ - "4tZwfgrHOc3mvqYlEYSvVi" - ], - "id": "5aPrFctv3WJZyJ99Ld7leT", - "name": "Give Life Back to Music" - }, - { - "album": "4mAhdh996uW5SnnFKXUmC0", - "artists": [ - "4tZwfgrHOc3mvqYlEYSvVi" - ], - "id": "1iAsoOFV16Ax6OFKMq8sld", - "name": "Prime (2012 Unfinished)" - }, - { - "album": "4mAhdh996uW5SnnFKXUmC0", - "artists": [ - "4tZwfgrHOc3mvqYlEYSvVi", - "2RdwBSPQiwcmiDo9kixcl8" - ], - "id": "3PgYTZHsdWp4C21bQ8zxvM", - "name": "LYTD (Vocoder Tests) [feat. Pharrell Williams]" - }, - { - "album": "4mAhdh996uW5SnnFKXUmC0", - "artists": [ - "4tZwfgrHOc3mvqYlEYSvVi", - "2RdwBSPQiwcmiDo9kixcl8", - "3yDIp0kaq9EFKe07X1X2rz" - ], - "id": "2olIQt0rL0hOHau1SJ4xf2", - "name": "Get Lucky (feat. Pharrell Williams and Nile Rodgers)" - }, - { - "album": "4mAhdh996uW5SnnFKXUmC0", - "artists": [ - "4tZwfgrHOc3mvqYlEYSvVi", - "1rAv1GhTQ2rmG94p9lU3rB", - "4nUBBtLtzqZGpdiynTJbYJ" - ], - "id": "4r4fX4NZIqVIhGyDvERp0l", - "name": "Infinity Repeating (2013 Demo) [feat. Julian Casablancas+The Voidz]" - } - ], - "year": null - }, - "sensitive_attributes": [] - } - ] - }, - { - "mode": "data", - "type": "spotify_track", - "name": "A_View_To_Kill", - "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", - "instances": [ - { - "schema_version": 0, - "attributes": { - "album": "6qnJcNAInQ2CAtC4zWG8HT", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "7fN3QQtmCMkiczQ41IuhwK", - "name": "A View to a Kill", - "spotify_id": null, - "url": "https://open.spotify.com/track/7fN3QQtmCMkiczQ41IuhwK?si=d0e32a11da1f4c72" - }, - "sensitive_attributes": [] - } - ] - }, - { - "mode": "data", - "type": "spotify_track", - "name": "Zenith", - "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", - "instances": [ - { - "schema_version": 0, - "attributes": { - "album": "2Js9F9O6V6mDZTal95Iner", - "artists": [ - "0UF7XLthtbSF2Eur7559oV", - "6G7P1WNRTlg3FxiYaW0hYh", - "31XcLJj5SRtJ0smI0iV9g8" - ], - "id": "0qKX14YZHptDWiEN0CgxGz", - "name": "Zenith", - "spotify_id": null, - "url": "https://open.spotify.com/track/0qKX14YZHptDWiEN0CgxGz?si=174ddb3f25414e2c" - }, - "sensitive_attributes": [] - } - ] - }, - { - "mode": "managed", - "type": "spotify_playlist", - "name": "playlist", - "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", - "instances": [ - { - "schema_version": 0, - "attributes": { - "description": "Wishing you make the nicest, most Randomly Accessible Memories this CodeR̶A̶M̶Jam :)", - "id": "2F8Qx9ioiQWDBDHx0vdMVW", - "name": "The CodeJam Playlist", - "public": true, - "snapshot_id": "AAAAAf9HUgM79VVH4LEpfe0PP/VVPudk", - "tracks": [ - "6iZRqJjpbOVEv4mFTpqg1d", - "2eXJRM1lQJ591AStNA26Yx", - "0f8GgsD1lDtRBAS5GEDKgg", - "5aPrFctv3WJZyJ99Ld7leT", - "1iAsoOFV16Ax6OFKMq8sld", - "3PgYTZHsdWp4C21bQ8zxvM", - "2olIQt0rL0hOHau1SJ4xf2", - "4r4fX4NZIqVIhGyDvERp0l", - "49PyCCLOdJi0jHkGyyY2vv", - "2La21GqU4fKTQLcfLxTeoz", - "7nZ9CzhiFRPhOQCn7eDSnn", - "6rbbDK18oqK3MxHGQhpmn2", - "5LInOGDHqrgetnMlnvaDNq", - "3pr0y5h4MkBngy6gyozLDV", - "00mO8JKk2wQ7VqdPcY1r4O", - "0UODoj6stYv542c7NB9MNd", - "6pLpm17TjgukQbWU5F6fwo", - "49uLH3xvQLd4I4pCQacGH8", - "3vDJUlhCEqZLyRQVbsWsEI", - "2La21GqU4fKTQLcfLxTeoz", - "6rbbDK18oqK3MxHGQhpmn2", - "1dm6Sss1w9auE5TXM2zLvg", - "0UODoj6stYv542c7NB9MNd", - "76fdtNvwn6hkOmBvgOom7L", - "1HFGQbPcif9c7e31GEYjW3", - "4Ht7gUwE3s8vqXDbegwn4Q", - "6Zf5CXbvYc5o2Wi2Nv5hcw", - "1PQiPJP7w21qTTp7zpihcF", - "66tkDkPsznE5zIHNt4QkXB", - "0rdWkO7ncrh1lrJGDmLq9L", - "0hhXziDUO0wNYPsstDQWN6", - "1qtiESAzfGMw3YqJvI97ki", - "1Avnd5xLg7HmaNofmYk7RQ", - "2IFaUS63AIAr9P46W1lEwt", - "7z5R6cbZpB4njz080aA6u2", - "3uvALUobqjZvTGAh6737Mb", - "0qKX14YZHptDWiEN0CgxGz", - "7fN3QQtmCMkiczQ41IuhwK" - ] - }, - "sensitive_attributes": [], - "private": "bnVsbA==", - "dependencies": [ - "data.spotify_search_track.LImperatrice", - "data.spotify_search_track.Odyssee", - "data.spotify_search_track.Parcels", - "data.spotify_search_track.RAM", - "data.spotify_track.A_View_To_Kill", - "data.spotify_track.Zenith" - ] - } - ] - } - ], - "check_results": null -} diff --git a/terraform.tfstate.backup b/terraform.tfstate.backup deleted file mode 100644 index 4447b51..0000000 --- a/terraform.tfstate.backup +++ /dev/null @@ -1,514 +0,0 @@ -{ - "version": 4, - "terraform_version": "1.9.1", - "serial": 1, - "lineage": "a371cb0b-d2cc-96ec-5077-6a90be280515", - "outputs": {}, - "resources": [ - { - "mode": "data", - "type": "spotify_search_track", - "name": "LImperatrice", - "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", - "instances": [ - { - "schema_version": 0, - "attributes": { - "album": null, - "artist": "L'Impératrice", - "explicit": true, - "id": "1720680005", - "limit": 10, - "name": null, - "tracks": [ - { - "album": "6qnJcNAInQ2CAtC4zWG8HT", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU", - "2HGAZDmVV3GAH4XFAZdvSG" - ], - "id": "49PyCCLOdJi0jHkGyyY2vv", - "name": "Sonate Pacifique" - }, - { - "album": "346ZDnGgJudDau17EEyWWA", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "2La21GqU4fKTQLcfLxTeoz", - "name": "Agitations tropicales" - }, - { - "album": "41Ht5x3AgpMVmoFoIzaUPO", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "7nZ9CzhiFRPhOQCn7eDSnn", - "name": "Vanille fraise" - }, - { - "album": "346ZDnGgJudDau17EEyWWA", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "6rbbDK18oqK3MxHGQhpmn2", - "name": "La lune" - }, - { - "album": "5pk9cqTDktuytdBNidkke4", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU", - "3lLHpTOJ11tWiUNGYN14gt" - ], - "id": "5LInOGDHqrgetnMlnvaDNq", - "name": "Everything Eventually Ends" - }, - { - "album": "7gE23KRzNbXeB6nZmQVqa3", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "3pr0y5h4MkBngy6gyozLDV", - "name": "Voodoo?" - }, - { - "album": "7gE23KRzNbXeB6nZmQVqa3", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "00mO8JKk2wQ7VqdPcY1r4O", - "name": "Peur des filles" - }, - { - "album": "346ZDnGgJudDau17EEyWWA", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "0UODoj6stYv542c7NB9MNd", - "name": "Le départ" - }, - { - "album": "1I2vgUWhcovQKIgQK0jHPB", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU", - "4NZvixzsSefsNiIqXn0NDe" - ], - "id": "6pLpm17TjgukQbWU5F6fwo", - "name": "Any Way (feat. Maggie Rogers)" - }, - { - "album": "7Fbph706oR8vaTPcYYvp9n", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "49uLH3xvQLd4I4pCQacGH8", - "name": "Love from the Other Side" - } - ], - "year": null - }, - "sensitive_attributes": [] - } - ] - }, - { - "mode": "data", - "type": "spotify_search_track", - "name": "Odyssee", - "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", - "instances": [ - { - "schema_version": 0, - "attributes": { - "album": "Odyssee EP", - "artist": null, - "explicit": true, - "id": "1720680005", - "limit": 10, - "name": null, - "tracks": [ - { - "album": "346ZDnGgJudDau17EEyWWA", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "3vDJUlhCEqZLyRQVbsWsEI", - "name": "Odyssée" - }, - { - "album": "346ZDnGgJudDau17EEyWWA", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "2La21GqU4fKTQLcfLxTeoz", - "name": "Agitations tropicales" - }, - { - "album": "346ZDnGgJudDau17EEyWWA", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "6rbbDK18oqK3MxHGQhpmn2", - "name": "La lune" - }, - { - "album": "346ZDnGgJudDau17EEyWWA", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "1dm6Sss1w9auE5TXM2zLvg", - "name": "Épilogue" - }, - { - "album": "346ZDnGgJudDau17EEyWWA", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "0UODoj6stYv542c7NB9MNd", - "name": "Le départ" - }, - { - "album": "346ZDnGgJudDau17EEyWWA", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "76fdtNvwn6hkOmBvgOom7L", - "name": "Parfum thérémine" - }, - { - "album": "4J8qpKJYBNSzT5rJseBtRj", - "artists": [ - "5YIaZ0ICxZyNEsATQRsxRk" - ], - "id": "1HFGQbPcif9c7e31GEYjW3", - "name": "L’épreuve" - }, - { - "album": "3WynrtGVOjSMhmJbF1c3N0", - "artists": [ - "1s5QJK3dJhK5QmlTYgEV7w" - ], - "id": "4Ht7gUwE3s8vqXDbegwn4Q", - "name": "Epilogue" - }, - { - "album": "0vU5QAZBmYfD6dkuAnalJy", - "artists": [ - "24WRvSDW3TaWsPvJ9JwDTx" - ], - "id": "6Zf5CXbvYc5o2Wi2Nv5hcw", - "name": "Epitaph" - }, - { - "album": "6Ul63eiW8REW6SCFmq8SDR", - "artists": [ - "6uTAGzcapoHq5Y4SMVPcZi" - ], - "id": "1PQiPJP7w21qTTp7zpihcF", - "name": "Epilog" - } - ], - "year": null - }, - "sensitive_attributes": [] - } - ] - }, - { - "mode": "data", - "type": "spotify_search_track", - "name": "Parcels", - "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", - "instances": [ - { - "schema_version": 0, - "attributes": { - "album": null, - "artist": "Parcels", - "explicit": true, - "id": "1720680005", - "limit": 8, - "name": null, - "tracks": [ - { - "album": "5v7PsESglCFeVcb7wNEWIW", - "artists": [ - "3oKRxpszQKUjjaHz388fVA" - ], - "id": "66tkDkPsznE5zIHNt4QkXB", - "name": "Tieduprightnow" - }, - { - "album": "0ugE0qpkFDNLYFmNiUOOKA", - "artists": [ - "3oKRxpszQKUjjaHz388fVA" - ], - "id": "0rdWkO7ncrh1lrJGDmLq9L", - "name": "Gamesofluck" - }, - { - "album": "0cIynhird3mHUUs71R7zsj", - "artists": [ - "3oKRxpszQKUjjaHz388fVA" - ], - "id": "0hhXziDUO0wNYPsstDQWN6", - "name": "Overnight" - }, - { - "album": "6tNAnVG8xrmMbINuWutrNy", - "artists": [ - "3oKRxpszQKUjjaHz388fVA" - ], - "id": "1qtiESAzfGMw3YqJvI97ki", - "name": "Lightenup" - }, - { - "album": "6tNAnVG8xrmMbINuWutrNy", - "artists": [ - "3oKRxpszQKUjjaHz388fVA" - ], - "id": "1Avnd5xLg7HmaNofmYk7RQ", - "name": "Withorwithout" - }, - { - "album": "6tNAnVG8xrmMbINuWutrNy", - "artists": [ - "3oKRxpszQKUjjaHz388fVA" - ], - "id": "2IFaUS63AIAr9P46W1lEwt", - "name": "IknowhowIfeel" - }, - { - "album": "4ptXy3D8sAopLdffBms1nG", - "artists": [ - "3oKRxpszQKUjjaHz388fVA" - ], - "id": "7z5R6cbZpB4njz080aA6u2", - "name": "Hideout" - }, - { - "album": "7Ih98kmIPJCsXcgrikcmoq", - "artists": [ - "7c5qu1gNlg8jWDzzmlp89O", - "3oKRxpszQKUjjaHz388fVA" - ], - "id": "3uvALUobqjZvTGAh6737Mb", - "name": "Stumble" - } - ], - "year": null - }, - "sensitive_attributes": [] - } - ] - }, - { - "mode": "data", - "type": "spotify_search_track", - "name": "RAM", - "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", - "instances": [ - { - "schema_version": 0, - "attributes": { - "album": "Random Access Memories (10th Anniversary Edition)", - "artist": null, - "explicit": true, - "id": "1720680005", - "limit": 8, - "name": null, - "tracks": [ - { - "album": "4mAhdh996uW5SnnFKXUmC0", - "artists": [ - "4tZwfgrHOc3mvqYlEYSvVi" - ], - "id": "6iZRqJjpbOVEv4mFTpqg1d", - "name": "Horizon (Japan CD)" - }, - { - "album": "4mAhdh996uW5SnnFKXUmC0", - "artists": [ - "4tZwfgrHOc3mvqYlEYSvVi" - ], - "id": "2eXJRM1lQJ591AStNA26Yx", - "name": "Giorgio by Moroder" - }, - { - "album": "4mAhdh996uW5SnnFKXUmC0", - "artists": [ - "4tZwfgrHOc3mvqYlEYSvVi", - "1rAv1GhTQ2rmG94p9lU3rB" - ], - "id": "0f8GgsD1lDtRBAS5GEDKgg", - "name": "Instant Crush (feat. Julian Casablancas)" - }, - { - "album": "4mAhdh996uW5SnnFKXUmC0", - "artists": [ - "4tZwfgrHOc3mvqYlEYSvVi" - ], - "id": "5aPrFctv3WJZyJ99Ld7leT", - "name": "Give Life Back to Music" - }, - { - "album": "4mAhdh996uW5SnnFKXUmC0", - "artists": [ - "4tZwfgrHOc3mvqYlEYSvVi" - ], - "id": "1iAsoOFV16Ax6OFKMq8sld", - "name": "Prime (2012 Unfinished)" - }, - { - "album": "4mAhdh996uW5SnnFKXUmC0", - "artists": [ - "4tZwfgrHOc3mvqYlEYSvVi", - "2RdwBSPQiwcmiDo9kixcl8" - ], - "id": "3PgYTZHsdWp4C21bQ8zxvM", - "name": "LYTD (Vocoder Tests) [feat. Pharrell Williams]" - }, - { - "album": "4mAhdh996uW5SnnFKXUmC0", - "artists": [ - "4tZwfgrHOc3mvqYlEYSvVi", - "2RdwBSPQiwcmiDo9kixcl8", - "3yDIp0kaq9EFKe07X1X2rz" - ], - "id": "2olIQt0rL0hOHau1SJ4xf2", - "name": "Get Lucky (feat. Pharrell Williams and Nile Rodgers)" - }, - { - "album": "4mAhdh996uW5SnnFKXUmC0", - "artists": [ - "4tZwfgrHOc3mvqYlEYSvVi", - "1rAv1GhTQ2rmG94p9lU3rB", - "4nUBBtLtzqZGpdiynTJbYJ" - ], - "id": "4r4fX4NZIqVIhGyDvERp0l", - "name": "Infinity Repeating (2013 Demo) [feat. Julian Casablancas+The Voidz]" - } - ], - "year": null - }, - "sensitive_attributes": [] - } - ] - }, - { - "mode": "data", - "type": "spotify_track", - "name": "A_View_To_Kill", - "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", - "instances": [ - { - "schema_version": 0, - "attributes": { - "album": "6qnJcNAInQ2CAtC4zWG8HT", - "artists": [ - "4PwlsrN0t5mLN0C827cbEU" - ], - "id": "7fN3QQtmCMkiczQ41IuhwK", - "name": "A View to a Kill", - "spotify_id": null, - "url": "https://open.spotify.com/track/7fN3QQtmCMkiczQ41IuhwK?si=d0e32a11da1f4c72" - }, - "sensitive_attributes": [] - } - ] - }, - { - "mode": "data", - "type": "spotify_track", - "name": "Zenith", - "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", - "instances": [ - { - "schema_version": 0, - "attributes": { - "album": "2Js9F9O6V6mDZTal95Iner", - "artists": [ - "0UF7XLthtbSF2Eur7559oV", - "6G7P1WNRTlg3FxiYaW0hYh", - "31XcLJj5SRtJ0smI0iV9g8" - ], - "id": "0qKX14YZHptDWiEN0CgxGz", - "name": "Zenith", - "spotify_id": null, - "url": "https://open.spotify.com/track/0qKX14YZHptDWiEN0CgxGz?si=174ddb3f25414e2c" - }, - "sensitive_attributes": [] - } - ] - }, - { - "mode": "managed", - "type": "spotify_playlist", - "name": "playlist", - "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", - "instances": [ - { - "schema_version": 0, - "attributes": { - "description": "Wishing you make the nicest, most Randomly Accessible Memories this CodeR̶A̶M̶Jam :)", - "id": "05jajLNgeqfSxYeIG1KABy", - "name": "The CodeJam Playlist", - "public": true, - "snapshot_id": "AAAAAlhTBx2hRLYLR2A+oq8zUkaaN0FA", - "tracks": [ - "6iZRqJjpbOVEv4mFTpqg1d", - "2eXJRM1lQJ591AStNA26Yx", - "0f8GgsD1lDtRBAS5GEDKgg", - "5aPrFctv3WJZyJ99Ld7leT", - "1iAsoOFV16Ax6OFKMq8sld", - "3PgYTZHsdWp4C21bQ8zxvM", - "2olIQt0rL0hOHau1SJ4xf2", - "4r4fX4NZIqVIhGyDvERp0l", - "49PyCCLOdJi0jHkGyyY2vv", - "2La21GqU4fKTQLcfLxTeoz", - "7nZ9CzhiFRPhOQCn7eDSnn", - "6rbbDK18oqK3MxHGQhpmn2", - "5LInOGDHqrgetnMlnvaDNq", - "3pr0y5h4MkBngy6gyozLDV", - "00mO8JKk2wQ7VqdPcY1r4O", - "0UODoj6stYv542c7NB9MNd", - "6pLpm17TjgukQbWU5F6fwo", - "49uLH3xvQLd4I4pCQacGH8", - "3vDJUlhCEqZLyRQVbsWsEI", - "2La21GqU4fKTQLcfLxTeoz", - "6rbbDK18oqK3MxHGQhpmn2", - "1dm6Sss1w9auE5TXM2zLvg", - "0UODoj6stYv542c7NB9MNd", - "76fdtNvwn6hkOmBvgOom7L", - "1HFGQbPcif9c7e31GEYjW3", - "4Ht7gUwE3s8vqXDbegwn4Q", - "6Zf5CXbvYc5o2Wi2Nv5hcw", - "1PQiPJP7w21qTTp7zpihcF", - "66tkDkPsznE5zIHNt4QkXB", - "0rdWkO7ncrh1lrJGDmLq9L", - "0hhXziDUO0wNYPsstDQWN6", - "1qtiESAzfGMw3YqJvI97ki", - "1Avnd5xLg7HmaNofmYk7RQ", - "2IFaUS63AIAr9P46W1lEwt", - "7z5R6cbZpB4njz080aA6u2", - "3uvALUobqjZvTGAh6737Mb", - "0qKX14YZHptDWiEN0CgxGz", - "7fN3QQtmCMkiczQ41IuhwK" - ] - }, - "sensitive_attributes": [], - "private": "bnVsbA==", - "dependencies": [ - "data.spotify_search_track.LImperatrice", - "data.spotify_search_track.Odyssee", - "data.spotify_search_track.Parcels", - "data.spotify_search_track.RAM", - "data.spotify_track.A_View_To_Kill", - "data.spotify_track.Zenith" - ] - } - ] - } - ], - "check_results": null -} From 190d71688f4cf54190041776cd1cf59f776b2ebf Mon Sep 17 00:00:00 2001 From: Gourav Date: Fri, 4 Jul 2025 11:29:38 +0530 Subject: [PATCH 43/48] add song --- main.tf | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/main.tf b/main.tf index 7c8a1e9..be0fa71 100644 --- a/main.tf +++ b/main.tf @@ -68,6 +68,10 @@ data "spotify_track" "starlight" { url = "https://open.spotify.com/track/5luWJxS799LLp2e88RffUx?si=ac548be34f354912" } +data "spotify_track" "vigilante" { + url = "https://open.spotify.com/track/2U7aXicPJAjJBYEIWIXsVI?si=dc24ab05cac54a43" +} + resource "spotify_playlist" "playlist" { @@ -89,6 +93,7 @@ resource "spotify_playlist" "playlist" { data.spotify_track.lady_hear_me_tonight.id, data.spotify_track.supermassive_black_hole.id, data.spotify_track.Get_Lucky.id, - data.spotify_track.starlight.id + data.spotify_track.starlight.id, + data.spotify_track.vigilante.id ]) } From 01c87bb3ca2c4bf3541013337849f17ea59b15ec Mon Sep 17 00:00:00 2001 From: Gourav Date: Fri, 4 Jul 2025 11:40:27 +0530 Subject: [PATCH 44/48] add Save Terraform State --- .github/workflows/tf-apply.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index e5e4e76..9682952 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -193,6 +193,13 @@ jobs: with: terraform_version: 1.12.2 + - name: Restore Terraform State + uses: actions/download-artifact@v4 + with: + name: terraform-state + path: . + + - name: Terraform Init run: terraform init @@ -227,6 +234,16 @@ jobs: SPOTIFY_API_KEY: ${{ env.SPOTIFY_API_KEY }} run: terraform apply -auto-approve -var "SPOTIFY_API_KEY=${SPOTIFY_API_KEY}" + - name: Save Terraform State + if: always() + uses: actions/upload-artifact@v4 + with: + name: terraform-state + path: | + terraform.tfstate + terraform.tfstate.backup + + - name: Upload debug artifacts if: failure() uses: actions/upload-artifact@v4 From 0f1887fa6ee5514dd8c622c4ca6eb2d0f242878a Mon Sep 17 00:00:00 2001 From: Gourav Date: Fri, 4 Jul 2025 11:47:35 +0530 Subject: [PATCH 45/48] add song --- main.tf | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/main.tf b/main.tf index be0fa71..d3aea05 100644 --- a/main.tf +++ b/main.tf @@ -72,6 +72,12 @@ data "spotify_track" "vigilante" { url = "https://open.spotify.com/track/2U7aXicPJAjJBYEIWIXsVI?si=dc24ab05cac54a43" } +data "spotify_track" "horizon" { + url = "https://open.spotify.com/track/69fx4BG9cZFOrBJk5aXDeL?si=8f99804af0b842b8" +} + + + resource "spotify_playlist" "playlist" { @@ -94,6 +100,7 @@ resource "spotify_playlist" "playlist" { data.spotify_track.supermassive_black_hole.id, data.spotify_track.Get_Lucky.id, data.spotify_track.starlight.id, - data.spotify_track.vigilante.id + data.spotify_track.vigilante.id, + data.spotify_track.horizon.id ]) } From 747fced58f0a038e756f2e029727d89050592312 Mon Sep 17 00:00:00 2001 From: Gourav Date: Fri, 4 Jul 2025 11:50:04 +0530 Subject: [PATCH 46/48] add song --- .github/workflows/tf-apply.yaml | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/.github/workflows/tf-apply.yaml b/.github/workflows/tf-apply.yaml index 9682952..e5e4e76 100644 --- a/.github/workflows/tf-apply.yaml +++ b/.github/workflows/tf-apply.yaml @@ -193,13 +193,6 @@ jobs: with: terraform_version: 1.12.2 - - name: Restore Terraform State - uses: actions/download-artifact@v4 - with: - name: terraform-state - path: . - - - name: Terraform Init run: terraform init @@ -234,16 +227,6 @@ jobs: SPOTIFY_API_KEY: ${{ env.SPOTIFY_API_KEY }} run: terraform apply -auto-approve -var "SPOTIFY_API_KEY=${SPOTIFY_API_KEY}" - - name: Save Terraform State - if: always() - uses: actions/upload-artifact@v4 - with: - name: terraform-state - path: | - terraform.tfstate - terraform.tfstate.backup - - - name: Upload debug artifacts if: failure() uses: actions/upload-artifact@v4 From 887ed60a4fb772d63d1ea05607787b541a98d120 Mon Sep 17 00:00:00 2001 From: Gourav Date: Fri, 4 Jul 2025 11:53:10 +0530 Subject: [PATCH 47/48] add tfstate again --- terraform.tfstate | 514 +++++++++++++++++++++++++++++++++++++++ terraform.tfstate.backup | 514 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 1028 insertions(+) create mode 100644 terraform.tfstate create mode 100644 terraform.tfstate.backup diff --git a/terraform.tfstate b/terraform.tfstate new file mode 100644 index 0000000..957cd2f --- /dev/null +++ b/terraform.tfstate @@ -0,0 +1,514 @@ +{ + "version": 4, + "terraform_version": "1.9.1", + "serial": 2, + "lineage": "a371cb0b-d2cc-96ec-5077-6a90be280515", + "outputs": {}, + "resources": [ + { + "mode": "data", + "type": "spotify_search_track", + "name": "LImperatrice", + "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "album": null, + "artist": "L'Impératrice", + "explicit": true, + "id": "1720680020", + "limit": 10, + "name": null, + "tracks": [ + { + "album": "6qnJcNAInQ2CAtC4zWG8HT", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU", + "2HGAZDmVV3GAH4XFAZdvSG" + ], + "id": "49PyCCLOdJi0jHkGyyY2vv", + "name": "Sonate Pacifique" + }, + { + "album": "346ZDnGgJudDau17EEyWWA", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "2La21GqU4fKTQLcfLxTeoz", + "name": "Agitations tropicales" + }, + { + "album": "41Ht5x3AgpMVmoFoIzaUPO", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "7nZ9CzhiFRPhOQCn7eDSnn", + "name": "Vanille fraise" + }, + { + "album": "346ZDnGgJudDau17EEyWWA", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "6rbbDK18oqK3MxHGQhpmn2", + "name": "La lune" + }, + { + "album": "5pk9cqTDktuytdBNidkke4", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU", + "3lLHpTOJ11tWiUNGYN14gt" + ], + "id": "5LInOGDHqrgetnMlnvaDNq", + "name": "Everything Eventually Ends" + }, + { + "album": "7gE23KRzNbXeB6nZmQVqa3", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "3pr0y5h4MkBngy6gyozLDV", + "name": "Voodoo?" + }, + { + "album": "7gE23KRzNbXeB6nZmQVqa3", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "00mO8JKk2wQ7VqdPcY1r4O", + "name": "Peur des filles" + }, + { + "album": "346ZDnGgJudDau17EEyWWA", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "0UODoj6stYv542c7NB9MNd", + "name": "Le départ" + }, + { + "album": "1I2vgUWhcovQKIgQK0jHPB", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU", + "4NZvixzsSefsNiIqXn0NDe" + ], + "id": "6pLpm17TjgukQbWU5F6fwo", + "name": "Any Way (feat. Maggie Rogers)" + }, + { + "album": "7Fbph706oR8vaTPcYYvp9n", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "49uLH3xvQLd4I4pCQacGH8", + "name": "Love from the Other Side" + } + ], + "year": null + }, + "sensitive_attributes": [] + } + ] + }, + { + "mode": "data", + "type": "spotify_search_track", + "name": "Odyssee", + "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "album": "Odyssee EP", + "artist": null, + "explicit": true, + "id": "1720680020", + "limit": 10, + "name": null, + "tracks": [ + { + "album": "346ZDnGgJudDau17EEyWWA", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "3vDJUlhCEqZLyRQVbsWsEI", + "name": "Odyssée" + }, + { + "album": "346ZDnGgJudDau17EEyWWA", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "2La21GqU4fKTQLcfLxTeoz", + "name": "Agitations tropicales" + }, + { + "album": "346ZDnGgJudDau17EEyWWA", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "6rbbDK18oqK3MxHGQhpmn2", + "name": "La lune" + }, + { + "album": "346ZDnGgJudDau17EEyWWA", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "1dm6Sss1w9auE5TXM2zLvg", + "name": "Épilogue" + }, + { + "album": "346ZDnGgJudDau17EEyWWA", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "0UODoj6stYv542c7NB9MNd", + "name": "Le départ" + }, + { + "album": "346ZDnGgJudDau17EEyWWA", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "76fdtNvwn6hkOmBvgOom7L", + "name": "Parfum thérémine" + }, + { + "album": "4J8qpKJYBNSzT5rJseBtRj", + "artists": [ + "5YIaZ0ICxZyNEsATQRsxRk" + ], + "id": "1HFGQbPcif9c7e31GEYjW3", + "name": "L’épreuve" + }, + { + "album": "3WynrtGVOjSMhmJbF1c3N0", + "artists": [ + "1s5QJK3dJhK5QmlTYgEV7w" + ], + "id": "4Ht7gUwE3s8vqXDbegwn4Q", + "name": "Epilogue" + }, + { + "album": "0vU5QAZBmYfD6dkuAnalJy", + "artists": [ + "24WRvSDW3TaWsPvJ9JwDTx" + ], + "id": "6Zf5CXbvYc5o2Wi2Nv5hcw", + "name": "Epitaph" + }, + { + "album": "6Ul63eiW8REW6SCFmq8SDR", + "artists": [ + "6uTAGzcapoHq5Y4SMVPcZi" + ], + "id": "1PQiPJP7w21qTTp7zpihcF", + "name": "Epilog" + } + ], + "year": null + }, + "sensitive_attributes": [] + } + ] + }, + { + "mode": "data", + "type": "spotify_search_track", + "name": "Parcels", + "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "album": null, + "artist": "Parcels", + "explicit": true, + "id": "1720680020", + "limit": 8, + "name": null, + "tracks": [ + { + "album": "5v7PsESglCFeVcb7wNEWIW", + "artists": [ + "3oKRxpszQKUjjaHz388fVA" + ], + "id": "66tkDkPsznE5zIHNt4QkXB", + "name": "Tieduprightnow" + }, + { + "album": "0ugE0qpkFDNLYFmNiUOOKA", + "artists": [ + "3oKRxpszQKUjjaHz388fVA" + ], + "id": "0rdWkO7ncrh1lrJGDmLq9L", + "name": "Gamesofluck" + }, + { + "album": "0cIynhird3mHUUs71R7zsj", + "artists": [ + "3oKRxpszQKUjjaHz388fVA" + ], + "id": "0hhXziDUO0wNYPsstDQWN6", + "name": "Overnight" + }, + { + "album": "6tNAnVG8xrmMbINuWutrNy", + "artists": [ + "3oKRxpszQKUjjaHz388fVA" + ], + "id": "1qtiESAzfGMw3YqJvI97ki", + "name": "Lightenup" + }, + { + "album": "6tNAnVG8xrmMbINuWutrNy", + "artists": [ + "3oKRxpszQKUjjaHz388fVA" + ], + "id": "1Avnd5xLg7HmaNofmYk7RQ", + "name": "Withorwithout" + }, + { + "album": "6tNAnVG8xrmMbINuWutrNy", + "artists": [ + "3oKRxpszQKUjjaHz388fVA" + ], + "id": "2IFaUS63AIAr9P46W1lEwt", + "name": "IknowhowIfeel" + }, + { + "album": "4ptXy3D8sAopLdffBms1nG", + "artists": [ + "3oKRxpszQKUjjaHz388fVA" + ], + "id": "7z5R6cbZpB4njz080aA6u2", + "name": "Hideout" + }, + { + "album": "7Ih98kmIPJCsXcgrikcmoq", + "artists": [ + "7c5qu1gNlg8jWDzzmlp89O", + "3oKRxpszQKUjjaHz388fVA" + ], + "id": "3uvALUobqjZvTGAh6737Mb", + "name": "Stumble" + } + ], + "year": null + }, + "sensitive_attributes": [] + } + ] + }, + { + "mode": "data", + "type": "spotify_search_track", + "name": "RAM", + "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "album": "Random Access Memories (10th Anniversary Edition)", + "artist": null, + "explicit": true, + "id": "1720680020", + "limit": 8, + "name": null, + "tracks": [ + { + "album": "4mAhdh996uW5SnnFKXUmC0", + "artists": [ + "4tZwfgrHOc3mvqYlEYSvVi" + ], + "id": "6iZRqJjpbOVEv4mFTpqg1d", + "name": "Horizon (Japan CD)" + }, + { + "album": "4mAhdh996uW5SnnFKXUmC0", + "artists": [ + "4tZwfgrHOc3mvqYlEYSvVi" + ], + "id": "2eXJRM1lQJ591AStNA26Yx", + "name": "Giorgio by Moroder" + }, + { + "album": "4mAhdh996uW5SnnFKXUmC0", + "artists": [ + "4tZwfgrHOc3mvqYlEYSvVi", + "1rAv1GhTQ2rmG94p9lU3rB" + ], + "id": "0f8GgsD1lDtRBAS5GEDKgg", + "name": "Instant Crush (feat. Julian Casablancas)" + }, + { + "album": "4mAhdh996uW5SnnFKXUmC0", + "artists": [ + "4tZwfgrHOc3mvqYlEYSvVi" + ], + "id": "5aPrFctv3WJZyJ99Ld7leT", + "name": "Give Life Back to Music" + }, + { + "album": "4mAhdh996uW5SnnFKXUmC0", + "artists": [ + "4tZwfgrHOc3mvqYlEYSvVi" + ], + "id": "1iAsoOFV16Ax6OFKMq8sld", + "name": "Prime (2012 Unfinished)" + }, + { + "album": "4mAhdh996uW5SnnFKXUmC0", + "artists": [ + "4tZwfgrHOc3mvqYlEYSvVi", + "2RdwBSPQiwcmiDo9kixcl8" + ], + "id": "3PgYTZHsdWp4C21bQ8zxvM", + "name": "LYTD (Vocoder Tests) [feat. Pharrell Williams]" + }, + { + "album": "4mAhdh996uW5SnnFKXUmC0", + "artists": [ + "4tZwfgrHOc3mvqYlEYSvVi", + "2RdwBSPQiwcmiDo9kixcl8", + "3yDIp0kaq9EFKe07X1X2rz" + ], + "id": "2olIQt0rL0hOHau1SJ4xf2", + "name": "Get Lucky (feat. Pharrell Williams and Nile Rodgers)" + }, + { + "album": "4mAhdh996uW5SnnFKXUmC0", + "artists": [ + "4tZwfgrHOc3mvqYlEYSvVi", + "1rAv1GhTQ2rmG94p9lU3rB", + "4nUBBtLtzqZGpdiynTJbYJ" + ], + "id": "4r4fX4NZIqVIhGyDvERp0l", + "name": "Infinity Repeating (2013 Demo) [feat. Julian Casablancas+The Voidz]" + } + ], + "year": null + }, + "sensitive_attributes": [] + } + ] + }, + { + "mode": "data", + "type": "spotify_track", + "name": "A_View_To_Kill", + "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "album": "6qnJcNAInQ2CAtC4zWG8HT", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "7fN3QQtmCMkiczQ41IuhwK", + "name": "A View to a Kill", + "spotify_id": null, + "url": "https://open.spotify.com/track/7fN3QQtmCMkiczQ41IuhwK?si=d0e32a11da1f4c72" + }, + "sensitive_attributes": [] + } + ] + }, + { + "mode": "data", + "type": "spotify_track", + "name": "Zenith", + "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "album": "2Js9F9O6V6mDZTal95Iner", + "artists": [ + "0UF7XLthtbSF2Eur7559oV", + "6G7P1WNRTlg3FxiYaW0hYh", + "31XcLJj5SRtJ0smI0iV9g8" + ], + "id": "0qKX14YZHptDWiEN0CgxGz", + "name": "Zenith", + "spotify_id": null, + "url": "https://open.spotify.com/track/0qKX14YZHptDWiEN0CgxGz?si=174ddb3f25414e2c" + }, + "sensitive_attributes": [] + } + ] + }, + { + "mode": "managed", + "type": "spotify_playlist", + "name": "playlist", + "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "description": "Wishing you make the nicest, most Randomly Accessible Memories this CodeR̶A̶M̶Jam :)", + "id": "05jajLNgeqfSxYeIG1KABy", + "name": "The CodeJam Playlist", + "public": true, + "snapshot_id": "AAAAAf9HUgM79VVH4LEpfe0PP/VVPudk", + "tracks": [ + "6iZRqJjpbOVEv4mFTpqg1d", + "2eXJRM1lQJ591AStNA26Yx", + "0f8GgsD1lDtRBAS5GEDKgg", + "5aPrFctv3WJZyJ99Ld7leT", + "1iAsoOFV16Ax6OFKMq8sld", + "3PgYTZHsdWp4C21bQ8zxvM", + "2olIQt0rL0hOHau1SJ4xf2", + "4r4fX4NZIqVIhGyDvERp0l", + "49PyCCLOdJi0jHkGyyY2vv", + "2La21GqU4fKTQLcfLxTeoz", + "7nZ9CzhiFRPhOQCn7eDSnn", + "6rbbDK18oqK3MxHGQhpmn2", + "5LInOGDHqrgetnMlnvaDNq", + "3pr0y5h4MkBngy6gyozLDV", + "00mO8JKk2wQ7VqdPcY1r4O", + "0UODoj6stYv542c7NB9MNd", + "6pLpm17TjgukQbWU5F6fwo", + "49uLH3xvQLd4I4pCQacGH8", + "3vDJUlhCEqZLyRQVbsWsEI", + "2La21GqU4fKTQLcfLxTeoz", + "6rbbDK18oqK3MxHGQhpmn2", + "1dm6Sss1w9auE5TXM2zLvg", + "0UODoj6stYv542c7NB9MNd", + "76fdtNvwn6hkOmBvgOom7L", + "1HFGQbPcif9c7e31GEYjW3", + "4Ht7gUwE3s8vqXDbegwn4Q", + "6Zf5CXbvYc5o2Wi2Nv5hcw", + "1PQiPJP7w21qTTp7zpihcF", + "66tkDkPsznE5zIHNt4QkXB", + "0rdWkO7ncrh1lrJGDmLq9L", + "0hhXziDUO0wNYPsstDQWN6", + "1qtiESAzfGMw3YqJvI97ki", + "1Avnd5xLg7HmaNofmYk7RQ", + "2IFaUS63AIAr9P46W1lEwt", + "7z5R6cbZpB4njz080aA6u2", + "3uvALUobqjZvTGAh6737Mb", + "0qKX14YZHptDWiEN0CgxGz", + "7fN3QQtmCMkiczQ41IuhwK" + ] + }, + "sensitive_attributes": [], + "private": "bnVsbA==", + "dependencies": [ + "data.spotify_search_track.LImperatrice", + "data.spotify_search_track.Odyssee", + "data.spotify_search_track.Parcels", + "data.spotify_search_track.RAM", + "data.spotify_track.A_View_To_Kill", + "data.spotify_track.Zenith" + ] + } + ] + } + ], + "check_results": null +} diff --git a/terraform.tfstate.backup b/terraform.tfstate.backup new file mode 100644 index 0000000..4447b51 --- /dev/null +++ b/terraform.tfstate.backup @@ -0,0 +1,514 @@ +{ + "version": 4, + "terraform_version": "1.9.1", + "serial": 1, + "lineage": "a371cb0b-d2cc-96ec-5077-6a90be280515", + "outputs": {}, + "resources": [ + { + "mode": "data", + "type": "spotify_search_track", + "name": "LImperatrice", + "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "album": null, + "artist": "L'Impératrice", + "explicit": true, + "id": "1720680005", + "limit": 10, + "name": null, + "tracks": [ + { + "album": "6qnJcNAInQ2CAtC4zWG8HT", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU", + "2HGAZDmVV3GAH4XFAZdvSG" + ], + "id": "49PyCCLOdJi0jHkGyyY2vv", + "name": "Sonate Pacifique" + }, + { + "album": "346ZDnGgJudDau17EEyWWA", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "2La21GqU4fKTQLcfLxTeoz", + "name": "Agitations tropicales" + }, + { + "album": "41Ht5x3AgpMVmoFoIzaUPO", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "7nZ9CzhiFRPhOQCn7eDSnn", + "name": "Vanille fraise" + }, + { + "album": "346ZDnGgJudDau17EEyWWA", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "6rbbDK18oqK3MxHGQhpmn2", + "name": "La lune" + }, + { + "album": "5pk9cqTDktuytdBNidkke4", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU", + "3lLHpTOJ11tWiUNGYN14gt" + ], + "id": "5LInOGDHqrgetnMlnvaDNq", + "name": "Everything Eventually Ends" + }, + { + "album": "7gE23KRzNbXeB6nZmQVqa3", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "3pr0y5h4MkBngy6gyozLDV", + "name": "Voodoo?" + }, + { + "album": "7gE23KRzNbXeB6nZmQVqa3", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "00mO8JKk2wQ7VqdPcY1r4O", + "name": "Peur des filles" + }, + { + "album": "346ZDnGgJudDau17EEyWWA", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "0UODoj6stYv542c7NB9MNd", + "name": "Le départ" + }, + { + "album": "1I2vgUWhcovQKIgQK0jHPB", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU", + "4NZvixzsSefsNiIqXn0NDe" + ], + "id": "6pLpm17TjgukQbWU5F6fwo", + "name": "Any Way (feat. Maggie Rogers)" + }, + { + "album": "7Fbph706oR8vaTPcYYvp9n", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "49uLH3xvQLd4I4pCQacGH8", + "name": "Love from the Other Side" + } + ], + "year": null + }, + "sensitive_attributes": [] + } + ] + }, + { + "mode": "data", + "type": "spotify_search_track", + "name": "Odyssee", + "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "album": "Odyssee EP", + "artist": null, + "explicit": true, + "id": "1720680005", + "limit": 10, + "name": null, + "tracks": [ + { + "album": "346ZDnGgJudDau17EEyWWA", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "3vDJUlhCEqZLyRQVbsWsEI", + "name": "Odyssée" + }, + { + "album": "346ZDnGgJudDau17EEyWWA", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "2La21GqU4fKTQLcfLxTeoz", + "name": "Agitations tropicales" + }, + { + "album": "346ZDnGgJudDau17EEyWWA", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "6rbbDK18oqK3MxHGQhpmn2", + "name": "La lune" + }, + { + "album": "346ZDnGgJudDau17EEyWWA", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "1dm6Sss1w9auE5TXM2zLvg", + "name": "Épilogue" + }, + { + "album": "346ZDnGgJudDau17EEyWWA", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "0UODoj6stYv542c7NB9MNd", + "name": "Le départ" + }, + { + "album": "346ZDnGgJudDau17EEyWWA", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "76fdtNvwn6hkOmBvgOom7L", + "name": "Parfum thérémine" + }, + { + "album": "4J8qpKJYBNSzT5rJseBtRj", + "artists": [ + "5YIaZ0ICxZyNEsATQRsxRk" + ], + "id": "1HFGQbPcif9c7e31GEYjW3", + "name": "L’épreuve" + }, + { + "album": "3WynrtGVOjSMhmJbF1c3N0", + "artists": [ + "1s5QJK3dJhK5QmlTYgEV7w" + ], + "id": "4Ht7gUwE3s8vqXDbegwn4Q", + "name": "Epilogue" + }, + { + "album": "0vU5QAZBmYfD6dkuAnalJy", + "artists": [ + "24WRvSDW3TaWsPvJ9JwDTx" + ], + "id": "6Zf5CXbvYc5o2Wi2Nv5hcw", + "name": "Epitaph" + }, + { + "album": "6Ul63eiW8REW6SCFmq8SDR", + "artists": [ + "6uTAGzcapoHq5Y4SMVPcZi" + ], + "id": "1PQiPJP7w21qTTp7zpihcF", + "name": "Epilog" + } + ], + "year": null + }, + "sensitive_attributes": [] + } + ] + }, + { + "mode": "data", + "type": "spotify_search_track", + "name": "Parcels", + "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "album": null, + "artist": "Parcels", + "explicit": true, + "id": "1720680005", + "limit": 8, + "name": null, + "tracks": [ + { + "album": "5v7PsESglCFeVcb7wNEWIW", + "artists": [ + "3oKRxpszQKUjjaHz388fVA" + ], + "id": "66tkDkPsznE5zIHNt4QkXB", + "name": "Tieduprightnow" + }, + { + "album": "0ugE0qpkFDNLYFmNiUOOKA", + "artists": [ + "3oKRxpszQKUjjaHz388fVA" + ], + "id": "0rdWkO7ncrh1lrJGDmLq9L", + "name": "Gamesofluck" + }, + { + "album": "0cIynhird3mHUUs71R7zsj", + "artists": [ + "3oKRxpszQKUjjaHz388fVA" + ], + "id": "0hhXziDUO0wNYPsstDQWN6", + "name": "Overnight" + }, + { + "album": "6tNAnVG8xrmMbINuWutrNy", + "artists": [ + "3oKRxpszQKUjjaHz388fVA" + ], + "id": "1qtiESAzfGMw3YqJvI97ki", + "name": "Lightenup" + }, + { + "album": "6tNAnVG8xrmMbINuWutrNy", + "artists": [ + "3oKRxpszQKUjjaHz388fVA" + ], + "id": "1Avnd5xLg7HmaNofmYk7RQ", + "name": "Withorwithout" + }, + { + "album": "6tNAnVG8xrmMbINuWutrNy", + "artists": [ + "3oKRxpszQKUjjaHz388fVA" + ], + "id": "2IFaUS63AIAr9P46W1lEwt", + "name": "IknowhowIfeel" + }, + { + "album": "4ptXy3D8sAopLdffBms1nG", + "artists": [ + "3oKRxpszQKUjjaHz388fVA" + ], + "id": "7z5R6cbZpB4njz080aA6u2", + "name": "Hideout" + }, + { + "album": "7Ih98kmIPJCsXcgrikcmoq", + "artists": [ + "7c5qu1gNlg8jWDzzmlp89O", + "3oKRxpszQKUjjaHz388fVA" + ], + "id": "3uvALUobqjZvTGAh6737Mb", + "name": "Stumble" + } + ], + "year": null + }, + "sensitive_attributes": [] + } + ] + }, + { + "mode": "data", + "type": "spotify_search_track", + "name": "RAM", + "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "album": "Random Access Memories (10th Anniversary Edition)", + "artist": null, + "explicit": true, + "id": "1720680005", + "limit": 8, + "name": null, + "tracks": [ + { + "album": "4mAhdh996uW5SnnFKXUmC0", + "artists": [ + "4tZwfgrHOc3mvqYlEYSvVi" + ], + "id": "6iZRqJjpbOVEv4mFTpqg1d", + "name": "Horizon (Japan CD)" + }, + { + "album": "4mAhdh996uW5SnnFKXUmC0", + "artists": [ + "4tZwfgrHOc3mvqYlEYSvVi" + ], + "id": "2eXJRM1lQJ591AStNA26Yx", + "name": "Giorgio by Moroder" + }, + { + "album": "4mAhdh996uW5SnnFKXUmC0", + "artists": [ + "4tZwfgrHOc3mvqYlEYSvVi", + "1rAv1GhTQ2rmG94p9lU3rB" + ], + "id": "0f8GgsD1lDtRBAS5GEDKgg", + "name": "Instant Crush (feat. Julian Casablancas)" + }, + { + "album": "4mAhdh996uW5SnnFKXUmC0", + "artists": [ + "4tZwfgrHOc3mvqYlEYSvVi" + ], + "id": "5aPrFctv3WJZyJ99Ld7leT", + "name": "Give Life Back to Music" + }, + { + "album": "4mAhdh996uW5SnnFKXUmC0", + "artists": [ + "4tZwfgrHOc3mvqYlEYSvVi" + ], + "id": "1iAsoOFV16Ax6OFKMq8sld", + "name": "Prime (2012 Unfinished)" + }, + { + "album": "4mAhdh996uW5SnnFKXUmC0", + "artists": [ + "4tZwfgrHOc3mvqYlEYSvVi", + "2RdwBSPQiwcmiDo9kixcl8" + ], + "id": "3PgYTZHsdWp4C21bQ8zxvM", + "name": "LYTD (Vocoder Tests) [feat. Pharrell Williams]" + }, + { + "album": "4mAhdh996uW5SnnFKXUmC0", + "artists": [ + "4tZwfgrHOc3mvqYlEYSvVi", + "2RdwBSPQiwcmiDo9kixcl8", + "3yDIp0kaq9EFKe07X1X2rz" + ], + "id": "2olIQt0rL0hOHau1SJ4xf2", + "name": "Get Lucky (feat. Pharrell Williams and Nile Rodgers)" + }, + { + "album": "4mAhdh996uW5SnnFKXUmC0", + "artists": [ + "4tZwfgrHOc3mvqYlEYSvVi", + "1rAv1GhTQ2rmG94p9lU3rB", + "4nUBBtLtzqZGpdiynTJbYJ" + ], + "id": "4r4fX4NZIqVIhGyDvERp0l", + "name": "Infinity Repeating (2013 Demo) [feat. Julian Casablancas+The Voidz]" + } + ], + "year": null + }, + "sensitive_attributes": [] + } + ] + }, + { + "mode": "data", + "type": "spotify_track", + "name": "A_View_To_Kill", + "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "album": "6qnJcNAInQ2CAtC4zWG8HT", + "artists": [ + "4PwlsrN0t5mLN0C827cbEU" + ], + "id": "7fN3QQtmCMkiczQ41IuhwK", + "name": "A View to a Kill", + "spotify_id": null, + "url": "https://open.spotify.com/track/7fN3QQtmCMkiczQ41IuhwK?si=d0e32a11da1f4c72" + }, + "sensitive_attributes": [] + } + ] + }, + { + "mode": "data", + "type": "spotify_track", + "name": "Zenith", + "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "album": "2Js9F9O6V6mDZTal95Iner", + "artists": [ + "0UF7XLthtbSF2Eur7559oV", + "6G7P1WNRTlg3FxiYaW0hYh", + "31XcLJj5SRtJ0smI0iV9g8" + ], + "id": "0qKX14YZHptDWiEN0CgxGz", + "name": "Zenith", + "spotify_id": null, + "url": "https://open.spotify.com/track/0qKX14YZHptDWiEN0CgxGz?si=174ddb3f25414e2c" + }, + "sensitive_attributes": [] + } + ] + }, + { + "mode": "managed", + "type": "spotify_playlist", + "name": "playlist", + "provider": "provider[\"registry.terraform.io/conradludgate/spotify\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "description": "Wishing you make the nicest, most Randomly Accessible Memories this CodeR̶A̶M̶Jam :)", + "id": "05jajLNgeqfSxYeIG1KABy", + "name": "The CodeJam Playlist", + "public": true, + "snapshot_id": "AAAAAlhTBx2hRLYLR2A+oq8zUkaaN0FA", + "tracks": [ + "6iZRqJjpbOVEv4mFTpqg1d", + "2eXJRM1lQJ591AStNA26Yx", + "0f8GgsD1lDtRBAS5GEDKgg", + "5aPrFctv3WJZyJ99Ld7leT", + "1iAsoOFV16Ax6OFKMq8sld", + "3PgYTZHsdWp4C21bQ8zxvM", + "2olIQt0rL0hOHau1SJ4xf2", + "4r4fX4NZIqVIhGyDvERp0l", + "49PyCCLOdJi0jHkGyyY2vv", + "2La21GqU4fKTQLcfLxTeoz", + "7nZ9CzhiFRPhOQCn7eDSnn", + "6rbbDK18oqK3MxHGQhpmn2", + "5LInOGDHqrgetnMlnvaDNq", + "3pr0y5h4MkBngy6gyozLDV", + "00mO8JKk2wQ7VqdPcY1r4O", + "0UODoj6stYv542c7NB9MNd", + "6pLpm17TjgukQbWU5F6fwo", + "49uLH3xvQLd4I4pCQacGH8", + "3vDJUlhCEqZLyRQVbsWsEI", + "2La21GqU4fKTQLcfLxTeoz", + "6rbbDK18oqK3MxHGQhpmn2", + "1dm6Sss1w9auE5TXM2zLvg", + "0UODoj6stYv542c7NB9MNd", + "76fdtNvwn6hkOmBvgOom7L", + "1HFGQbPcif9c7e31GEYjW3", + "4Ht7gUwE3s8vqXDbegwn4Q", + "6Zf5CXbvYc5o2Wi2Nv5hcw", + "1PQiPJP7w21qTTp7zpihcF", + "66tkDkPsznE5zIHNt4QkXB", + "0rdWkO7ncrh1lrJGDmLq9L", + "0hhXziDUO0wNYPsstDQWN6", + "1qtiESAzfGMw3YqJvI97ki", + "1Avnd5xLg7HmaNofmYk7RQ", + "2IFaUS63AIAr9P46W1lEwt", + "7z5R6cbZpB4njz080aA6u2", + "3uvALUobqjZvTGAh6737Mb", + "0qKX14YZHptDWiEN0CgxGz", + "7fN3QQtmCMkiczQ41IuhwK" + ] + }, + "sensitive_attributes": [], + "private": "bnVsbA==", + "dependencies": [ + "data.spotify_search_track.LImperatrice", + "data.spotify_search_track.Odyssee", + "data.spotify_search_track.Parcels", + "data.spotify_search_track.RAM", + "data.spotify_track.A_View_To_Kill", + "data.spotify_track.Zenith" + ] + } + ] + } + ], + "check_results": null +} From 0257bc75d6aed92ac152ebaaf7c3f228209dbaee Mon Sep 17 00:00:00 2001 From: Gourav Date: Fri, 4 Jul 2025 12:32:33 +0530 Subject: [PATCH 48/48] add instructions to add our own playlist --- update_playlist.md | 104 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 update_playlist.md diff --git a/update_playlist.md b/update_playlist.md new file mode 100644 index 0000000..b814930 --- /dev/null +++ b/update_playlist.md @@ -0,0 +1,104 @@ +## 🎶 Import Your Spotify Playlist into Terraform State + +To make Terraform manage your **own Spotify playlist** (instead of creating a new one every time), follow these steps carefully: + +--- + +### 🧱 1. Initialize Terraform + +Open your terminal in the project root and run: + +```bash +terraform init +``` + +--- + +### 🛠️ 2. Clone and Build Patched Spotify Auth Proxy + +Clone the patched Terraform Spotify provider and build the `spotify_auth_proxy` binary: + +```bash +git clone -b fix/base-url-127 https://github.com/gauravslnk/terraform-provider-spotify +cd terraform-provider-spotify/spotify_auth_proxy +go build -o spotify_auth_proxy.exe +``` + +> ✅ This builds the proxy binary required for authorization and token generation. + +--- + +### 🔐 3. Set Required Environment Variables + +Replace placeholders with your actual Spotify app credentials: + +```powershell +$env:SPOTIFY_CLIENT_ID = "" +$env:SPOTIFY_CLIENT_SECRET = "" +$env:SPOTIFY_REDIRECT_URI = "" # add your redirect uri which you set in spotify developer app +$env:SPOTIFY_PROXY_BASE_URI = "http://127.0.0.1:8080" +``` + +--- + +### 🚪 4. Start the Spotify Auth Proxy + +```powershell +.\spotify_auth_proxy --port 8080 +``` + +It will output something like this: + +``` +APIKey: gS4pTULOzrmczFBC3d4olxxtWBss-j4zt-gvQIaiPYaymedCSggxigu7Ksjgq4gS +Auth URL: http://127.0.0.1:8080/authorize?token=... +Listening on: 127.0.0.1:8080 +``` + +> ⚠️ **Do not close this terminal** — keep it running. + +--- + +### ✅ 5. Authorize Access + +Open the **Auth URL** in your browser, log in, and authorize the app. + +You should see: + +``` +Authorization successful +Token Retrieved +``` + +--- + +### ⛓️ 6. Import Your Playlist into Terraform State + +1. Open a **new terminal window**. +2. In the new terminal, run: + +```bash +terraform import spotify_playlist.playlist +``` + +Example: + +```bash +terraform import spotify_playlist.playlist 5GkQIP5IetMlF4E7IoUayV +``` + +3. When prompted for `SPOTIFY_API_KEY`, **paste the API key** shown in the other terminal. +4. Wait for a success message like: + +``` +spotify_playlist.playlist: Import prepared! +``` + +--- + +### 🧾 Result + +Your playlist is now fully tracked by Terraform! ✅ + +* It will **no longer create new playlists** on each run. +* Terraform will **apply updates** only to this existing playlist.