Created
October 19, 2022 13:23
-
-
Save joshdavenport/184ec5c89a766f931b0a11aef2e19e46 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
// Name: Bandcamp Album Player | |
// Description: Stream a bandcamp album | |
// Author: Josh Davenport-Smith | |
// Twitter: @jdprts | |
import "@johnlindquist/kit"; | |
import { WidgetAPI } from "@johnlindquist/kit/types/pro"; | |
const jsdom = await npm('jsdom'); | |
const INITIAL_PLAYER_WIDTH = 500; | |
const MAX_PLAYER_WIDTH = 740; | |
const INITIAL_PLAYER_HEIGHT = 120; | |
const htmlDecode = (input: string) => { | |
const doc = new jsdom.JSDOM(`<!DOCTYPE html><div>${input}</div>`); | |
return doc.window.document.querySelector('div').textContent; | |
} | |
const getStyles = () => ` | |
:root { | |
--dragbar-width: 30px; | |
} | |
iframe { | |
position: absolute; | |
left: var(--dragbar-width); | |
top: 0; | |
width: calc(100% - var(--dragbar-width)); | |
height: 100%; | |
-webkit-app-region: no-drag; | |
} | |
#dragbar { | |
position: absolute; | |
top: 0; | |
left: 0; | |
bottom: 0; | |
width: var(--dragbar-width); | |
cursor: grab; | |
background: #F1F1F1; | |
} | |
#dragbar:after { | |
content: ''; | |
position: absolute; | |
inset: 6px; | |
background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1IiBoZWlnaHQ9IjUiPgo8cmVjdCB3aWR0aD0iNSIgaGVpZ2h0PSI1IiBmaWxsPSIjRjFGMUYxIj48L3JlY3Q+CjxyZWN0IHdpZHRoPSIyIiBoZWlnaHQ9IjIiIGZpbGw9IiNjY2MiPjwvcmVjdD4KPC9zdmc+"); | |
} | |
#dragbar:active { | |
cursor: grabbing; | |
} | |
`; | |
const getScripts = (albumId: number) => ` | |
(() => { | |
const url = 'https://bandcamp.com/EmbeddedPlayer/album=${albumId}/size=large/bgcol=ffffff/linkcol=0687f5/tracklist=false/artwork=small/transparent=true/'; | |
const iframe = document.createElement('iframe'); | |
iframe.setAttribute('src', url); | |
iframe.setAttribute('seamless', true); | |
document.body.appendChild(iframe); | |
})(); | |
`; | |
// Obtain the album (or track) URL | |
const albumUrl = await arg('What is the album URL?'); | |
// Bail if the URL is invalid | |
if (!albumUrl.includes('bandcamp.com/album') && !albumUrl.includes('bandcamp.com/track')) { | |
throw new Error('Sorry, I don\'t recognise that URL'); | |
} | |
// Give feedback that we're metadata for the album | |
div(md(` | |
Loading album... | |
--- | |
Note: Some URLs will not work because of permissions set by the artist | |
`)); | |
// Grab the bandcamp page for bc-page-properties which stores meta about the album | |
const pageContents = await fetch(albumUrl).then((res) => res.text()); | |
const bandcampMetaParse = pageContents.match(/<meta name="bc-page-properties".*?content="([^"]+)">/); | |
await Promise.all([ | |
// Give the user a chance to read the message (some URLs will not work and there's no obious way to tell in advance) | |
new Promise((resolve) => setTimeout(resolve, 2000)), | |
// Also wait for the data to load, so we continue when both promises resolve | |
pageContents, | |
]) | |
if (!bandcampMetaParse) { | |
throw new Error('Sorry, I couldn\'t find the album ID'); | |
} | |
// Parse the meta we found | |
const bandcampMeta: { item_type?: string, item_id?: number, tralbum_page_version?: number } | undefined = JSON.parse(htmlDecode(bandcampMetaParse[1])); | |
if (!bandcampMeta?.item_id) { | |
throw new Error('Sorry, I couldn\'t find the album ID'); | |
} | |
// Create a widget with the player! | |
const w: WidgetAPI = await widget( | |
` | |
<div id="dragbar"></div> | |
<style type="text/css">${getStyles()}</style> | |
<script type="text/javascript">${getScripts(bandcampMeta.item_id)}</script> | |
`, | |
{ | |
alwaysOnTop: true, | |
width: INITIAL_PLAYER_WIDTH, | |
maxWidth: MAX_PLAYER_WIDTH, | |
height: INITIAL_PLAYER_HEIGHT, | |
minHeight: INITIAL_PLAYER_HEIGHT, | |
maxHeight: INITIAL_PLAYER_HEIGHT, | |
roundedCorners: false, | |
thickFrame: false, | |
} | |
); | |
w.setSize(INITIAL_PLAYER_WIDTH, INITIAL_PLAYER_HEIGHT); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment