From 762d2b81ff4fcb13e85061087abf06a2ef6421aa Mon Sep 17 00:00:00 2001 From: fullsizemalt <106900403+fullsizemalt@users.noreply.github.com> Date: Sun, 28 Dec 2025 22:36:52 -0800 Subject: [PATCH] feat: Add MSI, SCI, Disco Biscuits importers + refactor About page to be band-agnostic --- backend/importers/base.py | 29 +++-- backend/importers/setlistfm.py | 134 ++++++++++++++++++++++- backend/scripts/find_artist.py | 48 ++++++++ frontend/app/about/page.tsx | 37 ++++--- frontend/app/artists/[slug]/page.tsx | 4 +- frontend/app/layout.tsx | 9 +- frontend/app/privacy/page.tsx | 20 ++-- frontend/app/robots.ts | 2 +- frontend/app/settings/page.tsx | 2 +- frontend/app/sitemap.ts | 2 +- frontend/app/terms/page.tsx | 14 +-- frontend/app/welcome/page.tsx | 2 +- frontend/components/ui/search-dialog.tsx | 2 +- 13 files changed, 249 insertions(+), 56 deletions(-) create mode 100644 backend/scripts/find_artist.py diff --git a/backend/importers/base.py b/backend/importers/base.py index bd78f1c..d31dd09 100644 --- a/backend/importers/base.py +++ b/backend/importers/base.py @@ -182,16 +182,25 @@ class ImporterBase(ABC): else: # Include vertical slug in song slug for cross-band uniqueness song_slug = f"{vertical.slug}-{generate_slug(title)}" - song = Song( - title=title, - slug=song_slug, - original_artist=original_artist, - vertical_id=vertical.id - ) - self.session.add(song) - self.session.commit() - self.session.refresh(song) - song_id = song.id + + # Check if slug exists (handle simple case variations) + existing_slug = self.session.exec( + select(Song).where(Song.slug == song_slug) + ).first() + + if existing_slug: + song_id = existing_slug.id + else: + song = Song( + title=title, + slug=song_slug, + original_artist=original_artist, + vertical_id=vertical.id + ) + self.session.add(song) + self.session.commit() + self.session.refresh(song) + song_id = song.id if external_id: self.song_map[external_id] = song_id diff --git a/backend/importers/setlistfm.py b/backend/importers/setlistfm.py index 3c36b39..f2075cf 100644 --- a/backend/importers/setlistfm.py +++ b/backend/importers/setlistfm.py @@ -246,6 +246,72 @@ class BillyStringsImporter(SetlistFmImporter): ARTIST_MBID = "640db492-34c4-47df-be14-96e2cd4b9fe4" +class JoeRussosAlmostDeadImporter(SetlistFmImporter): + """Import Joe Russo's Almost Dead data from Setlist.fm""" + + VERTICAL_NAME = "Joe Russo's Almost Dead" + VERTICAL_SLUG = "jrad" + VERTICAL_DESCRIPTION = "Joe Russo's Almost Dead is an American rock band formed in 2013 that interprets the music of the Grateful Dead." + + # JRAD MusicBrainz ID + ARTIST_MBID = "84a69823-3d4f-4ede-b43f-17f85513181a" + + +class EggyImporter(SetlistFmImporter): + """Import Eggy data from Setlist.fm""" + + VERTICAL_NAME = "Eggy" + VERTICAL_SLUG = "eggy" + VERTICAL_DESCRIPTION = "Connecticut jam band formed in 2014. Known for improvisational rock and explosive live shows." + + # Eggy MusicBrainz ID + ARTIST_MBID = "ba0b9dc6-bd61-42c7-a28f-5179b1c04391" + + +class DogsInAPileImporter(SetlistFmImporter): + """Import Dogs in a Pile data from Setlist.fm""" + + VERTICAL_NAME = "Dogs in a Pile" + VERTICAL_SLUG = "dogs-in-a-pile" + VERTICAL_DESCRIPTION = "New Jersey jam band. Young and energetic." + + # Dogs in a Pile MusicBrainz ID + ARTIST_MBID = "a05236ee-3fac-45d7-96f5-b2cd6d03fda9" + + +class TheDiscoBiscuitsImporter(SetlistFmImporter): + """Import The Disco Biscuits data from Setlist.fm""" + + VERTICAL_NAME = "The Disco Biscuits" + VERTICAL_SLUG = "disco-biscuits" + VERTICAL_DESCRIPTION = "Philadelphia trance-fusion jam band. Pioneers of livetronica." + + # The Disco Biscuits MusicBrainz ID + ARTIST_MBID = "4e43632a-afef-4b54-a822-26311110d5c5" + + +class TheStringCheeseIncidentImporter(SetlistFmImporter): + """Import The String Cheese Incident data from Setlist.fm""" + + VERTICAL_NAME = "The String Cheese Incident" + VERTICAL_SLUG = "sci" + VERTICAL_DESCRIPTION = "Colorado jam band formed in 1993. Known for bluegrass-infused improvisational rock." + + # SCI MusicBrainz ID + ARTIST_MBID = "cff95140-6d57-498a-8834-10eb72865b29" + + +class MindlessSelfIndulgenceImporter(SetlistFmImporter): + """Import Mindless Self Indulgence data from Setlist.fm""" + + VERTICAL_NAME = "Mindless Self Indulgence" + VERTICAL_SLUG = "msi" + VERTICAL_DESCRIPTION = "New York City electronic rock band. Known for their high-energy, chaotic style." + + # MSI MusicBrainz ID + ARTIST_MBID = "44f42386-a733-4b51-8298-fe5c807d03aa" + + def main_dead_and_company(): """Run Dead & Company import""" from database import engine @@ -264,6 +330,60 @@ def main_billy_strings(): importer.import_all() +def main_jrad(): + """Run JRAD import""" + from database import engine + + with Session(engine) as session: + importer = JoeRussosAlmostDeadImporter(session) + importer.import_all() + + +def main_eggy(): + """Run Eggy import""" + from database import engine + + with Session(engine) as session: + importer = EggyImporter(session) + importer.import_all() + + +def main_dogs(): + """Run Dogs in a Pile import""" + from database import engine + + with Session(engine) as session: + importer = DogsInAPileImporter(session) + importer.import_all() + + +def main_biscuits(): + """Run The Disco Biscuits import""" + from database import engine + + with Session(engine) as session: + importer = TheDiscoBiscuitsImporter(session) + importer.import_all() + + +def main_sci(): + """Run SCI import""" + from database import engine + + with Session(engine) as session: + importer = TheStringCheeseIncidentImporter(session) + importer.import_all() + + +def main_msi(): + """Run Mindless Self Indulgence import""" + from database import engine + + with Session(engine) as session: + importer = MindlessSelfIndulgenceImporter(session) + importer.import_all() + + if __name__ == "__main__": import sys if len(sys.argv) > 1: @@ -271,5 +391,17 @@ if __name__ == "__main__": main_dead_and_company() elif sys.argv[1] == "bmfs": main_billy_strings() + elif sys.argv[1] == "jrad": + main_jrad() + elif sys.argv[1] == "eggy": + main_eggy() + elif sys.argv[1] == "dogs": + main_dogs() + elif sys.argv[1] == "biscuits": + main_biscuits() + elif sys.argv[1] == "sci": + main_sci() + elif sys.argv[1] == "msi": + main_msi() else: - print("Usage: python -m importers.setlistfm [deadco|bmfs]") + print("Usage: python -m importers.setlistfm [deadco|bmfs|jrad|eggy|dogs|biscuits|sci|msi]") diff --git a/backend/scripts/find_artist.py b/backend/scripts/find_artist.py new file mode 100644 index 0000000..00e64cd --- /dev/null +++ b/backend/scripts/find_artist.py @@ -0,0 +1,48 @@ + +import os +import sys +import requests +from dotenv import load_dotenv + +load_dotenv() + +API_KEY = os.getenv("SETLISTFM_API_KEY") +BASE_URL = "https://api.setlist.fm/rest/1.0" + +def search_artist(name): + print(f"Searching for '{name}'...") + headers = { + "Accept": "application/json", + "x-api-key": API_KEY + } + + url = f"{BASE_URL}/search/artists" + params = {"artistName": name, "sort": "relevance"} + + try: + response = requests.get(url, headers=headers, params=params) + response.raise_for_status() + data = response.json() + + artists = data.get("artist", []) + if not artists: + print("No artists found.") + return + + print(f"Found {len(artists)} results:") + for artist in artists[:3]: + print(f" - Name: {artist.get('name')}") + print(f" MBID: {artist.get('mbid')}") + print(f" URL: {artist.get('url')}") + print(f" Disambiguation: {artist.get('disambiguation', 'N/A')}") + print("-" * 20) + + except Exception as e: + print(f"Error: {e}") + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: python find_artist.py ") + sys.exit(1) + + search_artist(sys.argv[1]) diff --git a/frontend/app/about/page.tsx b/frontend/app/about/page.tsx index c181996..b35589b 100644 --- a/frontend/app/about/page.tsx +++ b/frontend/app/about/page.tsx @@ -6,9 +6,9 @@ export default function AboutPage() {
{/* Header */}
-

