diff --git a/.gitignore b/.gitignore index a4494b6..2e018b6 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,3 @@ scripts/*.har scripts/collection_* node_modules -.vscode diff --git a/public/favicon.png b/public/favicon.png deleted file mode 100644 index de4ca71..0000000 Binary files a/public/favicon.png and /dev/null differ diff --git a/pug/album_grid.pug b/pug/album_grid.pug index 67a4be9..c196547 100644 --- a/pug/album_grid.pug +++ b/pug/album_grid.pug @@ -1,14 +1,11 @@ extends includes/layout.pug -block title - - title = `Albums | ${title}` - block view .mx-auto.w100.wmx11.fs-body1#content - .d-grid.gx8.gy12.jc-center.break-word(style="grid-template-columns: repeat(auto-fit, 210px)") + .d-grid.gx8.gy12.jc-center(style="grid-template-columns: repeat(auto-fit, 210px)") each item in items div - a.album-grid-link(href=item.item_url target="_blank") + a.album-grid-link(href=item.item_url) img(loading="lazy" src=item.item_art_url width=210 height=210) p.fs-body3.mb8= item.item_title .d-flex.fw-wrap.g4 diff --git a/pug/artist_grid.pug b/pug/artist_grid.pug index a3a3485..26c7614 100644 --- a/pug/artist_grid.pug +++ b/pug/artist_grid.pug @@ -1,8 +1,5 @@ extends includes/layout.pug -block title - - title = `Artists | ${title}` - block view .mx-auto.w100.wmx9.fs-body1#content .d-flex.fd-column.g4 @@ -27,5 +24,5 @@ block view span.s-tag--sponsor!= icons.get("flower", 16) = label each preview in item.previews - a.d-flex(href=preview.item_url target="_blank") + a.d-flex(href=preview.item_url) img(loading="lazy" src=preview.item_art_url width=210 height=210 style="height: auto; width: auto; max-height: 70px") diff --git a/pug/includes/layout.pug b/pug/includes/layout.pug index 1c15842..2f61f22 100644 --- a/pug/includes/layout.pug +++ b/pug/includes/layout.pug @@ -8,17 +8,10 @@ mixin navi(key, value, icon, text) doctype html html - - let searchText = !filter_field ? null : filter_field === "item_id" ? items[0].item_title : filter - head meta(charset="utf-8") - - let title = "BC Explorer" - block title - if searchText - - title = `${searchText} | ${title}` - title#title= title - link(rel="icon" href="/favicon.png") + title BC Explorer link(rel="stylesheet" type="text/css" href="/static/stacks.min.css") script(src="/static/htmx.js") script(src="/static/wordcloud.js") @@ -105,7 +98,11 @@ html if filter && filter_field .s-sidebarwidget.s-sidebarwidget__blue.d-flex.ai-center.gx16.jc-space-between.p8.pl16 != icons.get("search") - .fl-grow1 Searching for #[strong= searchText] + .fl-grow1= `Searching for ` + if filter_field === "item_id" + strong= items[0].item_title + else + strong= filter a.s-btn.s-notice--btn(href=and({filter: null, filter_field: null, filter_fuzzy: null})) Clear else form.d-flex.ai-stretch.gx8.jc-space-between.baw0(hx-indicator="#search-submit") diff --git a/pug/includes/tag-status.pug b/pug/includes/tag-status.pug index 047f594..06d6d77 100644 --- a/pug/includes/tag-status.pug +++ b/pug/includes/tag-status.pug @@ -1,4 +1,4 @@ -if downloader.total > 0 || downloader.outcome +if downloader.total > 0 #tag-download if !downloader.running && !downloader.outcome form.s-notice.s-notice__info.d-flex.ai-center.p8.gx16.pl16(role="status" hx-swap="outerHTML" hx-target="#tag-download" hx-post="/api/tag-download") @@ -15,7 +15,6 @@ if downloader.total > 0 || downloader.outcome #tag-status-indicator p.mt12 You can keep using BC Explorer while this continues in the background. - let percentage = `${Math.round(downloader.processed/downloader.total*100)}%` - title#title(hx-swap-oob="true") #{percentage} | Tags | BC Explorer .s-progress.mt16 .s-progress--bar(style=`width: ${percentage}`) .d-flex.jc-space-between.fs-fine @@ -24,14 +23,12 @@ if downloader.total > 0 || downloader.outcome else if downloader.outcome === "Success" .s-notice.s-notice__success.p8.gx16.pl16.d-flex.ai-center.wmn3 - title#title(hx-swap-oob="true") * Tags downloaded! | BC Explorer != icons.get("cloud-check") .fl-grow1 Tags downloaded. - - downloader.resolve() + - downloadManager.resolve(account) a.s-btn.s-btn__outlined(href="") Refresh else .s-notice.s-notice__danger.p8.gx16.pl16.d-flex.ai-center.wmn3 - title#title(hx-swap-oob="true") * Tag download failed! | BC Explorer != icons.get("cloud-xmark") .fl-grow1= downloader.outcome diff --git a/pug/label_grid.pug b/pug/label_grid.pug index 07fdf5e..9dd0ef9 100644 --- a/pug/label_grid.pug +++ b/pug/label_grid.pug @@ -1,8 +1,5 @@ extends includes/layout.pug -block title - - title = `Labels | ${title}` - block view .mx-auto.w100.wmx9.fs-body1#content .d-flex.fd-column.g4 @@ -27,5 +24,5 @@ block view span.s-tag--sponsor!= icons.get("compact-disc", 16) = item.total_duration each preview in item.previews - a.d-flex(href=preview.item_url target="_blank") + a.d-flex(href=preview.item_url) img(loading="lazy" src=preview.item_art_url width=210 height=210 style="height: auto; width: auto; max-height: 70px") diff --git a/pug/tag_grid.pug b/pug/tag_grid.pug index fd2c429..e1f8849 100644 --- a/pug/tag_grid.pug +++ b/pug/tag_grid.pug @@ -1,8 +1,5 @@ extends includes/layout.pug -block title - - title = `Tags | ${title}` - block view script | var items = diff --git a/pug/track_list.pug b/pug/track_list.pug index 1c8cffa..8556fe2 100644 --- a/pug/track_list.pug +++ b/pug/track_list.pug @@ -1,8 +1,5 @@ extends includes/layout.pug -block title - - title = `Tracks | ${title}` - block view .mx-auto.w100.wmx11#content .s-table-container @@ -21,7 +18,7 @@ block view | ☆ else = item.track_number || "-" - td: a(href=item.item_url target="_blank")= item.title + td: a(href=item.item_url)= item.title td= item.artist td= item.item_title - let label = item.band_url.replace(/https?:\/\/(.*?)\.bandcamp\.com.*/, "$1") diff --git a/readme.md b/readme.md index 5545bf0..f18bb3e 100644 --- a/readme.md +++ b/readme.md @@ -16,7 +16,7 @@ but the idea is you can more easily search your whole collection and play it str ## import more reliable statistics -by default, the data is mostly correct, but you can do this to get the exact data +the default data is pretty close, but you can do this to get the exact data 1. log into bandcamp in your browser 2. in the top right, click the button to view your collection, should take you to a url like https://bandcamp.com/cloudrac3r diff --git a/routes/load-tags.js b/routes/load-tags.js index 24cb492..a061158 100644 --- a/routes/load-tags.js +++ b/routes/load-tags.js @@ -8,33 +8,24 @@ const {sync, db, from, router} = require("../passthrough") const pugSync = sync.require("../pug-sync") const insertTag = db.prepare("INSERT OR IGNORE INTO item_tag (account, item_id, tag) VALUES (?, ?, ?)") -const insertTrack = db.prepare("INSERT OR IGNORE INTO track (account, item_id, track_id, title, artist, track_number, duration) VALUES (@account, @item_id, @track_id, @title, @artist, @track_number, @duration)") +const insertTrack = db.prepare("INSERT OR IGNORE INTO track (account, item_id, track_id, title, artist, track_number, duration) VALUES (?, ?, ?, ?, ?, ?, ?)") class TagDownloader extends sync.reloadClassMethods(() => TagDownloader) { constructor(account) { super() this.account = account this.processed = 0 - this.untaggedItems = [] + this.untaggedItems = db.prepare("SELECT account, item_id, item_title, item_url, band_name FROM item LEFT JOIN item_tag USING (account, item_id) WHERE account = ? AND tag IS NULL").all(account) this.total = this.untaggedItems.length this.running = false this.outcome = null - this.check() - } - - check() { - if (this.running) return // don't reduce the total while items are being processed, this will break the progress bar - this.untaggedItems = db.prepare("SELECT account, item_id, item_title, item_url, band_name FROM item LEFT JOIN item_tag USING (account, item_id) WHERE account = ? AND tag IS NULL").all(this.account) - this.total = this.untaggedItems.length } async _start() { if (this.running) return this.running = true - this.outcome = null - this.processed = 0 try { - for (const {account, item_id, item_type, item_title, item_url, band_name} of this.untaggedItems) { + for (const {account, item_id, item_title, item_url, band_name} of this.untaggedItems) { const res = await fetch(item_url) // delete unreachable items, otherwise it will perpetually try to download tags for them @@ -56,18 +47,21 @@ class TagDownloader extends sync.reloadClassMethods(() => TagDownloader) { })() // @ts-ignore - const tracklist = JSON.parse(doc.querySelector("script[data-tralbum]").getAttribute("data-tralbum")).trackinfo + const tracks = [...doc.querySelectorAll(".track_row_view").cache] db.transaction(() => { - for (const track of tracklist) { - insertTrack.run({ - account, - item_id, - track_id: track.id, - title: track.title, - artist: track.artist || band_name, - track_number: track.track_num, - duration: track.duration - }) + for (const track of tracks) { + const track_number = parseInt(track.querySelector(".track_number").textContent) + let title = track.querySelector(".track-title").textContent + let artist = band_name + const match = title.match(/^([^-]*) - (.*)$/) + if (match) { + artist = match[1] + title = match[2] + } + const duration = track.querySelector(".time").textContent.split(":").reverse().reduce((a, c, i) => 60**i * c + a, 0) + console.log(track_number, title, artist, duration) + if (!track_number || !title || !artist || !duration) continue + insertTrack.run(account, item_id, track_number, title, artist, track_number, duration) } })() @@ -81,10 +75,6 @@ class TagDownloader extends sync.reloadClassMethods(() => TagDownloader) { this.running = false } } - - resolve() { - this.outcome = null - } } const downloadManager = new class { @@ -93,13 +83,14 @@ const downloadManager = new class { /** @param {string} account */ check(account) { - const downloader = this.inProgressTagDownloads.get(account) || (() => { + return this.inProgressTagDownloads.get(account) || (() => { const downloader = new TagDownloader(account) this.inProgressTagDownloads.set(account, downloader) + setTimeout(() => { + this.resolve(account) + }) return downloader })() - downloader.check() - return downloader } /** @param {string} account */ @@ -125,13 +116,13 @@ const schema = z.object({ router.get("/api/tag-download", defineEventHandler(async event => { const {account} = await getValidatedQuery(event, schema.parse) const downloader = downloadManager.check(account) - return pugSync.render(event, "includes/tag-status.pug", {downloader, account}) + return pugSync.render(event, "includes/tag-status.pug", {downloadManager, downloader, account}) })) router.post("/api/tag-download", defineEventHandler(async event => { const {account} = await readValidatedBody(event, schema.parse) const downloader = downloadManager.start(account) - return pugSync.render(event, "includes/tag-status.pug", {downloader, account}) + return pugSync.render(event, "includes/tag-status.pug", {downloadManager, downloader, account}) })) module.exports.downloadManager = downloadManager diff --git a/start.js b/start.js index d850987..697ca2c 100644 --- a/start.js +++ b/start.js @@ -66,13 +66,5 @@ router.get("/static/wordcloud.js", defineEventHandler({ } })) -router.get("/favicon.png", defineEventHandler({ - handler: async event => { - handleCacheHeaders(event, {maxAge: 86400}) - defaultContentType(event, "text/javascript") - return fs.promises.readFile("public/favicon.png") - } -})) - createServer(toNodeListener(app)).listen(2239) console.log("running on http://localhost:2239")