-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rewrite with web components and batch import to R4
- Loading branch information
1 parent
afff2db
commit 74a786f
Showing
5 changed files
with
286 additions
and
159 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,104 +1,8 @@ | ||
import 'https://cdn.jsdelivr.net/npm/@radio4000/components/dist/index.min.js' | ||
import 'https://cdn.jsdelivr.net/gh/oskarrough/rough-spinner/rough-spinner.js' | ||
import { html, render } from 'https://unpkg.com/lit-html?module' | ||
import { getAccessToken, extractSpotifyPlaylistId, getSpotifyPlaylist, parseSpotifyTrack, searchYoutube } from './helpers.js' | ||
|
||
import SpotifyToYoutube from './spotify-to-youtube.js' | ||
import R4BatchImport from './r4-batch-import.js' | ||
|
||
// Get and set the Spotify access token | ||
document.querySelector('#spotifyToYoutube').addEventListener('submit', handleSpotifyTokenSubmit) | ||
|
||
export async function handleSpotifyTokenSubmit(event) { | ||
event.preventDefault() | ||
|
||
const $form = event.target | ||
const formData = new FormData($form) | ||
|
||
const $btn = $form.querySelector('button[type="submit"]') | ||
const $token = $form.querySelector('[name="token"]') | ||
$btn.disabled = true | ||
|
||
// Get the Spotify token | ||
let token = formData.get('token') | ||
if (!token) { | ||
try { | ||
const clientId = formData.get('clientId') | ||
const clientSecret = formData.get('clientSecret') | ||
token = await getAccessToken(clientId, clientSecret) | ||
$token.value = token | ||
} catch (error) { | ||
console.error('An error occurred:', error) | ||
$token.value = 'ERROR GETTING TOKEN!' | ||
} | ||
} | ||
|
||
try { | ||
// Query the playlist | ||
const playlistId = extractSpotifyPlaylistId(formData.get('url')) | ||
const playlist = await getSpotifyPlaylist(playlistId, token) | ||
const tracks = playlist.map(item => parseSpotifyTrack(item.track)).slice(0,3) | ||
|
||
// Search YouTube and render as results come in | ||
const maxResults = 4 //formData.get('limit') | ||
for (const [i, t] of Object.entries(tracks)) { | ||
tracks[i].searchResults = await searchYoutube(t.artist, t.title, t.isrc, maxResults) | ||
displayResults(tracks, i) | ||
} | ||
console.log('Spotify tracks with YouTube search results', tracks) | ||
} catch (error) { | ||
console.error('An error occurred:', error) | ||
} finally { | ||
$btn.disabled = false | ||
} | ||
} | ||
|
||
function displayResults(tracks, i) { | ||
render(tableTemplate(tracks, Number(i) + 1), document.querySelector('#app')) | ||
} | ||
|
||
const tableTemplate = (tracks, i) => html` | ||
${i < tracks.length ? html`<rough-spinner spinner="1" fps="30"></rough-spinner> Matching ${i}/${tracks.length}...` : null} | ||
<form @submit=${saveResults}> | ||
<ul class="tracks"> | ||
${tracks.map( | ||
(track, i) => html`<li> | ||
<strong>${i}. ${track.artist} - ${track.title}</strong> | ||
<a target="_blank" href=${track.url}>link</a> | ||
<ul class="results"> | ||
${track.searchResults.map((video) => searchResultTemplate(track, video))} | ||
</ul> | ||
</li>` | ||
)} | ||
</ul> | ||
<button type="submit">Save results</button><br> | ||
<br> | ||
<textarea></textarea> | ||
</form> | ||
` | ||
|
||
const searchResultTemplate = (track, video) => html` | ||
<li> | ||
<label> | ||
<input type="radio" name=${'result_' + track.id} value=${video.id}> | ||
<img src=${video.thumbnail} alt=${video.title} /> | ||
</label> | ||
<ul> | ||
<li><a href=${`https://www.youtube.com/watch?v=` + video.id} target="_blank">${video.title}</a></li> | ||
${video.description ? html`<li>${video.description}</li>` : ''} | ||
<li><small> | ||
${video.channelTitle ? html`${video.channelTitle}, ` : ''} | ||
${video.views}${video.publishedAt ? html`, ${video.publishedAt}` : ''}</small></li> | ||
</ul> | ||
</li> | ||
` | ||
|
||
// Inserts a newline with the YouTube URL for every matched track | ||
function saveResults(event) { | ||
event.preventDefault() | ||
const fd = new FormData(event.target) | ||
const youtubeIds = [] | ||
for (const [_key, ytid] of fd.entries()) { | ||
youtubeIds.push(ytid) | ||
} | ||
const $output = event.target.querySelector('textarea') | ||
$output.value = youtubeIds.map(id => `https://www.youtube.com/watch?v=${id}`).join('\r\n') | ||
} | ||
|
||
customElements.define('spotify-to-youtube', SpotifyToYoutube) | ||
customElements.define('r4-batch-import', R4BatchImport) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { LitElement, html } from 'https://unpkg.com/lit?module' | ||
import { sdk } from 'https://cdn.jsdelivr.net/npm/@radio4000/sdk@latest/+esm' | ||
|
||
/** | ||
* @typedef {Object} Match | ||
* @property {string} spotifyId | ||
* @property {string} youtubeId | ||
* @property {string} title | ||
* @property {string} url | ||
*/ | ||
|
||
export default class R4BatchImport extends LitElement { | ||
static get properties() { | ||
return { | ||
matches: { type: Array }, | ||
channel: { type: Object, state: true }, | ||
} | ||
} | ||
|
||
async connectedCallback() { | ||
super.connectedCallback() | ||
const user = await sdk.users.readUser() | ||
if (user) this.setChannel() | ||
} | ||
|
||
async onSignIn({ detail }) { | ||
if (detail.error) throw new Error('Could not sign in') | ||
console.log(detail) | ||
this.setChannel() | ||
} | ||
|
||
async setChannel() { | ||
const { data: channels } = await sdk.channels.readUserChannels() | ||
this.channel = channels[0] || null | ||
} | ||
|
||
logout() { | ||
sdk.auth.signOut() | ||
this.channel = undefined | ||
} | ||
|
||
async submit(event) { | ||
this.loading = true | ||
event.preventDefault() | ||
console.log('import', this.matches) | ||
for (const x of this.matches) { | ||
await sdk.tracks.createTrack(this.channel.id, { | ||
url: x.url, | ||
title: x.title, | ||
}) | ||
} | ||
this.loading = false | ||
} | ||
|
||
render() { | ||
return html` | ||
${this.channel | ||
? html` | ||
${this.matches?.length | ||
? html` | ||
<p> | ||
Ready to import ${this.matches?.length} tracks (the ones above) to your Radio4000 channel: | ||
<strong>${this.channel.name}</strong> (@${this.channel.slug}) | ||
</p> | ||
<form @submit=${this.submit}> | ||
<button type="submit" ?disabled=${this.loading}>Import</button> | ||
</form> | ||
<br /> | ||
` | ||
: html`<p>Waiting for matches...</p>`} | ||
<button @click=${this.logout}>Logout of R4</button> | ||
` | ||
: html`<p>Sign in to Radio4000 to import a bunch of tracks</p> | ||
<r4-sign-in @submit=${this.onSignIn}></r4-sign-in>`} | ||
` | ||
} | ||
} |
Oops, something went wrong.