About Elmeg

+

About Fediversion

- A comprehensive community-driven archive dedicated to preserving the history and evolution of the band Goose. + The unified, community-driven platform for the entire Jam Scene.

@@ -22,9 +22,10 @@ export default function AboutPage() {

Our Mission

- Elmeg is a collaborative effort, growing organically through the contributions of the flock. - We believe that every performance is a shared experience. Our goal is to build a mycelium-like network - of information, where every setlist, note, and rating helps others discover the magic of the music. + Fediversion is a collaborative effort to bring the entire scene together under one roof. + We believe that the magic of live music transcends any single band. Our goal is to create a seamless, + interconnected archive where fans can track their stats, rate performances, and discover new music + across the entire spectrum of the jam universe.

@@ -37,33 +38,33 @@ export default function AboutPage() {

Heady Inspiration

- The soul of Elmeg's performance rating system is directly inspired by Heady Version. + The soul of our rating system is directly inspired by Heady Version. We've adopted the concept of "Heady Versions" to help fans identify and celebrate the definitive - takes on their favorite Goose songs. + takes on songs, now expanded across every band in the scene.

-

The Dead Heritage

+

The Taper Heritage

We stand on the shoulders of giants. The culture of meticulous documentation pioneered by the - legendary fans of the Grateful Dead. Elmeg continues this tradition, - bringing the spirit of the Tapestry into the modern era. + legendary fans of the Grateful Dead. Fediversion continues this tradition, + bringing the spirit of the Tapestry into the modern era for a new generation of fans.

-

Data & API

+

Community Data

- We are incredibly grateful to elgoose.net for providing their comprehensive - API. Their dedication to documenting the band's history makes the archival work of Elmeg possible - for all fans to enjoy. + We are incredibly grateful to the open data communities that make this possible, including + Setlist.fm, Phish.net, Elgoose.net, and others. + Their dedication to documenting history allows us to build this unified home for all fans.

@@ -75,9 +76,9 @@ export default function AboutPage() {

Official Disclaimer

- Elmeg is an independent, non-commercial community archive. - This site is NOT endorsed by, affiliated with, or sponsored by Goose or - their management. All trade names, trademarks, and band imagery are the property of their respective owners. + Fediversion is an independent, non-commercial community archive. + This site is NOT endorsed by, affiliated with, or sponsored by any of the bands featured herein. + All trade names, trademarks, and band imagery are the property of their respective owners. We are simply fans celebrating the music.

@@ -88,7 +89,7 @@ export default function AboutPage() { {/* Footer Note */}

- "Built by the flock, for the flock." + "Built by fans, for the scene."

diff --git a/frontend/app/artists/[slug]/page.tsx b/frontend/app/artists/[slug]/page.tsx index 214ae5b..b2a078c 100644 --- a/frontend/app/artists/[slug]/page.tsx +++ b/frontend/app/artists/[slug]/page.tsx @@ -30,8 +30,8 @@ export async function generateMetadata({ params }: ArtistPageProps): Promise -