feat: Add MSI, SCI, Disco Biscuits importers + refactor About page to be band-agnostic
Some checks failed
Deploy Fediversion / deploy (push) Failing after 1s

This commit is contained in:
fullsizemalt 2025-12-28 22:36:52 -08:00
parent 1a9c89e1f1
commit 762d2b81ff
13 changed files with 249 additions and 56 deletions

View file

@ -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

View file

@ -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]")

View file

@ -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 <artist_name>")
sys.exit(1)
search_artist(sys.argv[1])

View file

@ -6,9 +6,9 @@ export default function AboutPage() {
<div className="flex flex-col gap-12 max-w-4xl mx-auto py-8">
{/* Header */}
<section className="text-center space-y-4">
<h1 className="text-4xl font-extrabold tracking-tight sm:text-5xl">About Elmeg</h1>
<h1 className="text-4xl font-extrabold tracking-tight sm:text-5xl">About Fediversion</h1>
<p className="text-xl text-muted-foreground max-w-2xl mx-auto">
A comprehensive community-driven archive dedicated to preserving the history and evolution of the band <span className="text-foreground font-semibold">Goose</span>.
The unified, community-driven platform for the entire <span className="text-foreground font-semibold">Jam Scene</span>.
</p>
</section>
@ -22,9 +22,10 @@ export default function AboutPage() {
<h2 className="text-2xl font-bold">Our Mission</h2>
</div>
<p className="text-lg leading-relaxed text-muted-foreground">
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.
</p>
</CardContent>
</Card>
@ -37,33 +38,33 @@ export default function AboutPage() {
<h2 className="text-xl font-bold">Heady Inspiration</h2>
</div>
<p className="text-sm text-muted-foreground leading-relaxed">
The soul of Elmeg's performance rating system is directly inspired by <strong>Heady Version</strong>.
The soul of our rating system is directly inspired by <strong>Heady Version</strong>.
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.
</p>
</section>
<section className="space-y-4">
<div className="flex items-center gap-3">
<Music className="h-6 w-6 text-blue-500" />
<h2 className="text-xl font-bold">The Dead Heritage</h2>
<h2 className="text-xl font-bold">The Taper Heritage</h2>
</div>
<p className="text-sm text-muted-foreground leading-relaxed">
We stand on the shoulders of giants. The culture of meticulous documentation pioneered by the
legendary fans of the <strong>Grateful Dead</strong>. Elmeg continues this tradition,
bringing the spirit of the Tapestry into the modern era.
legendary fans of the <strong>Grateful Dead</strong>. Fediversion continues this tradition,
bringing the spirit of the Tapestry into the modern era for a new generation of fans.
</p>
</section>
<section className="space-y-4">
<div className="flex items-center gap-3">
<Music className="h-6 w-6 text-green-500" />
<h2 className="text-xl font-bold">Data & API</h2>
<h2 className="text-xl font-bold">Community Data</h2>
</div>
<p className="text-sm text-muted-foreground leading-relaxed">
We are incredibly grateful to <strong>elgoose.net</strong> for providing their comprehensive
API. Their dedication to documenting the band&apos;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
<strong> Setlist.fm</strong>, <strong>Phish.net</strong>, <strong>Elgoose.net</strong>, and others.
Their dedication to documenting history allows us to build this unified home for all fans.
</p>
</section>
</div>
@ -75,9 +76,9 @@ export default function AboutPage() {
<div className="space-y-2">
<h3 className="font-bold text-yellow-600 dark:text-yellow-400 uppercase tracking-wider text-sm">Official Disclaimer</h3>
<p className="text-sm text-yellow-800 dark:text-yellow-100/80 leading-relaxed">
Elmeg is an independent, non-commercial community archive.
This site is <strong>NOT</strong> endorsed by, affiliated with, or sponsored by <strong>Goose</strong> 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 <strong>NOT</strong> 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.
</p>
</div>
@ -88,7 +89,7 @@ export default function AboutPage() {
{/* Footer Note */}
<section className="text-center pt-8 border-t">
<p className="text-sm text-muted-foreground italic">
"Built by the flock, for the flock."
"Built by fans, for the scene."
</p>
</section>
</div >

View file

@ -30,8 +30,8 @@ export async function generateMetadata({ params }: ArtistPageProps): Promise<Met
if (!data) return { title: "Artist Not Found" }
return {
title: `${data.artist.name} | Elmeg`,
description: data.artist.bio || `Artist profile for ${data.artist.name} on Elmeg.`,
title: `${data.artist.name} | Fediversion`,
description: data.artist.bio || `Artist profile for ${data.artist.name} on Fediversion.`,
}
}

View file

@ -21,7 +21,10 @@ const jetbrainsMono = JetBrains_Mono({
});
export const metadata: Metadata = {
title: "Fediversion",
title: {
default: "Fediversion",
template: "%s | Fediversion",
},
description: "The ultimate HeadyVersion platform for all jam bands",
};
@ -37,11 +40,11 @@ export default function RootLayout({
jetbrainsMono.variable,
"min-h-screen bg-background font-sans antialiased flex flex-col"
)}>
<Script
{/* <Script
defer
src="https://stats.elmeg.xyz/stats"
data-website-id="4338bbf0-fe74-4256-8973-8cdc0cffe08c"
/>
/> */}
<ThemeProvider
attribute="class"
defaultTheme="dark"

View file

@ -1,21 +1,21 @@
import { Metadata } from "next"
export const metadata: Metadata = {
title: "Privacy Policy - Elmeg",
description: "Privacy Policy for Elmeg, a community archive platform for live music fans.",
title: "Privacy Policy - Fediversion",
description: "Privacy Policy for Fediversion, a community archive platform for live music fans.",
}
export default function PrivacyPage() {
return (
<div className="max-w-3xl mx-auto py-8">
<h1 className="text-4xl font-bold mb-2">Privacy Policy</h1>
<p className="text-muted-foreground mb-8">Last updated: December 21, 2024</p>
<p className="text-muted-foreground mb-8">Last updated: December 28, 2025</p>
<div className="prose prose-neutral dark:prose-invert max-w-none space-y-8">
<section>
<h2 className="text-2xl font-semibold mb-4">1. Introduction</h2>
<p className="text-muted-foreground leading-relaxed">
Elmeg ("we," "our," or "us") respects your privacy and is committed to protecting your
Fediversion ("we," "our," or "us") respects your privacy and is committed to protecting your
personal data. This Privacy Policy explains how we collect, use, disclose, and safeguard
your information when you use our Service.
</p>
@ -119,8 +119,8 @@ export default function PrivacyPage() {
</ul>
<p className="mt-3">
To exercise these rights, contact us at{" "}
<a href="mailto:privacy@elmeg.xyz" className="text-primary hover:underline">
privacy@elmeg.xyz
<a href="mailto:privacy@fediversion.runfoo.run" className="text-primary hover:underline">
privacy@fediversion.runfoo.run
</a>.
</p>
</div>
@ -174,13 +174,13 @@ export default function PrivacyPage() {
<p>If you have questions about this Privacy Policy or our data practices, contact us at:</p>
<div className="mt-4 p-4 bg-muted/50 rounded-lg">
<p><strong className="text-foreground">Email:</strong>{" "}
<a href="mailto:privacy@elmeg.xyz" className="text-primary hover:underline">
privacy@elmeg.xyz
<a href="mailto:privacy@fediversion.runfoo.run" className="text-primary hover:underline">
privacy@fediversion.runfoo.run
</a>
</p>
<p className="mt-2"><strong className="text-foreground">General Support:</strong>{" "}
<a href="mailto:support@elmeg.xyz" className="text-primary hover:underline">
support@elmeg.xyz
<a href="mailto:support@fediversion.runfoo.run" className="text-primary hover:underline">
support@fediversion.runfoo.run
</a>
</p>
</div>

View file

@ -7,6 +7,6 @@ export default function robots(): MetadataRoute.Robots {
allow: '/',
disallow: ['/admin/', '/api/'],
},
sitemap: 'https://elmeg.xyz/sitemap.xml',
sitemap: 'https://fediversion.runfoo.run/sitemap.xml',
}
}

View file

@ -678,7 +678,7 @@ function PrivacySection({ settings, onChange }: {
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'my-elmeg-data.json'
a.download = 'my-fediversion-data.json'
a.click()
URL.revokeObjectURL(url)
}

View file

@ -1,7 +1,7 @@
import { MetadataRoute } from 'next'
export default function sitemap(): MetadataRoute.Sitemap {
const baseUrl = 'https://elmeg.xyz'
const baseUrl = 'https://fediversion.runfoo.run'
const routes = [
'',

View file

@ -1,21 +1,21 @@
import { Metadata } from "next"
export const metadata: Metadata = {
title: "Terms of Service - Elmeg",
description: "Terms of Service for Elmeg, a community archive platform for live music fans.",
title: "Terms of Service - Fediversion",
description: "Terms of Service for Fediversion, a community archive platform for live music fans.",
}
export default function TermsPage() {
return (
<div className="max-w-3xl mx-auto py-8">
<h1 className="text-4xl font-bold mb-2">Terms of Service</h1>
<p className="text-muted-foreground mb-8">Last updated: December 21, 2024</p>
<p className="text-muted-foreground mb-8">Last updated: December 28, 2025</p>
<div className="prose prose-neutral dark:prose-invert max-w-none space-y-8">
<section>
<h2 className="text-2xl font-semibold mb-4">1. Acceptance of Terms</h2>
<p className="text-muted-foreground leading-relaxed">
By accessing or using Elmeg ("the Service"), you agree to be bound by these Terms of Service.
By accessing or using Fediversion ("the Service"), you agree to be bound by these Terms of Service.
If you do not agree to these terms, please do not use the Service. We reserve the right to
update these terms at any time, and your continued use of the Service constitutes acceptance
of any changes.
@ -25,7 +25,7 @@ export default function TermsPage() {
<section>
<h2 className="text-2xl font-semibold mb-4">2. Description of Service</h2>
<p className="text-muted-foreground leading-relaxed">
Elmeg is a community-driven archive platform for live music enthusiasts. The Service allows
Fediversion is a community-driven archive platform for live music enthusiasts. The Service allows
users to browse setlists, rate performances, participate in discussions, and contribute to
the archive. The Service is provided "as is" and we make no guarantees regarding availability,
accuracy, or completeness of content.
@ -140,8 +140,8 @@ export default function TermsPage() {
<h2 className="text-2xl font-semibold mb-4">11. Contact</h2>
<p className="text-muted-foreground leading-relaxed">
If you have questions about these Terms of Service, please contact us at{" "}
<a href="mailto:support@elmeg.xyz" className="text-primary hover:underline">
support@elmeg.xyz
<a href="mailto:support@fediversion.runfoo.run" className="text-primary hover:underline">
support@fediversion.runfoo.run
</a>.
</p>
</section>

View file

@ -15,7 +15,7 @@ import { getApiUrl } from "@/lib/api-config"
const STEPS = [
{
id: "intro",
title: "Welcome to Elmeg",
title: "Welcome to Fediversion",
description: "Let's get you set up to discover and track your favorite shows.",
icon: PartyPopper
},

View file

@ -81,7 +81,7 @@ export function SearchDialog() {
className="inline-flex items-center rounded-lg font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border border-input bg-background/50 shadow-sm hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2 relative w-full justify-start text-sm text-muted-foreground sm:pr-12 md:w-40 lg:w-64"
>
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<span className="hidden lg:inline-flex">Search Elmeg...</span>
<span className="hidden lg:inline-flex">Search Fediversion...</span>
<span className="inline-flex lg:hidden">Search...</span>
<kbd className="pointer-events-none absolute right-1.5 top-1.5 hidden h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium opacity-100 sm:flex">
<span className="text-xs"></span>K