new
This commit is contained in:
parent
be789cb732
commit
c23eb924c3
85 changed files with 7090 additions and 253 deletions
22
archive.py
22
archive.py
|
|
@ -1,31 +1,35 @@
|
||||||
|
# archive.py
|
||||||
import logging
|
import logging
|
||||||
from waybackpy import WaybackMachineSaveAPI
|
|
||||||
import time
|
|
||||||
import random
|
import random
|
||||||
|
import time
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from waybackpy import WaybackMachineSaveAPI
|
||||||
|
|
||||||
from config import ARCHIVE_URLS, USER_AGENT
|
from config import ARCHIVE_URLS, USER_AGENT
|
||||||
|
|
||||||
# Configure logging
|
|
||||||
logging.basicConfig(
|
|
||||||
level=logging.INFO,
|
|
||||||
format='%(asctime)s - %(levelname)s - %(message)s'
|
|
||||||
)
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def archive_url(url):
|
|
||||||
|
def archive_url(url: str):
|
||||||
logger.info(f"🌐 Archiving {url} ...")
|
logger.info(f"🌐 Archiving {url} ...")
|
||||||
try:
|
try:
|
||||||
save_api = WaybackMachineSaveAPI(url, user_agent=USER_AGENT)
|
save_api = WaybackMachineSaveAPI(url, user_agent=USER_AGENT)
|
||||||
save_api.save()
|
save_api.save()
|
||||||
logger.info(f"✅ Archived {url}")
|
logger.info(f"✅ Archived {url}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"⚠️ Exception archiving {url}: {e}")
|
logger.error(f"⚠️ Exception archiving {url}: {e}", exc_info=True)
|
||||||
|
|
||||||
|
|
||||||
def archive_all_urls():
|
def archive_all_urls():
|
||||||
|
logger.info("--- Starting archival process for all URLs ---")
|
||||||
for url in ARCHIVE_URLS:
|
for url in ARCHIVE_URLS:
|
||||||
delay = 10 + random.uniform(-3, 3)
|
delay = 10 + random.uniform(-3, 3)
|
||||||
|
logger.info(f"Waiting {delay:.2f} seconds before next archive...")
|
||||||
time.sleep(delay)
|
time.sleep(delay)
|
||||||
archive_url(url)
|
archive_url(url)
|
||||||
|
logger.info("--- Archival process finished ---")
|
||||||
|
|
||||||
|
|
||||||
def test_archive():
|
def test_archive():
|
||||||
test_url = "https://httpbin.org/anything/foo/bar"
|
test_url = "https://httpbin.org/anything/foo/bar"
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ CSV_FILENAME = "artists.csv"
|
||||||
XLSX_FILENAME = "artists.xlsx"
|
XLSX_FILENAME = "artists.xlsx"
|
||||||
|
|
||||||
exclude_names = {
|
exclude_names = {
|
||||||
|
"🎹Worst Comps & Edits"
|
||||||
"K4$H K4$$!n0",
|
"K4$H K4$$!n0",
|
||||||
"K4HKn0",
|
"K4HKn0",
|
||||||
"AI Models",
|
"AI Models",
|
||||||
|
|
|
||||||
43
diff.py
43
diff.py
|
|
@ -1,22 +1,37 @@
|
||||||
|
# diff.py
|
||||||
import csv
|
import csv
|
||||||
|
import logging
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
def read_csv_to_dict(filename):
|
logger = logging.getLogger(__name__)
|
||||||
d = {}
|
|
||||||
with open(filename, newline='', encoding='utf-8') as f:
|
|
||||||
|
def read_csv_to_dict(filename: str) -> Dict[str, Dict[str, str]]:
|
||||||
|
data = {}
|
||||||
|
try:
|
||||||
|
with open(filename, newline="", encoding="utf-8") as f:
|
||||||
reader = csv.DictReader(f)
|
reader = csv.DictReader(f)
|
||||||
for row in reader:
|
for row in reader:
|
||||||
d[row["Artist Name"]] = row
|
if "Artist Name" in row and row["Artist Name"]:
|
||||||
return d
|
data[row["Artist Name"]] = row
|
||||||
|
except FileNotFoundError:
|
||||||
|
logger.warning(f"CSV file not found: {filename}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error reading CSV file {filename}: {e}", exc_info=True)
|
||||||
|
return data
|
||||||
|
|
||||||
def detect_changes(old_data, new_data):
|
|
||||||
|
def detect_changes(
|
||||||
|
old_data: Dict[str, Dict[str, str]], new_data: Dict[str, Dict[str, str]]
|
||||||
|
) -> List[str]:
|
||||||
changes = []
|
changes = []
|
||||||
|
|
||||||
old_keys = set(old_data.keys())
|
old_keys = set(old_data.keys())
|
||||||
new_keys = set(new_data.keys())
|
new_keys = set(new_data.keys())
|
||||||
|
|
||||||
removed = old_keys - new_keys
|
removed = sorted(list(old_keys - new_keys))
|
||||||
added = new_keys - old_keys
|
added = sorted(list(new_keys - old_keys))
|
||||||
common = old_keys & new_keys
|
common = sorted(list(old_keys & new_keys))
|
||||||
|
|
||||||
for artist in removed:
|
for artist in removed:
|
||||||
changes.append(f"❌ Removed: **{artist}**")
|
changes.append(f"❌ Removed: **{artist}**")
|
||||||
|
|
@ -28,15 +43,15 @@ def detect_changes(old_data, new_data):
|
||||||
old_row = old_data[artist]
|
old_row = old_data[artist]
|
||||||
new_row = new_data[artist]
|
new_row = new_data[artist]
|
||||||
|
|
||||||
if old_row["URL"] != new_row["URL"]:
|
if old_row.get("URL") != new_row.get("URL"):
|
||||||
changes.append(f"🔗 Link changed for **{artist}**")
|
changes.append(f"🔗 Link changed for **{artist}**")
|
||||||
if old_row["Credit"] != new_row["Credit"]:
|
if old_row.get("Credit") != new_row.get("Credit"):
|
||||||
changes.append(f"✏️ Credit changed for **{artist}**")
|
changes.append(f"✏️ Credit changed for **{artist}**")
|
||||||
if old_row["Links Work"] != new_row["Links Work"]:
|
if old_row.get("Links Work") != new_row.get("Links Work"):
|
||||||
changes.append(f"🔄 Links Work status changed for **{artist}**")
|
changes.append(f"🔄 Links Work status changed for **{artist}**")
|
||||||
if old_row["Updated"] != new_row["Updated"]:
|
if old_row.get("Updated") != new_row.get("Updated"):
|
||||||
changes.append(f"🕒 Updated date changed for **{artist}**")
|
changes.append(f"🕒 Updated date changed for **{artist}**")
|
||||||
if old_row["Best"] != new_row["Best"]:
|
if old_row.get("Best") != new_row.get("Best"):
|
||||||
changes.append(f"⭐ Best flag changed for **{artist}**")
|
changes.append(f"⭐ Best flag changed for **{artist}**")
|
||||||
|
|
||||||
return changes
|
return changes
|
||||||
|
|
@ -1,36 +1,42 @@
|
||||||
import requests, zipfile
|
# downloader.py
|
||||||
|
import logging
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from config import HTML_FILENAME, XLSX_FILENAME, XLSX_URL, ZIP_FILENAME, ZIP_URL
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _download_file(url: str, filename: str, timeout: int = 30) -> bool:
|
||||||
|
logger.info(f"🔄 Downloading {filename}...")
|
||||||
|
try:
|
||||||
|
with requests.get(url, timeout=timeout) as r:
|
||||||
|
r.raise_for_status()
|
||||||
|
with open(filename, "wb") as f:
|
||||||
|
f.write(r.content)
|
||||||
|
logger.info(f"✅ Saved {filename}")
|
||||||
|
return True
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"❌ Failed to download {filename}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
from config import ZIP_URL, ZIP_FILENAME, HTML_FILENAME, XLSX_URL, XLSX_FILENAME
|
|
||||||
|
|
||||||
def download_zip_and_extract_html():
|
def download_zip_and_extract_html():
|
||||||
print("🔄 Downloading ZIP...")
|
if not _download_file(ZIP_URL, ZIP_FILENAME):
|
||||||
try:
|
|
||||||
with requests.get(ZIP_URL, timeout=30) as r:
|
|
||||||
r.raise_for_status()
|
|
||||||
with open(ZIP_FILENAME, "wb") as f:
|
|
||||||
f.write(r.content)
|
|
||||||
print(f"✅ Saved ZIP as {ZIP_FILENAME}")
|
|
||||||
except requests.RequestException as e:
|
|
||||||
print(f"❌ Failed to download ZIP: {e}")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
logger.info(f"📦 Extracting {HTML_FILENAME} from {ZIP_FILENAME}...")
|
||||||
try:
|
try:
|
||||||
with zipfile.ZipFile(ZIP_FILENAME, "r") as z:
|
with zipfile.ZipFile(ZIP_FILENAME, "r") as z:
|
||||||
with z.open(HTML_FILENAME) as html_file:
|
html_content = z.read(HTML_FILENAME)
|
||||||
html_content = html_file.read()
|
|
||||||
with open(HTML_FILENAME, "wb") as f:
|
with open(HTML_FILENAME, "wb") as f:
|
||||||
f.write(html_content)
|
f.write(html_content)
|
||||||
print(f"✅ Extracted {HTML_FILENAME}")
|
logger.info(f"✅ Extracted {HTML_FILENAME}")
|
||||||
except (zipfile.BadZipFile, KeyError) as e:
|
except (zipfile.BadZipFile, KeyError, FileNotFoundError) as e:
|
||||||
print(f"❌ Failed to extract {HTML_FILENAME}: {e}")
|
logger.error(f"❌ Failed to extract {HTML_FILENAME}: {e}")
|
||||||
|
|
||||||
|
|
||||||
def download_xlsx():
|
def download_xlsx():
|
||||||
print("🔄 Downloading XLSX...")
|
_download_file(XLSX_URL, XLSX_FILENAME)
|
||||||
try:
|
|
||||||
with requests.get(XLSX_URL, timeout=30) as r:
|
|
||||||
r.raise_for_status()
|
|
||||||
with open(XLSX_FILENAME, "wb") as f:
|
|
||||||
f.write(r.content)
|
|
||||||
print(f"✅ Saved XLSX as {XLSX_FILENAME}")
|
|
||||||
except requests.RequestException as e:
|
|
||||||
print(f"❌ Failed to download XLSX: {e}")
|
|
||||||
41
frontend/artistgrid-sheets-frontend/.gitignore
vendored
Normal file
41
frontend/artistgrid-sheets-frontend/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# env files (can opt-in for committing if needed)
|
||||||
|
.env*
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
36
frontend/artistgrid-sheets-frontend/README.md
Normal file
36
frontend/artistgrid-sheets-frontend/README.md
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
First, run the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
# or
|
||||||
|
yarn dev
|
||||||
|
# or
|
||||||
|
pnpm dev
|
||||||
|
# or
|
||||||
|
bun dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||||
|
|
||||||
|
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||||
|
|
||||||
|
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
To learn more about Next.js, take a look at the following resources:
|
||||||
|
|
||||||
|
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||||
|
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||||
|
|
||||||
|
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||||
|
|
||||||
|
## Deploy on Vercel
|
||||||
|
|
||||||
|
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||||
|
|
||||||
|
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||||
BIN
frontend/artistgrid-sheets-frontend/app/favicon.ico
Normal file
BIN
frontend/artistgrid-sheets-frontend/app/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
122
frontend/artistgrid-sheets-frontend/app/globals.css
Normal file
122
frontend/artistgrid-sheets-frontend/app/globals.css
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
@import "tailwindcss";
|
||||||
|
@import "tw-animate-css";
|
||||||
|
|
||||||
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
|
@theme inline {
|
||||||
|
--color-background: var(--background);
|
||||||
|
--color-foreground: var(--foreground);
|
||||||
|
--font-sans: var(--font-geist-sans);
|
||||||
|
--font-mono: var(--font-geist-mono);
|
||||||
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||||
|
--color-sidebar-accent: var(--sidebar-accent);
|
||||||
|
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||||
|
--color-sidebar-primary: var(--sidebar-primary);
|
||||||
|
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||||
|
--color-sidebar: var(--sidebar);
|
||||||
|
--color-chart-5: var(--chart-5);
|
||||||
|
--color-chart-4: var(--chart-4);
|
||||||
|
--color-chart-3: var(--chart-3);
|
||||||
|
--color-chart-2: var(--chart-2);
|
||||||
|
--color-chart-1: var(--chart-1);
|
||||||
|
--color-ring: var(--ring);
|
||||||
|
--color-input: var(--input);
|
||||||
|
--color-border: var(--border);
|
||||||
|
--color-destructive: var(--destructive);
|
||||||
|
--color-accent-foreground: var(--accent-foreground);
|
||||||
|
--color-accent: var(--accent);
|
||||||
|
--color-muted-foreground: var(--muted-foreground);
|
||||||
|
--color-muted: var(--muted);
|
||||||
|
--color-secondary-foreground: var(--secondary-foreground);
|
||||||
|
--color-secondary: var(--secondary);
|
||||||
|
--color-primary-foreground: var(--primary-foreground);
|
||||||
|
--color-primary: var(--primary);
|
||||||
|
--color-popover-foreground: var(--popover-foreground);
|
||||||
|
--color-popover: var(--popover);
|
||||||
|
--color-card-foreground: var(--card-foreground);
|
||||||
|
--color-card: var(--card);
|
||||||
|
--radius-sm: calc(var(--radius) - 4px);
|
||||||
|
--radius-md: calc(var(--radius) - 2px);
|
||||||
|
--radius-lg: var(--radius);
|
||||||
|
--radius-xl: calc(var(--radius) + 4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--radius: 0.625rem;
|
||||||
|
--background: oklch(1 0 0);
|
||||||
|
--foreground: oklch(0.145 0 0);
|
||||||
|
--card: oklch(1 0 0);
|
||||||
|
--card-foreground: oklch(0.145 0 0);
|
||||||
|
--popover: oklch(1 0 0);
|
||||||
|
--popover-foreground: oklch(0.145 0 0);
|
||||||
|
--primary: oklch(0.205 0 0);
|
||||||
|
--primary-foreground: oklch(0.985 0 0);
|
||||||
|
--secondary: oklch(0.97 0 0);
|
||||||
|
--secondary-foreground: oklch(0.205 0 0);
|
||||||
|
--muted: oklch(0.97 0 0);
|
||||||
|
--muted-foreground: oklch(0.556 0 0);
|
||||||
|
--accent: oklch(0.97 0 0);
|
||||||
|
--accent-foreground: oklch(0.205 0 0);
|
||||||
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
|
--border: oklch(0.922 0 0);
|
||||||
|
--input: oklch(0.922 0 0);
|
||||||
|
--ring: oklch(0.708 0 0);
|
||||||
|
--chart-1: oklch(0.646 0.222 41.116);
|
||||||
|
--chart-2: oklch(0.6 0.118 184.704);
|
||||||
|
--chart-3: oklch(0.398 0.07 227.392);
|
||||||
|
--chart-4: oklch(0.828 0.189 84.429);
|
||||||
|
--chart-5: oklch(0.769 0.188 70.08);
|
||||||
|
--sidebar: oklch(0.985 0 0);
|
||||||
|
--sidebar-foreground: oklch(0.145 0 0);
|
||||||
|
--sidebar-primary: oklch(0.205 0 0);
|
||||||
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-accent: oklch(0.97 0 0);
|
||||||
|
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||||
|
--sidebar-border: oklch(0.922 0 0);
|
||||||
|
--sidebar-ring: oklch(0.708 0 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: oklch(0.145 0 0);
|
||||||
|
--foreground: oklch(0.985 0 0);
|
||||||
|
--card: oklch(0.205 0 0);
|
||||||
|
--card-foreground: oklch(0.985 0 0);
|
||||||
|
--popover: oklch(0.205 0 0);
|
||||||
|
--popover-foreground: oklch(0.985 0 0);
|
||||||
|
--primary: oklch(0.922 0 0);
|
||||||
|
--primary-foreground: oklch(0.205 0 0);
|
||||||
|
--secondary: oklch(0.269 0 0);
|
||||||
|
--secondary-foreground: oklch(0.985 0 0);
|
||||||
|
--muted: oklch(0.269 0 0);
|
||||||
|
--muted-foreground: oklch(0.708 0 0);
|
||||||
|
--accent: oklch(0.269 0 0);
|
||||||
|
--accent-foreground: oklch(0.985 0 0);
|
||||||
|
--destructive: oklch(0.704 0.191 22.216);
|
||||||
|
--border: oklch(1 0 0 / 10%);
|
||||||
|
--input: oklch(1 0 0 / 15%);
|
||||||
|
--ring: oklch(0.556 0 0);
|
||||||
|
--chart-1: oklch(0.488 0.243 264.376);
|
||||||
|
--chart-2: oklch(0.696 0.17 162.48);
|
||||||
|
--chart-3: oklch(0.769 0.188 70.08);
|
||||||
|
--chart-4: oklch(0.627 0.265 303.9);
|
||||||
|
--chart-5: oklch(0.645 0.246 16.439);
|
||||||
|
--sidebar: oklch(0.205 0 0);
|
||||||
|
--sidebar-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||||
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-accent: oklch(0.269 0 0);
|
||||||
|
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-border: oklch(1 0 0 / 10%);
|
||||||
|
--sidebar-ring: oklch(0.556 0 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border outline-ring/50;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
|
}
|
||||||
37
frontend/artistgrid-sheets-frontend/app/layout.tsx
Normal file
37
frontend/artistgrid-sheets-frontend/app/layout.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
import { Geist, Geist_Mono } from "next/font/google";
|
||||||
|
import "./globals.css";
|
||||||
|
|
||||||
|
const geistSans = Geist({
|
||||||
|
variable: "--font-geist-sans",
|
||||||
|
subsets: ["latin"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const geistMono = Geist_Mono({
|
||||||
|
variable: "--font-geist-mono",
|
||||||
|
subsets: ["latin"],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "ArtistGrid Sheets",
|
||||||
|
description: "We pull from TrackerHub and parse it into a CSV file. Still a work in progress.",
|
||||||
|
icons: {
|
||||||
|
icon: "/favicon.png", // make sure favicon.png is in /public
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body
|
||||||
|
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
68
frontend/artistgrid-sheets-frontend/app/page.tsx
Normal file
68
frontend/artistgrid-sheets-frontend/app/page.tsx
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Github, FileDown, FileText, FileSpreadsheet } from "lucide-react";
|
||||||
|
|
||||||
|
const buttonData = [
|
||||||
|
{
|
||||||
|
name: "View on GitHub",
|
||||||
|
href: "https://github.com/ArtistGrid/Sheets",
|
||||||
|
icon: Github,
|
||||||
|
isExternal: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Download CSV",
|
||||||
|
href: "https://sheets.artistgrid.cx/artists.csv",
|
||||||
|
icon: FileDown,
|
||||||
|
downloadName: "artists.csv",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "View HTML",
|
||||||
|
href: "https://sheets.artistgrid.cx/artists.html",
|
||||||
|
icon: FileText,
|
||||||
|
isExternal: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Download XLSX",
|
||||||
|
href: "https://sheets.artistgrid.cx/artists.xlsx",
|
||||||
|
icon: FileSpreadsheet,
|
||||||
|
downloadName: "ArtistGrid.xlsx",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function ArtistGridSheets() {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-black text-white flex items-center justify-center p-4 sm:p-6">
|
||||||
|
<div className="w-full max-w-lg text-center bg-neutral-950 border border-neutral-800 rounded-2xl p-8 sm:p-12 shadow-2xl shadow-black/30 animate-in fade-in-0 zoom-in-95 duration-500">
|
||||||
|
<h1 className="text-3xl sm:text-4xl font-bold bg-gradient-to-b from-neutral-50 to-neutral-400 bg-clip-text text-transparent mb-4">
|
||||||
|
ArtistGrid Sheets
|
||||||
|
</h1>
|
||||||
|
<p className="text-neutral-400 mb-10 max-w-sm mx-auto">
|
||||||
|
We pull from TrackerHub and parse it into a CSV file. Still a work in
|
||||||
|
progress.
|
||||||
|
</p>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
{buttonData.map((button) => (
|
||||||
|
<Button
|
||||||
|
key={button.name}
|
||||||
|
asChild
|
||||||
|
className="bg-white text-black hover:bg-neutral-200 font-semibold rounded-lg h-14 text-base transition-all duration-300 ease-out hover:-translate-y-1 hover:shadow-[0_0_30px_rgba(255,255,255,0.3)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-black focus-visible:ring-white"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href={button.href}
|
||||||
|
{...(button.isExternal && {
|
||||||
|
target: "_blank",
|
||||||
|
rel: "noopener noreferrer",
|
||||||
|
})}
|
||||||
|
{...(button.downloadName && { download: button.downloadName })}
|
||||||
|
>
|
||||||
|
<button.icon className="w-5 h-5 mr-2.5" aria-hidden="true" />
|
||||||
|
{button.name}
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
21
frontend/artistgrid-sheets-frontend/components.json
Normal file
21
frontend/artistgrid-sheets-frontend/components.json
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "new-york",
|
||||||
|
"rsc": true,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "",
|
||||||
|
"css": "app/globals.css",
|
||||||
|
"baseColor": "neutral",
|
||||||
|
"cssVariables": true,
|
||||||
|
"prefix": ""
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"components": "@/components",
|
||||||
|
"utils": "@/lib/utils",
|
||||||
|
"ui": "@/components/ui",
|
||||||
|
"lib": "@/lib",
|
||||||
|
"hooks": "@/hooks"
|
||||||
|
},
|
||||||
|
"iconLibrary": "lucide"
|
||||||
|
}
|
||||||
59
frontend/artistgrid-sheets-frontend/components/ui/button.tsx
Normal file
59
frontend/artistgrid-sheets-frontend/components/ui/button.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
import * as React from "react"
|
||||||
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const buttonVariants = cva(
|
||||||
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default:
|
||||||
|
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
|
||||||
|
destructive:
|
||||||
|
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||||
|
outline:
|
||||||
|
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
||||||
|
secondary:
|
||||||
|
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
||||||
|
ghost:
|
||||||
|
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||||
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
||||||
|
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
||||||
|
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
||||||
|
icon: "size-9",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
size: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function Button({
|
||||||
|
className,
|
||||||
|
variant,
|
||||||
|
size,
|
||||||
|
asChild = false,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"button"> &
|
||||||
|
VariantProps<typeof buttonVariants> & {
|
||||||
|
asChild?: boolean
|
||||||
|
}) {
|
||||||
|
const Comp = asChild ? Slot : "button"
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
data-slot="button"
|
||||||
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Button, buttonVariants }
|
||||||
25
frontend/artistgrid-sheets-frontend/eslint.config.mjs
Normal file
25
frontend/artistgrid-sheets-frontend/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { dirname } from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
import { FlatCompat } from "@eslint/eslintrc";
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
});
|
||||||
|
|
||||||
|
const eslintConfig = [
|
||||||
|
...compat.extends("next/core-web-vitals", "next/typescript"),
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
"node_modules/**",
|
||||||
|
".next/**",
|
||||||
|
"out/**",
|
||||||
|
"build/**",
|
||||||
|
"next-env.d.ts",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default eslintConfig;
|
||||||
6
frontend/artistgrid-sheets-frontend/lib/utils.ts
Normal file
6
frontend/artistgrid-sheets-frontend/lib/utils.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { clsx, type ClassValue } from "clsx"
|
||||||
|
import { twMerge } from "tailwind-merge"
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs))
|
||||||
|
}
|
||||||
17
frontend/artistgrid-sheets-frontend/next.config.js
Normal file
17
frontend/artistgrid-sheets-frontend/next.config.js
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
/**
|
||||||
|
* @type {import('next').NextConfig}
|
||||||
|
*/
|
||||||
|
const nextConfig = {
|
||||||
|
output: 'export',
|
||||||
|
|
||||||
|
// Optional: Change links `/me` -> `/me/` and emit `/me.html` -> `/me/index.html`
|
||||||
|
// trailingSlash: true,
|
||||||
|
|
||||||
|
// Optional: Prevent automatic `/me` -> `/me/`, instead preserve `href`
|
||||||
|
// skipTrailingSlashRedirect: true,
|
||||||
|
|
||||||
|
// Optional: Change the output directory `out` -> `dist`
|
||||||
|
// distDir: 'dist',
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = nextConfig
|
||||||
7
frontend/artistgrid-sheets-frontend/next.config.ts
Normal file
7
frontend/artistgrid-sheets-frontend/next.config.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
/* config options here */
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
6238
frontend/artistgrid-sheets-frontend/package-lock.json
generated
Normal file
6238
frontend/artistgrid-sheets-frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
33
frontend/artistgrid-sheets-frontend/package.json
Normal file
33
frontend/artistgrid-sheets-frontend/package.json
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"name": "artistgrid-sheets-frontend",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "eslint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"lucide-react": "^0.540.0",
|
||||||
|
"next": "15.5.0",
|
||||||
|
"react": "19.1.0",
|
||||||
|
"react-dom": "19.1.0",
|
||||||
|
"tailwind-merge": "^3.3.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3",
|
||||||
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@types/node": "^20",
|
||||||
|
"@types/react": "^19",
|
||||||
|
"@types/react-dom": "^19",
|
||||||
|
"eslint": "^9",
|
||||||
|
"eslint-config-next": "15.5.0",
|
||||||
|
"tailwindcss": "^4",
|
||||||
|
"tw-animate-css": "^1.3.7",
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
||||||
5
frontend/artistgrid-sheets-frontend/postcss.config.mjs
Normal file
5
frontend/artistgrid-sheets-frontend/postcss.config.mjs
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
const config = {
|
||||||
|
plugins: ["@tailwindcss/postcss"],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
BIN
frontend/artistgrid-sheets-frontend/public/favicon.png
Normal file
BIN
frontend/artistgrid-sheets-frontend/public/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8 KiB |
27
frontend/artistgrid-sheets-frontend/tsconfig.json
Normal file
27
frontend/artistgrid-sheets-frontend/tsconfig.json
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2017",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
{
|
{
|
||||||
"last_updated": "2025-08-19T05:14:17.304876Z",
|
"last_updated": "2025-08-22T01:41:42.228132+00:00",
|
||||||
"files": {
|
"files": {
|
||||||
"Artists.html": {
|
"Artists.html": {
|
||||||
"hash": "b48e3d341500e82e179f8813443e5fed899313b48ef79b83bd87388189c49a90",
|
"hash": "6ddd64a83ad530c441bcfd88007c923878a4bd529f07ea60947b1185b8cc1879",
|
||||||
"last_archived": "2025-08-19T05:14:17.304886Z"
|
"last_archived": "2025-08-19T05:14:17.304886Z"
|
||||||
},
|
},
|
||||||
"artists.csv": {
|
"artists.csv": {
|
||||||
"hash": "609ab1ad6adf62bc1ab8e1b433e1676ab6787e65c760e22d7da7a8e26bbdf33e"
|
"hash": "01e5dd552b219b3472ad95a27660add9e9b47a868ed62bde4f1f303e0e1a514d"
|
||||||
},
|
},
|
||||||
"artists.xlsx": {
|
"artists.xlsx": {
|
||||||
"hash": "101b6cdef091774668b030bd1bed27a78cb227b487f5f04883c7a2f4bb184b5e"
|
"hash": "69d0f7146e1aa4ce9ab3080bec2cc0ed1696b5ea3f289296a58d00f19ed5812f"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
125
main.py
125
main.py
|
|
@ -1,96 +1,115 @@
|
||||||
from flask import Flask, send_file, send_from_directory, jsonify
|
# main.py
|
||||||
from flask_cors import CORS
|
|
||||||
import threading
|
|
||||||
import os
|
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
|
||||||
from config import HTML_FILENAME, CSV_FILENAME, XLSX_FILENAME
|
from flask import Flask, jsonify, send_file, send_from_directory
|
||||||
|
from flask_cors import CORS
|
||||||
|
|
||||||
|
from config import CSV_FILENAME, HTML_FILENAME, XLSX_FILENAME
|
||||||
from update_loop import update_loop
|
from update_loop import update_loop
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
CORS(app)
|
CORS(app)
|
||||||
|
|
||||||
# Serve main files
|
|
||||||
|
@app.route("/")
|
||||||
|
def serve_index():
|
||||||
|
return send_file("templates/index.html")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/artists.html")
|
@app.route("/artists.html")
|
||||||
def serve_artists_html():
|
def serve_artists_html():
|
||||||
return send_file(HTML_FILENAME, mimetype="text/html")
|
return send_file(HTML_FILENAME)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/artists.csv")
|
@app.route("/artists.csv")
|
||||||
def serve_artists_csv():
|
def serve_artists_csv():
|
||||||
return send_file(CSV_FILENAME, mimetype="text/csv")
|
return send_file(CSV_FILENAME)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/artists.xlsx")
|
@app.route("/artists.xlsx")
|
||||||
def serve_artists_xlsx():
|
def serve_artists_xlsx():
|
||||||
return send_file(XLSX_FILENAME, mimetype="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
return send_file(XLSX_FILENAME)
|
||||||
|
|
||||||
# Serve index and frontend assets
|
|
||||||
@app.route("/")
|
|
||||||
@app.route("/index")
|
|
||||||
@app.route("/index.html")
|
|
||||||
def serve_index():
|
|
||||||
return send_file("templates/index.html", mimetype="text/html")
|
|
||||||
|
|
||||||
@app.route("/_next/<path:filename>")
|
@app.route("/_next/<path:filename>")
|
||||||
def serve_next_static(filename):
|
def serve_next_static(filename):
|
||||||
return send_from_directory("templates/_next", filename)
|
return send_from_directory("templates/_next", filename)
|
||||||
|
|
||||||
# Serve /info JSON
|
|
||||||
|
def get_status_data():
|
||||||
|
info_path = os.path.join("info", "status.json")
|
||||||
|
if not os.path.exists(info_path):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
with open(info_path, "r") as f:
|
||||||
|
return json.load(f)
|
||||||
|
except (IOError, json.JSONDecodeError) as e:
|
||||||
|
logger.error(f"Failed to read or parse status.json: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
@app.route("/info")
|
@app.route("/info")
|
||||||
def info_json():
|
def info_json():
|
||||||
info_path = os.path.join("info", "status.json")
|
data = get_status_data()
|
||||||
if os.path.exists(info_path):
|
if data:
|
||||||
with open(info_path) as f:
|
return jsonify(data)
|
||||||
return jsonify(json.load(f))
|
return jsonify({"error": "Info not available"}), 404
|
||||||
return {"error": "Info not available"}, 404
|
|
||||||
|
|
||||||
# Serve /info HTML
|
|
||||||
@app.route("/info/html")
|
@app.route("/info/html")
|
||||||
def info_html():
|
def info_html():
|
||||||
info_path = os.path.join("info", "status.json")
|
data = get_status_data()
|
||||||
if os.path.exists(info_path):
|
if not data:
|
||||||
with open(info_path) as f:
|
return "<p>Status info not available.</p>", 404
|
||||||
data = json.load(f)
|
|
||||||
html = f"""
|
files_info = data.get("files", {})
|
||||||
<html>
|
html_info = files_info.get(HTML_FILENAME, {})
|
||||||
<head><title>File Info</title></head>
|
csv_info = files_info.get(CSV_FILENAME, {})
|
||||||
|
xlsx_info = files_info.get(XLSX_FILENAME, {})
|
||||||
|
|
||||||
|
return f"""
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>File Info</title>
|
||||||
|
<style>body {{ font-family: sans-serif; }} li {{ margin-bottom: 1em; }}</style>
|
||||||
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Latest File Info</h1>
|
<h1>Latest File Info</h1>
|
||||||
<p><strong>Last Updated:</strong> {data.get('last_updated')}</p>
|
<p><strong>Last Updated:</strong> {data.get('last_updated', 'N/A')}</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Artists.html</strong><br>
|
<li><strong>{HTML_FILENAME}</strong><br>
|
||||||
Hash: {data['files']['Artists.html']['hash']}<br>
|
Hash: {html_info.get('hash', 'N/A')}<br>
|
||||||
Archived: {data['files']['Artists.html']['last_archived']}
|
Archived: {html_info.get('last_archived', 'N/A')}
|
||||||
</li>
|
</li>
|
||||||
<li><strong>artists.csv</strong><br>
|
<li><strong>{CSV_FILENAME}</strong><br>
|
||||||
Hash: {data['files']['artists.csv']['hash']}
|
Hash: {csv_info.get('hash', 'N/A')}
|
||||||
</li>
|
</li>
|
||||||
<li><strong>artists.xlsx</strong><br>
|
<li><strong>{XLSX_FILENAME}</strong><br>
|
||||||
Hash: {data['files']['artists.xlsx']['hash']}
|
Hash: {xlsx_info.get('hash', 'N/A')}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
return html
|
|
||||||
return "<p>Status info not available.</p>", 404
|
|
||||||
|
|
||||||
# 404 page
|
|
||||||
@app.errorhandler(404)
|
@app.errorhandler(404)
|
||||||
def page_not_found(e):
|
def page_not_found(e):
|
||||||
return send_file("templates/404.html", mimetype="text/html"), 404
|
return send_file("templates/404.html"), 404
|
||||||
|
|
||||||
|
|
||||||
# Start app and updater
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Run update loop in background
|
logger.info("Starting background update thread...")
|
||||||
threading.Thread(target=update_loop, daemon=True).start()
|
threading.Thread(target=update_loop, daemon=True).start()
|
||||||
|
logger.info("Starting Flask server...")
|
||||||
# Optional: perform initial download/generation if needed
|
|
||||||
from downloader import download_zip_and_extract_html, download_xlsx
|
|
||||||
from parser import generate_csv
|
|
||||||
|
|
||||||
# Uncomment below if you want to do initial sync before serving
|
|
||||||
# download_zip_and_extract_html()
|
|
||||||
# download_xlsx()
|
|
||||||
# generate_csv()
|
|
||||||
|
|
||||||
app.run(host="0.0.0.0", port=5000)
|
app.run(host="0.0.0.0", port=5000)
|
||||||
30
notify.py
30
notify.py
|
|
@ -1,20 +1,30 @@
|
||||||
import os, json, requests
|
# notify.py
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
from config import DISCORD_WEBHOOK_URL
|
from config import DISCORD_WEBHOOK_URL
|
||||||
|
|
||||||
def send_discord_message(content):
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def send_discord_message(content: str):
|
||||||
if not DISCORD_WEBHOOK_URL:
|
if not DISCORD_WEBHOOK_URL:
|
||||||
print("⚠️ Discord webhook URL not set in env")
|
logger.warning("Discord webhook URL not set. Skipping notification.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if len(content) > 2000:
|
||||||
|
content = content[:1990] + "\n... (truncated)"
|
||||||
|
|
||||||
headers = {"Content-Type": "application/json"}
|
headers = {"Content-Type": "application/json"}
|
||||||
data = {"content": content}
|
data = {"content": content}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
resp = requests.post(DISCORD_WEBHOOK_URL, headers=headers, data=json.dumps(data), timeout=10)
|
response = requests.post(
|
||||||
if resp.status_code in (200, 204):
|
DISCORD_WEBHOOK_URL, headers=headers, data=json.dumps(data), timeout=10
|
||||||
print("✅ Discord notification sent")
|
)
|
||||||
else:
|
response.raise_for_status()
|
||||||
print(f"⚠️ Failed to send Discord notification, status code {resp.status_code}")
|
logger.info("✅ Discord notification sent successfully.")
|
||||||
except Exception as e:
|
except requests.RequestException as e:
|
||||||
print(f"⚠️ Exception sending Discord notification: {e}")
|
logger.error(f"⚠️ Exception sending Discord notification: {e}")
|
||||||
64
parser.py
64
parser.py
|
|
@ -1,50 +1,70 @@
|
||||||
from bs4 import BeautifulSoup
|
# parser.py
|
||||||
import csv
|
import csv
|
||||||
|
import logging
|
||||||
|
|
||||||
from config import HTML_FILENAME, CSV_FILENAME, exclude_names
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
from config import CSV_FILENAME, HTML_FILENAME, exclude_names
|
||||||
from utils import clean_artist_name, force_star_flag
|
from utils import clean_artist_name, force_star_flag
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def generate_csv():
|
def generate_csv():
|
||||||
print("📝 Generating CSV...")
|
logger.info(f"📝 Generating {CSV_FILENAME} from {HTML_FILENAME}...")
|
||||||
|
try:
|
||||||
with open(HTML_FILENAME, "r", encoding="utf-8") as f:
|
with open(HTML_FILENAME, "r", encoding="utf-8") as f:
|
||||||
soup = BeautifulSoup(f, "html.parser")
|
soup = BeautifulSoup(f, "html.parser")
|
||||||
|
except FileNotFoundError:
|
||||||
|
logger.error(f"❌ {HTML_FILENAME} not found. Cannot generate CSV.")
|
||||||
|
return
|
||||||
|
|
||||||
rows = soup.select("table.waffle tbody tr")[3:]
|
table_body = soup.select_one("table.waffle tbody")
|
||||||
|
if not table_body:
|
||||||
|
logger.error("❌ Could not find the table body in HTML. Cannot generate CSV.")
|
||||||
|
return
|
||||||
|
|
||||||
|
rows = table_body.select("tr")
|
||||||
data = []
|
data = []
|
||||||
starring = True
|
starring_section = True
|
||||||
|
|
||||||
for row in rows:
|
for row in rows[3:]:
|
||||||
cells = row.find_all("td")
|
cells = row.find_all("td")
|
||||||
if len(cells) < 4:
|
if len(cells) < 4:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Always take the artist name from the column text
|
|
||||||
artist_name_raw = cells[0].get_text(strip=True)
|
artist_name_raw = cells[0].get_text(strip=True)
|
||||||
|
|
||||||
# Only use the <a> for the URL (if it exists)
|
|
||||||
link_tag = cells[0].find("a")
|
link_tag = cells[0].find("a")
|
||||||
artist_url = link_tag["href"] if link_tag else ""
|
artist_url = link_tag.get("href") if link_tag else ""
|
||||||
if not artist_url:
|
|
||||||
|
if not artist_name_raw or not artist_url:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if "AI Models" in artist_name_raw:
|
if "AI Models" in artist_name_raw:
|
||||||
starring = False
|
starring_section = False
|
||||||
|
|
||||||
artist_name_clean = clean_artist_name(artist_name_raw)
|
artist_name_clean = clean_artist_name(artist_name_raw)
|
||||||
if artist_name_clean in exclude_names or "🚩" in artist_name_raw:
|
if artist_name_clean in exclude_names or "🚩" in artist_name_raw:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
best = force_star_flag(starring)
|
data.append(
|
||||||
credit = cells[1].get_text(strip=True)
|
[
|
||||||
updated = cells[2].get_text(strip=True)
|
artist_name_clean,
|
||||||
links_work = cells[3].get_text(strip=True)
|
artist_url,
|
||||||
|
cells[1].get_text(strip=True),
|
||||||
|
cells[3].get_text(strip=True),
|
||||||
|
cells[2].get_text(strip=True),
|
||||||
|
force_star_flag(starring_section),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
data.append([artist_name_clean, artist_url, credit, links_work, updated, best])
|
try:
|
||||||
|
with open(CSV_FILENAME, "w", newline="", encoding="utf-8") as csvfile:
|
||||||
with open(CSV_FILENAME, "w", newline='', encoding="utf-8") as csvfile:
|
|
||||||
writer = csv.writer(csvfile, quoting=csv.QUOTE_ALL)
|
writer = csv.writer(csvfile, quoting=csv.QUOTE_ALL)
|
||||||
writer.writerow(["Artist Name", "URL", "Credit", "Links Work", "Updated", "Best"])
|
writer.writerow(
|
||||||
|
["Artist Name", "URL", "Credit", "Links Work", "Updated", "Best"]
|
||||||
|
)
|
||||||
writer.writerows(data)
|
writer.writerows(data)
|
||||||
|
logger.info(f"✅ Generated {CSV_FILENAME} with {len(data)} rows.")
|
||||||
print(f"✅ CSV saved as {CSV_FILENAME}")
|
except IOError as e:
|
||||||
|
logger.error(f"❌ Failed to write CSV file {CSV_FILENAME}: {e}")
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
||||||
self.__BUILD_MANIFEST=function(e,r,t){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},__routerFilterStatic:{numItems:2,errorRate:1e-4,numBits:39,numHashes:14,bitArray:[0,1,1,0,r,e,e,r,r,e,e,r,e,e,e,r,r,e,e,e,e,r,e,r,r,r,r,e,e,e,r,e,r,e,r,e,e,e,r]},__routerFilterDynamic:{numItems:r,errorRate:1e-4,numBits:r,numHashes:null,bitArray:[]},"/_error":["static/chunks/pages/_error-71d2b6a7b832d02a.js"],sortedPages:["/_app","/_error"]}}(1,0,1e-4),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
|
|
||||||
1
templates/_next/static/chunks/139.7a5a8e93a21948c1.js
Normal file
1
templates/_next/static/chunks/139.7a5a8e93a21948c1.js
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[139],{5139:(e,t,l)=>{Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return s}});let u=l(4252),n=l(7876),a=u._(l(4232)),o=l(1033);async function r(e){let{Component:t,ctx:l}=e;return{pageProps:await (0,o.loadGetInitialProps)(t,l)}}class s extends a.default.Component{render(){let{Component:e,pageProps:t}=this.props;return(0,n.jsx)(e,{...t})}}s.origGetInitialProps=r,s.getInitialProps=r,("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)}}]);
|
||||||
1
templates/_next/static/chunks/255-e2f794a071b5ab87.js
Normal file
1
templates/_next/static/chunks/255-e2f794a071b5ab87.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
||||||
"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[472],{472:(e,t,l)=>{Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return s}});let u=l(4252),n=l(7876),a=u._(l(4232)),o=l(2746);async function r(e){let{Component:t,ctx:l}=e;return{pageProps:await (0,o.loadGetInitialProps)(t,l)}}class s extends a.default.Component{render(){let{Component:e,pageProps:t}=this.props;return(0,n.jsx)(e,{...t})}}s.origGetInitialProps=r,s.getInitialProps=r,("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)}}]);
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
templates/_next/static/chunks/646.f342b7cffc01feb0.js
Normal file
1
templates/_next/static/chunks/646.f342b7cffc01feb0.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
templates/_next/static/chunks/78-578bf7339c7a46f2.js
Normal file
1
templates/_next/static/chunks/78-578bf7339c7a46f2.js
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
||||||
|
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[492],{2474:(e,t,l)=>{(window.__NEXT_P=window.__NEXT_P||[]).push(["/_not-found/page",function(){return l(9520)}])},4585:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"styles",{enumerable:!0,get:function(){return l}});let l={error:{fontFamily:'system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"',height:"100vh",textAlign:"center",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center"},desc:{display:"inline-block"},h1:{display:"inline-block",margin:"0 20px 0 0",padding:"0 23px 0 0",fontSize:24,fontWeight:500,verticalAlign:"top",lineHeight:"49px"},h2:{fontSize:14,fontWeight:400,lineHeight:"49px",margin:0}};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},8886:(e,t,l)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"HTTPAccessErrorFallback",{enumerable:!0,get:function(){return o}});let r=l(5155),n=l(4585);function o(e){let{status:t,message:l}=e;return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)("title",{children:t+": "+l}),(0,r.jsx)("div",{style:n.styles.error,children:(0,r.jsxs)("div",{children:[(0,r.jsx)("style",{dangerouslySetInnerHTML:{__html:"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}),(0,r.jsx)("h1",{className:"next-error-h1",style:n.styles.h1,children:t}),(0,r.jsx)("div",{style:n.styles.desc,children:(0,r.jsx)("h2",{style:n.styles.h2,children:l})})]})})]})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},9520:(e,t,l)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return o}});let r=l(5155),n=l(8886),o=function(){return(0,r.jsx)("html",{children:(0,r.jsx)("body",{children:(0,r.jsx)(n.HTTPAccessErrorFallback,{status:404,message:"This page could not be found."})})})};("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)}},e=>{e.O(0,[441,255,358],()=>e(e.s=2474)),_N_E=e.O()}]);
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[492],{3632:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return o}});let l=r(5155),n=r(6395);function o(){return(0,l.jsx)(n.HTTPAccessErrorFallback,{status:404,message:"This page could not be found."})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3868:(e,t,r)=>{(window.__NEXT_P=window.__NEXT_P||[]).push(["/_not-found/page",function(){return r(3632)}])},6395:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"HTTPAccessErrorFallback",{enumerable:!0,get:function(){return o}}),r(8229);let l=r(5155);r(2115);let n={error:{fontFamily:'system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"',height:"100vh",textAlign:"center",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center"},desc:{display:"inline-block"},h1:{display:"inline-block",margin:"0 20px 0 0",padding:"0 23px 0 0",fontSize:24,fontWeight:500,verticalAlign:"top",lineHeight:"49px"},h2:{fontSize:14,fontWeight:400,lineHeight:"49px",margin:0}};function o(e){let{status:t,message:r}=e;return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)("title",{children:t+": "+r}),(0,l.jsx)("div",{style:n.error,children:(0,l.jsxs)("div",{children:[(0,l.jsx)("style",{dangerouslySetInnerHTML:{__html:"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}),(0,l.jsx)("h1",{className:"next-error-h1",style:n.h1,children:t}),(0,l.jsx)("div",{style:n.desc,children:(0,l.jsx)("h2",{style:n.h2,children:r})})]})})]})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)}},e=>{var t=t=>e(e.s=t);e.O(0,[441,684,358],()=>t(3868)),_N_E=e.O()}]);
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[492],{3632:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return o}});let l=r(5155),n=r(6395);function o(){return(0,l.jsx)(n.HTTPAccessErrorFallback,{status:404,message:"This page could not be found."})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)},3868:(e,t,r)=>{(window.__NEXT_P=window.__NEXT_P||[]).push(["/_not-found/page",function(){return r(3632)}])},6395:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"HTTPAccessErrorFallback",{enumerable:!0,get:function(){return o}}),r(8229);let l=r(5155);r(2115);let n={error:{fontFamily:'system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"',height:"100vh",textAlign:"center",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center"},desc:{display:"inline-block"},h1:{display:"inline-block",margin:"0 20px 0 0",padding:"0 23px 0 0",fontSize:24,fontWeight:500,verticalAlign:"top",lineHeight:"49px"},h2:{fontSize:14,fontWeight:400,lineHeight:"49px",margin:0}};function o(e){let{status:t,message:r}=e;return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)("title",{children:t+": "+r}),(0,l.jsx)("div",{style:n.error,children:(0,l.jsxs)("div",{children:[(0,l.jsx)("style",{dangerouslySetInnerHTML:{__html:"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}),(0,l.jsx)("h1",{className:"next-error-h1",style:n.h1,children:t}),(0,l.jsx)("div",{style:n.desc,children:(0,l.jsx)("h2",{style:n.h2,children:r})})]})})]})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)}},e=>{var t=t=>e(e.s=t);e.O(0,[441,684,358],()=>t(3868)),_N_E=e.O()}]);
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[177],{2705:(e,t,r)=>{Promise.resolve().then(r.bind(r,7780)),Promise.resolve().then(r.t.bind(r,9840,23)),Promise.resolve().then(r.t.bind(r,9324,23))},7780:(e,t,r)=>{"use strict";r.d(t,{ThemeProvider:()=>b});var n=r(5155),s=r(2115),a=(e,t,r,n,s,a,l,o)=>{let c=document.documentElement,i=["light","dark"];function m(t){var r;(Array.isArray(e)?e:[e]).forEach(e=>{let r="class"===e,n=r&&a?s.map(e=>a[e]||e):s;r?(c.classList.remove(...n),c.classList.add(a&&a[t]?a[t]:t)):c.setAttribute(e,t)}),r=t,o&&i.includes(r)&&(c.style.colorScheme=r)}if(n)m(n);else try{let e=localStorage.getItem(t)||r,n=l&&"system"===e?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":e;m(n)}catch(e){}},l=["light","dark"],o="(prefers-color-scheme: dark)",c=s.createContext(void 0),i=e=>s.useContext(c)?s.createElement(s.Fragment,null,e.children):s.createElement(d,{...e}),m=["light","dark"],d=e=>{let{forcedTheme:t,disableTransitionOnChange:r=!1,enableSystem:n=!0,enableColorScheme:a=!0,storageKey:i="theme",themes:d=m,defaultTheme:b=n?"system":"light",attribute:p="data-theme",value:v,children:g,nonce:E,scriptProps:S}=e,[k,w]=s.useState(()=>h(i,b)),[C,T]=s.useState(()=>"system"===k?f():k),_=v?Object.values(v):d,L=s.useCallback(e=>{let t=e;if(!t)return;"system"===e&&n&&(t=f());let s=v?v[t]:t,o=r?y(E):null,c=document.documentElement,i=e=>{"class"===e?(c.classList.remove(..._),s&&c.classList.add(s)):e.startsWith("data-")&&(s?c.setAttribute(e,s):c.removeAttribute(e))};if(Array.isArray(p)?p.forEach(i):i(p),a){let e=l.includes(b)?b:null,r=l.includes(t)?t:e;c.style.colorScheme=r}null==o||o()},[E]),A=s.useCallback(e=>{let t="function"==typeof e?e(k):e;w(t);try{localStorage.setItem(i,t)}catch(e){}},[k]),P=s.useCallback(e=>{T(f(e)),"system"===k&&n&&!t&&L("system")},[k,t]);s.useEffect(()=>{let e=window.matchMedia(o);return e.addListener(P),P(e),()=>e.removeListener(P)},[P]),s.useEffect(()=>{let e=e=>{e.key===i&&(e.newValue?w(e.newValue):A(b))};return window.addEventListener("storage",e),()=>window.removeEventListener("storage",e)},[A]),s.useEffect(()=>{L(null!=t?t:k)},[t,k]);let N=s.useMemo(()=>({theme:k,setTheme:A,forcedTheme:t,resolvedTheme:"system"===k?C:k,themes:n?[...d,"system"]:d,systemTheme:n?C:void 0}),[k,A,t,C,n,d]);return s.createElement(c.Provider,{value:N},s.createElement(u,{forcedTheme:t,storageKey:i,attribute:p,enableSystem:n,enableColorScheme:a,defaultTheme:b,value:v,themes:d,nonce:E,scriptProps:S}),g)},u=s.memo(e=>{let{forcedTheme:t,storageKey:r,attribute:n,enableSystem:l,enableColorScheme:o,defaultTheme:c,value:i,themes:m,nonce:d,scriptProps:u}=e,h=JSON.stringify([n,r,c,t,m,i,l,o]).slice(1,-1);return s.createElement("script",{...u,suppressHydrationWarning:!0,nonce:"",dangerouslySetInnerHTML:{__html:"(".concat(a.toString(),")(").concat(h,")")}})}),h=(e,t)=>{let r;try{r=localStorage.getItem(e)||void 0}catch(e){}return r||t},y=e=>{let t=document.createElement("style");return e&&t.setAttribute("nonce",e),t.appendChild(document.createTextNode("*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}")),document.head.appendChild(t),()=>{window.getComputedStyle(document.body),setTimeout(()=>{document.head.removeChild(t)},1)}},f=e=>(e||(e=window.matchMedia(o)),e.matches?"dark":"light");function b(e){let{children:t,...r}=e;return(0,n.jsx)(i,{...r,children:t})}},9324:()=>{},9840:e=>{e.exports={style:{fontFamily:"'Inter', 'Inter Fallback'",fontStyle:"normal"},className:"__className_e8ce0c"}}},e=>{var t=t=>e(e.s=t);e.O(0,[385,441,684,358],()=>t(2705)),_N_E=e.O()}]);
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[177],{3067:(e,t,r)=>{Promise.resolve().then(r.bind(r,7780)),Promise.resolve().then(r.t.bind(r,9840,23)),Promise.resolve().then(r.t.bind(r,9324,23))},7780:(e,t,r)=>{"use strict";r.d(t,{ThemeProvider:()=>b});var n=r(5155),s=r(2115),a=(e,t,r,n,s,a,l,o)=>{let c=document.documentElement,i=["light","dark"];function m(t){var r;(Array.isArray(e)?e:[e]).forEach(e=>{let r="class"===e,n=r&&a?s.map(e=>a[e]||e):s;r?(c.classList.remove(...n),c.classList.add(a&&a[t]?a[t]:t)):c.setAttribute(e,t)}),r=t,o&&i.includes(r)&&(c.style.colorScheme=r)}if(n)m(n);else try{let e=localStorage.getItem(t)||r,n=l&&"system"===e?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":e;m(n)}catch(e){}},l=["light","dark"],o="(prefers-color-scheme: dark)",c=s.createContext(void 0),i=e=>s.useContext(c)?s.createElement(s.Fragment,null,e.children):s.createElement(d,{...e}),m=["light","dark"],d=e=>{let{forcedTheme:t,disableTransitionOnChange:r=!1,enableSystem:n=!0,enableColorScheme:a=!0,storageKey:i="theme",themes:d=m,defaultTheme:b=n?"system":"light",attribute:p="data-theme",value:v,children:g,nonce:E,scriptProps:S}=e,[k,w]=s.useState(()=>h(i,b)),[C,T]=s.useState(()=>"system"===k?f():k),_=v?Object.values(v):d,L=s.useCallback(e=>{let t=e;if(!t)return;"system"===e&&n&&(t=f());let s=v?v[t]:t,o=r?y(E):null,c=document.documentElement,i=e=>{"class"===e?(c.classList.remove(..._),s&&c.classList.add(s)):e.startsWith("data-")&&(s?c.setAttribute(e,s):c.removeAttribute(e))};if(Array.isArray(p)?p.forEach(i):i(p),a){let e=l.includes(b)?b:null,r=l.includes(t)?t:e;c.style.colorScheme=r}null==o||o()},[E]),A=s.useCallback(e=>{let t="function"==typeof e?e(k):e;w(t);try{localStorage.setItem(i,t)}catch(e){}},[k]),P=s.useCallback(e=>{T(f(e)),"system"===k&&n&&!t&&L("system")},[k,t]);s.useEffect(()=>{let e=window.matchMedia(o);return e.addListener(P),P(e),()=>e.removeListener(P)},[P]),s.useEffect(()=>{let e=e=>{e.key===i&&(e.newValue?w(e.newValue):A(b))};return window.addEventListener("storage",e),()=>window.removeEventListener("storage",e)},[A]),s.useEffect(()=>{L(null!=t?t:k)},[t,k]);let N=s.useMemo(()=>({theme:k,setTheme:A,forcedTheme:t,resolvedTheme:"system"===k?C:k,themes:n?[...d,"system"]:d,systemTheme:n?C:void 0}),[k,A,t,C,n,d]);return s.createElement(c.Provider,{value:N},s.createElement(u,{forcedTheme:t,storageKey:i,attribute:p,enableSystem:n,enableColorScheme:a,defaultTheme:b,value:v,themes:d,nonce:E,scriptProps:S}),g)},u=s.memo(e=>{let{forcedTheme:t,storageKey:r,attribute:n,enableSystem:l,enableColorScheme:o,defaultTheme:c,value:i,themes:m,nonce:d,scriptProps:u}=e,h=JSON.stringify([n,r,c,t,m,i,l,o]).slice(1,-1);return s.createElement("script",{...u,suppressHydrationWarning:!0,nonce:"",dangerouslySetInnerHTML:{__html:"(".concat(a.toString(),")(").concat(h,")")}})}),h=(e,t)=>{let r;try{r=localStorage.getItem(e)||void 0}catch(e){}return r||t},y=e=>{let t=document.createElement("style");return e&&t.setAttribute("nonce",e),t.appendChild(document.createTextNode("*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}")),document.head.appendChild(t),()=>{window.getComputedStyle(document.body),setTimeout(()=>{document.head.removeChild(t)},1)}},f=e=>(e||(e=window.matchMedia(o)),e.matches?"dark":"light");function b(e){let{children:t,...r}=e;return(0,n.jsx)(i,{...r,children:t})}},9324:()=>{},9840:e=>{e.exports={style:{fontFamily:"'Inter', 'Inter Fallback'",fontStyle:"normal"},className:"__className_e8ce0c"}}},e=>{var t=t=>e(e.s=t);e.O(0,[385,441,684,358],()=>t(3067)),_N_E=e.O()}]);
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[177],{1290:()=>{},5039:(e,a,s)=>{Promise.resolve().then(s.t.bind(s,8480,23)),Promise.resolve().then(s.t.bind(s,5680,23)),Promise.resolve().then(s.t.bind(s,1290,23))},5680:e=>{e.exports={style:{fontFamily:"'Geist Mono', 'Geist Mono Fallback'",fontStyle:"normal"},className:"__className_9a8899",variable:"__variable_9a8899"}},8480:e=>{e.exports={style:{fontFamily:"'Geist', 'Geist Fallback'",fontStyle:"normal"},className:"__className_5cfdac",variable:"__variable_5cfdac"}}},e=>{e.O(0,[587,441,255,358],()=>e(e.s=5039)),_N_E=e.O()}]);
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[974],{3302:(e,r,t)=>{"use strict";t.r(r),t.d(r,{default:()=>h});var s=t(5155),n=t(2115),i=t(4624),a=t(2085),o=t(2596),l=t(9688);let d=(0,a.F)("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",{variants:{variant:{default:"bg-primary text-primary-foreground hover:bg-primary/90",destructive:"bg-destructive text-destructive-foreground hover:bg-destructive/90",outline:"border border-input bg-background hover:bg-accent hover:text-accent-foreground",secondary:"bg-secondary text-secondary-foreground hover:bg-secondary/80",ghost:"hover:bg-accent hover:text-accent-foreground",link:"text-primary underline-offset-4 hover:underline"},size:{default:"h-10 px-4 py-2",sm:"h-9 rounded-md px-3",lg:"h-11 rounded-md px-8",icon:"h-10 w-10"}},defaultVariants:{variant:"default",size:"default"}}),c=n.forwardRef((e,r)=>{let{className:t,variant:n,size:a,asChild:c=!1,...u}=e,g=c?i.DX:"button";return(0,s.jsx)(g,{className:function(){for(var e=arguments.length,r=Array(e),t=0;t<e;t++)r[t]=arguments[t];return(0,l.QP)((0,o.$)(r))}(d({variant:n,size:a,className:t})),ref:r,...u})});c.displayName="Button";var u=t(9099),g=t(1788),f=t(1415);function h(){return(0,s.jsx)("div",{className:"min-h-screen bg-background text-foreground flex items-center justify-center p-4",children:(0,s.jsxs)("div",{className:"max-w-2xl mx-auto text-center space-y-8",children:[(0,s.jsx)(f.zW,{triggerOnce:!0,children:(0,s.jsx)("h1",{className:"text-4xl md:text-6xl font-bold tracking-tight",children:"ArtistGrid Sheets"})}),(0,s.jsx)(f.zW,{delay:200,triggerOnce:!0,children:(0,s.jsxs)("p",{className:"text-lg md:text-xl text-muted-foreground leading-relaxed",children:["We pull from"," ",(0,s.jsx)("a",{href:"https://docs.google.com/spreadsheets/d/1zoOIaNbBvfuL3sS3824acpqGxOdSZSIHM8-nI9C-Vfc/htmlview",className:"underline text-white",target:"_blank",rel:"noopener noreferrer",children:"TrackerHub"})," ","and parse it into a CSV file. Still a work in progress."]})}),(0,s.jsx)(f.zW,{delay:400,triggerOnce:!0,children:(0,s.jsxs)("div",{className:"flex flex-col sm:flex-row gap-4 justify-center items-center",children:[(0,s.jsx)("a",{href:"https://github.com/ArtistGrid/Sheets",target:"_blank",rel:"noopener noreferrer",children:(0,s.jsxs)(c,{size:"lg",className:"w-full sm:w-auto",children:[(0,s.jsx)(u.A,{className:"mr-2 h-5 w-5"}),"View on GitHub"]})}),(0,s.jsxs)(c,{variant:"outline",size:"lg",className:"w-full sm:w-auto bg-transparent",children:[(0,s.jsx)(g.A,{className:"mr-2 h-5 w-5"}),(0,s.jsx)("a",{href:"https://sheets.artistgrid.cx/artists.csv",target:"_blank",rel:"noopener noreferrer",children:" Download CSV"})]})]})})]})})}},3368:(e,r,t)=>{Promise.resolve().then(t.bind(t,3302))}},e=>{var r=r=>e(e.s=r);e.O(0,[369,441,684,358],()=>r(3368)),_N_E=e.O()}]);
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[974],{4441:()=>{}},_=>{var e=e=>_(_.s=e);_.O(0,[441,684,358],()=>e(4441)),_N_E=_.O()}]);
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[974],{5942:(e,r,t)=>{Promise.resolve().then(t.bind(t,6937))},6937:(e,r,t)=>{"use strict";t.r(r),t.d(r,{default:()=>v});var s=t(5155);t(2115);var i=t(6673),a=t(3101),n=t(2821),o=t(5889);let d=(0,a.F)("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",{variants:{variant:{default:"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",destructive:"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",outline:"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",secondary:"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",ghost:"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",link:"text-primary underline-offset-4 hover:underline"},size:{default:"h-9 px-4 py-2 has-[>svg]:px-3",sm:"h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",lg:"h-10 rounded-md px-6 has-[>svg]:px-4",icon:"size-9"}},defaultVariants:{variant:"default",size:"default"}});function l(e){let{className:r,variant:t,size:a,asChild:l=!1,...c}=e,u=l?i.DX:"button";return(0,s.jsx)(u,{"data-slot":"button",className:function(){for(var e=arguments.length,r=Array(e),t=0;t<e;t++)r[t]=arguments[t];return(0,o.QP)((0,n.$)(r))}(d({variant:t,size:a,className:r})),...c})}var c=t(4684),u=t(7676),h=t(9715),g=t(4722);let b=[{name:"View on GitHub",href:"https://github.com/ArtistGrid/Sheets",icon:c.A,isExternal:!0},{name:"Download CSV",href:"https://sheets.artistgrid.cx/artists.csv",icon:u.A,downloadName:"artists.csv"},{name:"View HTML",href:"https://sheets.artistgrid.cx/artists.html",icon:h.A,isExternal:!0},{name:"Download XLSX",href:"https://sheets.artistgrid.cx/artists.xlsx",icon:g.A,downloadName:"ArtistGrid.xlsx"}];function v(){return(0,s.jsx)("div",{className:"min-h-screen bg-black text-white flex items-center justify-center p-4 sm:p-6",children:(0,s.jsxs)("div",{className:"w-full max-w-lg text-center bg-neutral-950 border border-neutral-800 rounded-2xl p-8 sm:p-12 shadow-2xl shadow-black/30 animate-in fade-in-0 zoom-in-95 duration-500",children:[(0,s.jsx)("h1",{className:"text-3xl sm:text-4xl font-bold bg-gradient-to-b from-neutral-50 to-neutral-400 bg-clip-text text-transparent mb-4",children:"ArtistGrid Sheets"}),(0,s.jsx)("p",{className:"text-neutral-400 mb-10 max-w-sm mx-auto",children:"We pull from TrackerHub and parse it into a CSV file. Still a work in progress."}),(0,s.jsx)("div",{className:"grid grid-cols-1 sm:grid-cols-2 gap-4",children:b.map(e=>(0,s.jsx)(l,{asChild:!0,className:"bg-white text-black hover:bg-neutral-200 font-semibold rounded-lg h-14 text-base transition-all duration-300 ease-out hover:-translate-y-1 hover:shadow-[0_0_30px_rgba(255,255,255,0.3)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-black focus-visible:ring-white",children:(0,s.jsxs)("a",{href:e.href,...e.isExternal&&{target:"_blank",rel:"noopener noreferrer"},...e.downloadName&&{download:e.downloadName},children:[(0,s.jsx)(e.icon,{className:"w-5 h-5 mr-2.5","aria-hidden":"true"}),e.name]})},e.name))})]})})}}},e=>{e.O(0,[78,441,255,358],()=>e(e.s=5942)),_N_E=e.O()}]);
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
templates/_next/static/chunks/main-615a29f30abc5587.js
Normal file
1
templates/_next/static/chunks/main-615a29f30abc5587.js
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
||||||
|
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[358],{8259:(e,s,n)=>{Promise.resolve().then(n.t.bind(n,7150,23)),Promise.resolve().then(n.t.bind(n,1959,23)),Promise.resolve().then(n.t.bind(n,7989,23)),Promise.resolve().then(n.t.bind(n,3886,23)),Promise.resolve().then(n.t.bind(n,9766,23)),Promise.resolve().then(n.t.bind(n,5278,23)),Promise.resolve().then(n.t.bind(n,8924,23)),Promise.resolve().then(n.t.bind(n,4431,23)),Promise.resolve().then(n.bind(n,622))},9393:()=>{}},e=>{var s=s=>e(e.s=s);e.O(0,[441,255],()=>(s(1666),s(8259))),_N_E=e.O()}]);
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[358],{2585:(e,s,n)=>{Promise.resolve().then(n.t.bind(n,894,23)),Promise.resolve().then(n.t.bind(n,4970,23)),Promise.resolve().then(n.t.bind(n,6614,23)),Promise.resolve().then(n.t.bind(n,6975,23)),Promise.resolve().then(n.t.bind(n,7555,23)),Promise.resolve().then(n.t.bind(n,4911,23)),Promise.resolve().then(n.t.bind(n,9665,23)),Promise.resolve().then(n.t.bind(n,1295,23))}},e=>{var s=s=>e(e.s=s);e.O(0,[441,684],()=>(s(5415),s(2585))),_N_E=e.O()}]);
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[358],{9941:(e,s,n)=>{Promise.resolve().then(n.t.bind(n,894,23)),Promise.resolve().then(n.t.bind(n,4970,23)),Promise.resolve().then(n.t.bind(n,6614,23)),Promise.resolve().then(n.t.bind(n,6975,23)),Promise.resolve().then(n.t.bind(n,7555,23)),Promise.resolve().then(n.t.bind(n,4911,23)),Promise.resolve().then(n.t.bind(n,9665,23)),Promise.resolve().then(n.t.bind(n,1295,23))}},e=>{var s=s=>e(e.s=s);e.O(0,[441,684],()=>(s(5415),s(9941))),_N_E=e.O()}]);
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[139,636],{326:(e,t,n)=>{(window.__NEXT_P=window.__NEXT_P||[]).push(["/_app",function(){return n(5139)}])},5139:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return s}});let u=n(4252),l=n(7876),a=u._(n(4232)),o=n(1033);async function r(e){let{Component:t,ctx:n}=e;return{pageProps:await (0,o.loadGetInitialProps)(t,n)}}class s extends a.default.Component{render(){let{Component:e,pageProps:t}=this.props;return(0,l.jsx)(e,{...t})}}s.origGetInitialProps=r,s.getInitialProps=r,("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)}},e=>{var t=t=>e(e.s=t);e.O(0,[593,792],()=>(t(326),t(6763))),_N_E=e.O()}]);
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[472,636],{326:(e,t,n)=>{(window.__NEXT_P=window.__NEXT_P||[]).push(["/_app",function(){return n(472)}])},472:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return s}});let u=n(4252),l=n(7876),a=u._(n(4232)),o=n(2746);async function r(e){let{Component:t,ctx:n}=e;return{pageProps:await (0,o.loadGetInitialProps)(t,n)}}class s extends a.default.Component{render(){let{Component:e,pageProps:t}=this.props;return(0,l.jsx)(e,{...t})}}s.origGetInitialProps=r,s.getInitialProps=r,("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)}},e=>{var t=t=>e(e.s=t);e.O(0,[593,792],()=>(t(326),t(4294))),_N_E=e.O()}]);
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
||||||
(()=>{"use strict";var e={},t={};function r(o){var n=t[o];if(void 0!==n)return n.exports;var a=t[o]={exports:{}},i=!0;try{e[o](a,a.exports,r),i=!1}finally{i&&delete t[o]}return a.exports}r.m=e,(()=>{var e=[];r.O=(t,o,n,a)=>{if(o){a=a||0;for(var i=e.length;i>0&&e[i-1][2]>a;i--)e[i]=e[i-1];e[i]=[o,n,a];return}for(var u=1/0,i=0;i<e.length;i++){for(var[o,n,a]=e[i],c=!0,l=0;l<o.length;l++)(!1&a||u>=a)&&Object.keys(r.O).every(e=>r.O[e](o[l]))?o.splice(l--,1):(c=!1,a<u&&(u=a));if(c){e.splice(i--,1);var s=n();void 0!==s&&(t=s)}}return t}})(),(()=>{var e,t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__;r.t=function(o,n){if(1&n&&(o=this(o)),8&n||"object"==typeof o&&o&&(4&n&&o.__esModule||16&n&&"function"==typeof o.then))return o;var a=Object.create(null);r.r(a);var i={};e=e||[null,t({}),t([]),t(t)];for(var u=2&n&&o;"object"==typeof u&&!~e.indexOf(u);u=t(u))Object.getOwnPropertyNames(u).forEach(e=>i[e]=()=>o[e]);return i.default=()=>o,r.d(a,i),a}})(),r.d=(e,t)=>{for(var o in t)r.o(t,o)&&!r.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:t[o]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((t,o)=>(r.f[o](e,t),t),[])),r.u=e=>"static/chunks/"+e+"."+({341:"2903e54d3da731c1",472:"a3826d29d6854395"})[e]+".js",r.miniCssF=e=>{},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{var e={},t="_N_E:";r.l=(o,n,a,i)=>{if(e[o]){e[o].push(n);return}if(void 0!==a)for(var u,c,l=document.getElementsByTagName("script"),s=0;s<l.length;s++){var d=l[s];if(d.getAttribute("src")==o||d.getAttribute("data-webpack")==t+a){u=d;break}}u||(c=!0,(u=document.createElement("script")).charset="utf-8",u.timeout=120,r.nc&&u.setAttribute("nonce",r.nc),u.setAttribute("data-webpack",t+a),u.src=r.tu(o)),e[o]=[n];var f=(t,r)=>{u.onerror=u.onload=null,clearTimeout(p);var n=e[o];if(delete e[o],u.parentNode&&u.parentNode.removeChild(u),n&&n.forEach(e=>e(r)),t)return t(r)},p=setTimeout(f.bind(null,void 0,{type:"timeout",target:u}),12e4);u.onerror=f.bind(null,u.onerror),u.onload=f.bind(null,u.onload),c&&document.head.appendChild(u)}})(),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{var e;r.tt=()=>(void 0===e&&(e={createScriptURL:e=>e},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("nextjs#bundler",e))),e)})(),r.tu=e=>r.tt().createScriptURL(e),r.p="/_next/",(()=>{var e={68:0,385:0};r.f.j=(t,o)=>{var n=r.o(e,t)?e[t]:void 0;if(0!==n){if(n)o.push(n[2]);else if(/^(385|68)$/.test(t))e[t]=0;else{var a=new Promise((r,o)=>n=e[t]=[r,o]);o.push(n[2]=a);var i=r.p+r.u(t),u=Error();r.l(i,o=>{if(r.o(e,t)&&(0!==(n=e[t])&&(e[t]=void 0),n)){var a=o&&("load"===o.type?"missing":o.type),i=o&&o.target&&o.target.src;u.message="Loading chunk "+t+" failed.\n("+a+": "+i+")",u.name="ChunkLoadError",u.type=a,u.request=i,n[1](u)}},"chunk-"+t,t)}}},r.O.j=t=>0===e[t];var t=(t,o)=>{var n,a,[i,u,c]=o,l=0;if(i.some(t=>0!==e[t])){for(n in u)r.o(u,n)&&(r.m[n]=u[n]);if(c)var s=c(r)}for(t&&t(o);l<i.length;l++)a=i[l],r.o(e,a)&&e[a]&&e[a][0](),e[a]=0;return r.O(s)},o=self.webpackChunk_N_E=self.webpackChunk_N_E||[];o.forEach(t.bind(null,0)),o.push=t.bind(null,o.push.bind(o))})()})();
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
(()=>{"use strict";var e={},t={};function r(o){var n=t[o];if(void 0!==n)return n.exports;var a=t[o]={exports:{}},i=!0;try{e[o](a,a.exports,r),i=!1}finally{i&&delete t[o]}return a.exports}r.m=e,(()=>{var e=[];r.O=(t,o,n,a)=>{if(o){a=a||0;for(var i=e.length;i>0&&e[i-1][2]>a;i--)e[i]=e[i-1];e[i]=[o,n,a];return}for(var u=1/0,i=0;i<e.length;i++){for(var[o,n,a]=e[i],l=!0,c=0;c<o.length;c++)(!1&a||u>=a)&&Object.keys(r.O).every(e=>r.O[e](o[c]))?o.splice(c--,1):(l=!1,a<u&&(u=a));if(l){e.splice(i--,1);var s=n();void 0!==s&&(t=s)}}return t}})(),(()=>{var e,t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__;r.t=function(o,n){if(1&n&&(o=this(o)),8&n||"object"==typeof o&&o&&(4&n&&o.__esModule||16&n&&"function"==typeof o.then))return o;var a=Object.create(null);r.r(a);var i={};e=e||[null,t({}),t([]),t(t)];for(var u=2&n&&o;"object"==typeof u&&!~e.indexOf(u);u=t(u))Object.getOwnPropertyNames(u).forEach(e=>i[e]=()=>o[e]);return i.default=()=>o,r.d(a,i),a}})(),r.d=(e,t)=>{for(var o in t)r.o(t,o)&&!r.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:t[o]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((t,o)=>(r.f[o](e,t),t),[])),r.u=e=>"static/chunks/"+e+"."+({139:"7a5a8e93a21948c1",646:"f342b7cffc01feb0"})[e]+".js",r.miniCssF=e=>{},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{var e={},t="_N_E:";r.l=(o,n,a,i)=>{if(e[o])return void e[o].push(n);if(void 0!==a)for(var u,l,c=document.getElementsByTagName("script"),s=0;s<c.length;s++){var f=c[s];if(f.getAttribute("src")==o||f.getAttribute("data-webpack")==t+a){u=f;break}}u||(l=!0,(u=document.createElement("script")).charset="utf-8",u.timeout=120,r.nc&&u.setAttribute("nonce",r.nc),u.setAttribute("data-webpack",t+a),u.src=r.tu(o)),e[o]=[n];var d=(t,r)=>{u.onerror=u.onload=null,clearTimeout(p);var n=e[o];if(delete e[o],u.parentNode&&u.parentNode.removeChild(u),n&&n.forEach(e=>e(r)),t)return t(r)},p=setTimeout(d.bind(null,void 0,{type:"timeout",target:u}),12e4);u.onerror=d.bind(null,u.onerror),u.onload=d.bind(null,u.onload),l&&document.head.appendChild(u)}})(),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{var e;r.tt=()=>(void 0===e&&(e={createScriptURL:e=>e},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("nextjs#bundler",e))),e)})(),r.tu=e=>r.tt().createScriptURL(e),r.p="/_next/",(()=>{var e={68:0,587:0};r.f.j=(t,o)=>{var n=r.o(e,t)?e[t]:void 0;if(0!==n)if(n)o.push(n[2]);else if(/^(587|68)$/.test(t))e[t]=0;else{var a=new Promise((r,o)=>n=e[t]=[r,o]);o.push(n[2]=a);var i=r.p+r.u(t),u=Error();r.l(i,o=>{if(r.o(e,t)&&(0!==(n=e[t])&&(e[t]=void 0),n)){var a=o&&("load"===o.type?"missing":o.type),i=o&&o.target&&o.target.src;u.message="Loading chunk "+t+" failed.\n("+a+": "+i+")",u.name="ChunkLoadError",u.type=a,u.request=i,n[1](u)}},"chunk-"+t,t)}},r.O.j=t=>0===e[t];var t=(t,o)=>{var n,a,[i,u,l]=o,c=0;if(i.some(t=>0!==e[t])){for(n in u)r.o(u,n)&&(r.m[n]=u[n]);if(l)var s=l(r)}for(t&&t(o);c<i.length;c++)a=i[c],r.o(e,a)&&e[a]&&e[a][0](),e[a]=0;return r.O(s)},o=self.webpackChunk_N_E=self.webpackChunk_N_E||[];o.forEach(t.bind(null,0)),o.push=t.bind(null,o.push.bind(o))})()})();
|
||||||
File diff suppressed because one or more lines are too long
3
templates/_next/static/css/323a36643e3c1db1.css
Normal file
3
templates/_next/static/css/323a36643e3c1db1.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
||||||
self.__BUILD_MANIFEST=function(e,r,t){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},__routerFilterStatic:{numItems:2,errorRate:1e-4,numBits:39,numHashes:14,bitArray:[0,1,1,0,r,e,e,r,r,e,e,r,e,e,e,r,r,e,e,e,e,r,e,r,r,r,r,e,e,e,r,e,r,e,r,e,e,e,r]},__routerFilterDynamic:{numItems:r,errorRate:1e-4,numBits:r,numHashes:null,bitArray:[]},"/_error":["static/chunks/pages/_error-71d2b6a7b832d02a.js"],sortedPages:["/_app","/_error"]}}(1,0,1e-4),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
self.__SSG_MANIFEST=new Set([]);self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB()
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
self.__BUILD_MANIFEST=function(e,r,t){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},__routerFilterStatic:{numItems:3,errorRate:1e-4,numBits:58,numHashes:14,bitArray:[1,1,0,e,0,e,e,r,e,e,r,e,e,e,r,e,r,r,e,r,r,r,e,r,r,r,r,r,e,r,e,e,e,e,r,e,e,r,e,e,e,r,e,r,e,r,r,e,e,e,r,r,e,e,e,r,e,e]},__routerFilterDynamic:{numItems:r,errorRate:1e-4,numBits:r,numHashes:null,bitArray:[]},"/_error":["static/chunks/pages/_error-013f4188946cdd04.js"],sortedPages:["/_app","/_error"]}}(1,0,1e-4),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
|
||||||
Binary file not shown.
Binary file not shown.
BIN
templates/_next/static/media/569ce4b8f30dc480-s.p.woff2
Normal file
BIN
templates/_next/static/media/569ce4b8f30dc480-s.p.woff2
Normal file
Binary file not shown.
Binary file not shown.
BIN
templates/_next/static/media/747892c23ea88013-s.woff2
Normal file
BIN
templates/_next/static/media/747892c23ea88013-s.woff2
Normal file
Binary file not shown.
BIN
templates/_next/static/media/8d697b304b401681-s.woff2
Normal file
BIN
templates/_next/static/media/8d697b304b401681-s.woff2
Normal file
Binary file not shown.
Binary file not shown.
BIN
templates/_next/static/media/93f479601ee12b01-s.p.woff2
Normal file
BIN
templates/_next/static/media/93f479601ee12b01-s.p.woff2
Normal file
Binary file not shown.
BIN
templates/_next/static/media/9610d9e46709d722-s.woff2
Normal file
BIN
templates/_next/static/media/9610d9e46709d722-s.woff2
Normal file
Binary file not shown.
Binary file not shown.
BIN
templates/_next/static/media/ba015fad6dcf6784-s.woff2
Normal file
BIN
templates/_next/static/media/ba015fad6dcf6784-s.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
|
|
@ -1,18 +1,20 @@
|
||||||
1:"$Sreact.fragment"
|
1:"$Sreact.fragment"
|
||||||
2:I[7780,["177","static/chunks/app/layout-546c94f4a8071dd0.js"],"ThemeProvider"]
|
2:I[9766,[],""]
|
||||||
3:I[7555,[],""]
|
3:I[8924,[],""]
|
||||||
4:I[1295,[],""]
|
4:I[1959,[],"ClientPageRoot"]
|
||||||
5:I[894,[],"ClientPageRoot"]
|
5:I[6937,["78","static/chunks/78-578bf7339c7a46f2.js","974","static/chunks/app/page-d0a5f652f053f84b.js"],"default"]
|
||||||
6:I[3302,["369","static/chunks/369-033af3d47e75a2e9.js","974","static/chunks/app/page-369ac552c0f033d3.js"],"default"]
|
8:I[4431,[],"OutletBoundary"]
|
||||||
9:I[9665,[],"OutletBoundary"]
|
a:I[5278,[],"AsyncMetadataOutlet"]
|
||||||
c:I[9665,[],"ViewportBoundary"]
|
c:I[4431,[],"ViewportBoundary"]
|
||||||
e:I[9665,[],"MetadataBoundary"]
|
e:I[4431,[],"MetadataBoundary"]
|
||||||
10:I[6614,[],""]
|
f:"$Sreact.suspense"
|
||||||
:HL["/_next/static/css/5c4bd492bdff6129.css","style"]
|
11:I[7150,[],""]
|
||||||
0:{"P":null,"b":"GswdzUCnMYMZOzXXFJekf","p":"","c":["",""],"i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/5c4bd492bdff6129.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","suppressHydrationWarning":true,"children":["$","body",null,{"className":"__className_e8ce0c","children":["$","$L2",null,{"attribute":"class","defaultTheme":"dark","enableSystem":true,"disableTransitionOnChange":true,"children":["$","$L3",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L5",null,{"Component":"$6","searchParams":{},"params":{},"promises":["$@7","$@8"]}],"$undefined",null,["$","$L9",null,{"children":["$La","$Lb",null]}]]}],{},null,false]},null,false],["$","$1","h",{"children":[null,["$","$1","NLGqWe3sR1VBX3v8KTt8Y",{"children":[["$","$Lc",null,{"children":"$Ld"}],null]}],["$","$Le",null,{"children":"$Lf"}]]}],false]],"m":"$undefined","G":["$10","$undefined"],"s":false,"S":true}
|
:HL["/_next/static/css/323a36643e3c1db1.css","style"]
|
||||||
7:{}
|
0:{"P":null,"b":"luWZjwc8VZvb8hGtFuZa2","p":"","c":["",""],"i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/323a36643e3c1db1.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__variable_5cfdac __variable_9a8899 antialiased","children":["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]]}],{"children":["__PAGE__",["$","$1","c",{"children":[["$","$L4",null,{"Component":"$5","searchParams":{},"params":{},"promises":["$@6","$@7"]}],null,["$","$L8",null,{"children":["$L9",["$","$La",null,{"promise":"$@b"}]]}]]}],{},null,false]},null,false],["$","$1","h",{"children":[null,[["$","$Lc",null,{"children":"$Ld"}],null],["$","$Le",null,{"children":["$","div",null,{"hidden":true,"children":["$","$f",null,{"fallback":null,"children":"$L10"}]}]}]]}],false]],"m":"$undefined","G":["$11",[]],"s":false,"S":true}
|
||||||
8:{}
|
6:{}
|
||||||
|
7:"$0:f:0:1:2:children:1:props:children:0:props:params"
|
||||||
d:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
|
d:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
|
||||||
a:null
|
9:null
|
||||||
b:null
|
12:I[622,[],"IconMark"]
|
||||||
f:[["$","title","0",{"children":"ArtistGrid Sheets"}],["$","link","1",{"rel":"icon","href":"./favicon.png"}]]
|
b:{"metadata":[["$","title","0",{"children":"ArtistGrid Sheets"}],["$","meta","1",{"name":"description","content":"We pull from TrackerHub and parse it into a CSV file. Still a work in progress."}],["$","link","2",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","3",{"rel":"icon","href":"/favicon.png"}],["$","$L12","4",{}]],"error":null,"digest":"$undefined"}
|
||||||
|
10:"$b:metadata"
|
||||||
|
|
|
||||||
|
|
@ -1,76 +1,97 @@
|
||||||
|
# update_loop.py
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
from downloader import download_zip_and_extract_html, download_xlsx
|
|
||||||
from parser import generate_csv
|
|
||||||
from diff import read_csv_to_dict, detect_changes
|
|
||||||
from archive import archive_all_urls
|
from archive import archive_all_urls
|
||||||
|
from config import CSV_FILENAME, HTML_FILENAME, XLSX_FILENAME
|
||||||
|
from diff import detect_changes, read_csv_to_dict
|
||||||
|
from downloader import download_xlsx, download_zip_and_extract_html
|
||||||
from notify import send_discord_message
|
from notify import send_discord_message
|
||||||
|
from parser import generate_csv
|
||||||
from utils import hash_file
|
from utils import hash_file
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
last_html_hash = None
|
last_html_hash = None
|
||||||
last_csv_data = {}
|
last_csv_data = {}
|
||||||
INFO_PATH = "info/status.json"
|
INFO_PATH = os.path.join("info", "status.json")
|
||||||
|
UPDATE_INTERVAL_SECONDS = 600
|
||||||
|
|
||||||
def write_info(html_hash, csv_hash, xlsx_hash):
|
|
||||||
|
def write_info(html_hash: str, csv_hash: str, xlsx_hash: str, is_archived: bool):
|
||||||
os.makedirs("info", exist_ok=True)
|
os.makedirs("info", exist_ok=True)
|
||||||
info = {
|
now_iso = datetime.now(timezone.utc).isoformat()
|
||||||
"last_updated": datetime.utcnow().isoformat() + "Z",
|
|
||||||
"files": {
|
try:
|
||||||
"Artists.html": {
|
with open(INFO_PATH, "r") as f:
|
||||||
"hash": html_hash,
|
info = json.load(f)
|
||||||
"last_archived": datetime.utcnow().isoformat() + "Z"
|
except (FileNotFoundError, json.JSONDecodeError):
|
||||||
},
|
info = {"files": {HTML_FILENAME: {}}}
|
||||||
"artists.csv": {
|
|
||||||
"hash": csv_hash
|
info["last_updated"] = now_iso
|
||||||
},
|
info["files"][HTML_FILENAME]["hash"] = html_hash
|
||||||
"artists.xlsx": {
|
if is_archived:
|
||||||
"hash": xlsx_hash
|
info["files"][HTML_FILENAME]["last_archived"] = now_iso
|
||||||
}
|
|
||||||
}
|
info["files"][CSV_FILENAME] = {"hash": csv_hash}
|
||||||
}
|
info["files"][XLSX_FILENAME] = {"hash": xlsx_hash}
|
||||||
|
|
||||||
with open(INFO_PATH, "w") as f:
|
with open(INFO_PATH, "w") as f:
|
||||||
json.dump(info, f, indent=2)
|
json.dump(info, f, indent=2)
|
||||||
|
|
||||||
|
|
||||||
def update_loop():
|
def update_loop():
|
||||||
global last_html_hash, last_csv_data
|
global last_html_hash, last_csv_data
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
logger.info("--- Starting update cycle ---")
|
||||||
try:
|
try:
|
||||||
download_zip_and_extract_html()
|
download_zip_and_extract_html()
|
||||||
download_xlsx()
|
download_xlsx()
|
||||||
generate_csv()
|
generate_csv()
|
||||||
|
|
||||||
html_hash = hash_file("Artists.html")
|
if not all(
|
||||||
csv_hash = hash_file("artists.csv")
|
os.path.exists(f) for f in [HTML_FILENAME, CSV_FILENAME, XLSX_FILENAME]
|
||||||
xlsx_hash = hash_file("artists.xlsx")
|
):
|
||||||
|
logger.warning(
|
||||||
|
"One or more files are missing after download/parse. Skipping this cycle."
|
||||||
|
)
|
||||||
|
time.sleep(UPDATE_INTERVAL_SECONDS)
|
||||||
|
continue
|
||||||
|
|
||||||
current_data = read_csv_to_dict("artists.csv")
|
html_hash = hash_file(HTML_FILENAME)
|
||||||
|
csv_hash = hash_file(CSV_FILENAME)
|
||||||
|
xlsx_hash = hash_file(XLSX_FILENAME)
|
||||||
|
current_csv_data = read_csv_to_dict(CSV_FILENAME)
|
||||||
|
|
||||||
|
archived_this_cycle = False
|
||||||
if last_html_hash is None:
|
if last_html_hash is None:
|
||||||
print("ℹ️ Initial HTML hash stored.")
|
logger.info("First run: storing initial file hashes.")
|
||||||
elif html_hash != last_html_hash:
|
elif html_hash != last_html_hash:
|
||||||
print("🔔 Artists.html has changed! Archiving URLs...")
|
logger.info("🔔 Artists.html has changed! Checking for data differences.")
|
||||||
|
changes = detect_changes(last_csv_data, current_csv_data)
|
||||||
changes = detect_changes(last_csv_data, current_data)
|
|
||||||
if changes:
|
if changes:
|
||||||
message = "**CSV Update Detected:**\n" + "\n".join(changes)
|
message = "**Tracker Update Detected:**\n" + "\n".join(changes)
|
||||||
send_discord_message(message)
|
send_discord_message(message)
|
||||||
else:
|
|
||||||
print("ℹ️ No detectable content changes found in CSV.")
|
|
||||||
|
|
||||||
archive_all_urls()
|
archive_all_urls()
|
||||||
|
archived_this_cycle = True
|
||||||
else:
|
else:
|
||||||
print("ℹ️ Artists.html unchanged. No archiving needed.")
|
logger.info("ℹ️ HTML hash changed, but no data differences found.")
|
||||||
|
else:
|
||||||
write_info(html_hash, csv_hash, xlsx_hash)
|
logger.info("ℹ️ Artists.html is unchanged.")
|
||||||
|
|
||||||
|
write_info(html_hash, csv_hash, xlsx_hash, is_archived=archived_this_cycle)
|
||||||
last_html_hash = html_hash
|
last_html_hash = html_hash
|
||||||
last_csv_data = current_data
|
last_csv_data = current_csv_data
|
||||||
|
logger.info("--- Update cycle finished ---")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"⚠️ Error updating files: {e}")
|
logger.critical(
|
||||||
|
f"An unexpected error occurred in the update loop: {e}", exc_info=True
|
||||||
|
)
|
||||||
|
|
||||||
time.sleep(600)
|
logger.info(f"Sleeping for {UPDATE_INTERVAL_SECONDS} seconds...")
|
||||||
|
time.sleep(UPDATE_INTERVAL_SECONDS)
|
||||||
22
utils.py
22
utils.py
|
|
@ -1,14 +1,22 @@
|
||||||
import re, hashlib
|
# utils.py
|
||||||
|
import hashlib
|
||||||
|
import re
|
||||||
|
|
||||||
def clean_artist_name(text):
|
|
||||||
return re.sub(r'[⭐🤖🎭\u2B50\uFE0F]', '', text).strip()
|
|
||||||
|
|
||||||
def force_star_flag(starred=True):
|
def clean_artist_name(text: str) -> str:
|
||||||
|
return re.sub(r"[⭐🤖🎭\u2B50\uFE0F]", "", text).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def force_star_flag(starred: bool = True) -> str:
|
||||||
return "Yes" if starred else "No"
|
return "Yes" if starred else "No"
|
||||||
|
|
||||||
def hash_file(filename):
|
|
||||||
|
def hash_file(filename: str, block_size: int = 65536) -> str:
|
||||||
hasher = hashlib.sha256()
|
hasher = hashlib.sha256()
|
||||||
|
try:
|
||||||
with open(filename, "rb") as f:
|
with open(filename, "rb") as f:
|
||||||
buf = f.read()
|
for block in iter(lambda: f.read(block_size), b""):
|
||||||
hasher.update(buf)
|
hasher.update(block)
|
||||||
|
except FileNotFoundError:
|
||||||
|
return "file_not_found"
|
||||||
return hasher.hexdigest()
|
return hasher.hexdigest()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue