From 5f6bf410c090a75c7634356cad1c4deaff9d044b Mon Sep 17 00:00:00 2001 From: Yuri Tatishchev Date: Wed, 4 Feb 2026 22:57:33 -0800 Subject: [PATCH] add zod schemas for amq json data --- src/lib/types/amq/anime-extended.ts | 385 ++++++++++++++++++++++++++++ src/lib/types/amq/anime.ts | 61 +++++ src/lib/types/amq/artist.ts | 13 + src/lib/types/amq/group.ts | 14 + src/lib/types/amq/index.ts | 5 + src/lib/types/amq/song.ts | 29 +++ 6 files changed, 507 insertions(+) create mode 100644 src/lib/types/amq/anime-extended.ts create mode 100644 src/lib/types/amq/anime.ts create mode 100644 src/lib/types/amq/artist.ts create mode 100644 src/lib/types/amq/group.ts create mode 100644 src/lib/types/amq/index.ts create mode 100644 src/lib/types/amq/song.ts diff --git a/src/lib/types/amq/anime-extended.ts b/src/lib/types/amq/anime-extended.ts new file mode 100644 index 0000000..a2841bb --- /dev/null +++ b/src/lib/types/amq/anime-extended.ts @@ -0,0 +1,385 @@ +import { z } from "zod"; + +export const AmqAnimeCategory = z.enum([ + "TV", + "OVA", + "Movie", + "Season", + "movie", + "TV Special", + "ONA", + "special", + "Music Video", + "Game Cutscene Compilation", + "Doujin", +]); + +export const AmqAnimeGenre = z.enum([ + "Action", + "Adventure", + "Comedy", + "Drama", + "Ecchi", + "Fantasy", + "Horror", + "Mahou Shoujo", + "Mecha", + "Music", + "Mystery", + "Psychological", + "Romance", + "Sci-Fi", + "Slice of Life", + "Sports", + "Supernatural", + "Thriller", +]); + +export const AmqAnimeTag = z.enum([ + "Achromatic", + "Achronological Order", + "Acrobatics", + "Acting", + "Adoption", + "Advertisement", + "Afterlife", + "Age Gap", + "Age Regression", + "Agender", + "Agriculture", + "Airsoft", + "Alchemy", + "Aliens", + "Alternate Universe", + "American Football", + "Amnesia", + "Anachronism", + "Ancient China", + "Angels", + "Animals", + "Anthology", + "Anthropomorphism", + "Anti-Hero", + "Archery", + "Aromantic", + "Arranged Marriage", + "Artificial Intelligence", + "Asexual", + "Assassins", + "Astronomy", + "Athletics", + "Augmented Reality", + "Autobiographical", + "Aviation", + "Badminton", + "Ballet", + "Band", + "Bar", + "Baseball", + "Basketball", + "Battle Royale", + "Biographical", + "Bisexual", + "Blackmail", + "Board Game", + "Boarding School", + "Body Horror", + "Body Image", + "Body Swapping", + "Bowling", + "Boxing", + "Boys' Love", + "Bullying", + "Butler", + "CGI", + "Calligraphy", + "Camping", + "Cannibalism", + "Card Battle", + "Cars", + "Centaur", + "Cheerleading", + "Chibi", + "Chimera", + "Chuunibyou", + "Circus", + "Class Struggle", + "Classic Literature", + "Classical Music", + "Clone", + "Coastal", + "Cohabitation", + "College", + "Coming of Age", + "Conspiracy", + "Cosmic Horror", + "Cosplay", + "Cowboys", + "Creature Taming", + "Crime", + "Criminal Organization", + "Crossdressing", + "Crossover", + "Cult", + "Cultivation", + "Curses", + "Cute Boys Doing Cute Things", + "Cute Girls Doing Cute Things", + "Cyberpunk", + "Cyborg", + "Cycling", + "Dancing", + "Death Game", + "Delinquents", + "Demons", + "Denpa", + "Desert", + "Detective", + "Dinosaurs", + "Disability", + "Dissociative Identities", + "Dragons", + "Drawing", + "Drugs", + "Dullahan", + "Dungeon", + "Dystopian", + "E-Sports", + "Eco-Horror", + "Economics", + "Educational", + "Elderly Protagonist", + "Elf", + "Ensemble Cast", + "Environmental", + "Episodic", + "Ero Guro", + "Espionage", + "Estranged Family", + "Exorcism", + "Fairy", + "Fairy Tale", + "Fake Relationship", + "Family Life", + "Fashion", + "Female Harem", + "Female Protagonist", + "Femboy", + "Fencing", + "Filmmaking", + "Firefighters", + "Fishing", + "Fitness", + "Flash", + "Food", + "Football", + "Foreign", + "Found Family", + "Fugitive", + "Full CGI", + "Gambling", + "Gangs", + "Gender Bending", + "Ghost", + "Go", + "Goblin", + "Gods", + "Golf", + "Gore", + "Guns", + "Gyaru", + "Handball", + "Henshin", + "Heterosexual", + "Hikikomori", + "Hip-hop Music", + "Historical", + "Homeless", + "Horticulture", + "Ice Skating", + "Idol", + "Indigenous Cultures", + "Inn", + "Isekai", + "Iyashikei", + "Jazz Music", + "Josei", + "Judo", + "Kabuki", + "Kaiju", + "Karuta", + "Kemonomimi", + "Kids", + "Kingdom Management", + "Konbini", + "Kuudere", + "LGBTQ+ Themes", + "Lacrosse", + "Language Barrier", + "Lost Civilization", + "Love Triangle", + "Mafia", + "Magic", + "Mahjong", + "Maids", + "Makeup", + "Male Harem", + "Male Protagonist", + "Manzai", + "Marriage", + "Martial Arts", + "Matchmaking", + "Matriarchy", + "Medicine", + "Medieval", + "Memory Manipulation", + "Mermaid", + "Meta", + "Metal Music", + "Military", + "Mixed Gender Harem", + "Mixed Media", + "Modeling", + "Monster Boy", + "Monster Girl", + "Mopeds", + "Motorcycles", + "Mountaineering", + "Musical Theater", + "Mythology", + "Natural Disaster", + "Necromancy", + "Nekomimi", + "Ninja", + "No Dialogue", + "Noir", + "Non-fiction", + "Nudity", + "Nun", + "Office", + "Office Lady", + "Oiran", + "Ojou-sama", + "Orphan", + "Otaku Culture", + "Outdoor Activities", + "POV", + "Pandemic", + "Parenthood", + "Parkour", + "Parody", + "Philosophy", + "Photography", + "Pirates", + "Poker", + "Police", + "Politics", + "Polyamorous", + "Post-Apocalyptic", + "Pregnancy", + "Primarily Adult Cast", + "Primarily Animal Cast", + "Primarily Child Cast", + "Primarily Female Cast", + "Primarily Male Cast", + "Primarily Teen Cast", + "Prison", + "Proxy Battle", + "Psychosexual", + "Puppetry", + "Rakugo", + "Real Robot", + "Rehabilitation", + "Reincarnation", + "Religion", + "Rescue", + "Restaurant", + "Revenge", + "Reverse Isekai", + "Robots", + "Rock Music", + "Rotoscoping", + "Royal Affairs", + "Rugby", + "Rural", + "Samurai", + "Satire", + "School", + "School Club", + "Scuba Diving", + "Seinen", + "Shapeshifting", + "Ships", + "Shogi", + "Shoujo", + "Shounen", + "Shrine Maiden", + "Skateboarding", + "Skeleton", + "Slapstick", + "Slavery", + "Snowscape", + "Software Development", + "Space", + "Space Opera", + "Spearplay", + "Steampunk", + "Stop Motion", + "Succubus", + "Suicide", + "Sumo", + "Super Power", + "Super Robot", + "Superhero", + "Surfing", + "Surreal Comedy", + "Survival", + "Swimming", + "Swordplay", + "Table Tennis", + "Tanks", + "Tanned Skin", + "Teacher", + "Teens' Love", + "Tennis", + "Terrorism", + "Time Loop", + "Time Manipulation", + "Time Skip", + "Tokusatsu", + "Tomboy", + "Torture", + "Tragedy", + "Trains", + "Transgender", + "Travel", + "Triads", + "Tsundere", + "Twins", + "Unrequited Love", + "Urban", + "Urban Fantasy", + "VTuber", + "Vampire", + "Vertical Video", + "Veterinarian", + "Video Games", + "Vikings", + "Villainess", + "Virtual World", + "Vocal Synth", + "Volleyball", + "War", + "Werewolf", + "Wilderness", + "Witch", + "Work", + "Wrestling", + "Writing", + "Wuxia", + "Yakuza", + "Yandere", + "Youkai", + "Yuri", + "Zombie", +]); diff --git a/src/lib/types/amq/anime.ts b/src/lib/types/amq/anime.ts new file mode 100644 index 0000000..47b4d97 --- /dev/null +++ b/src/lib/types/amq/anime.ts @@ -0,0 +1,61 @@ +import { z } from "zod"; + +import { AmqAnimeCategory, AmqAnimeGenre, AmqAnimeTag } from "./anime-extended"; + +export const Season = z.enum({ + Winter: 0, + Spring: 1, + Summer: 2, + Fall: 3, +} as const); + +export const SongLinkType = z.enum({ + OP: 1, + ED: 2, + INS: 3, +} as const); + +const BooleanInt = z.enum({ + false: 0, + true: 1, +} as const); + +export const AmqSongLink = z.object({ + songId: z.int().positive(), + number: z.int().nonnegative(), + type: SongLinkType, + annSongId: z.int().positive(), + uploaded: BooleanInt, + rebroadcast: BooleanInt, + dub: BooleanInt, +}); + +export const AmqAnimeSchema = z.object({ + annId: z.int().positive(), + aniListId: z.int().positive().nullable(), + malId: z.int().positive(), + kitsuId: z.int().positive().nullable(), + category: z.object({ + name: AmqAnimeCategory, + number: z.union([z.number(), z.string()]).nullable(), + }), + genres: z.array(AmqAnimeGenre), + tags: z.array(AmqAnimeTag), + mainName: z.string(), + mainNames: z.object({ + EN: z.string().nullable(), + JA: z.string().nullable(), + }), + names: z.array( + z.object({ + language: z.enum(["EN", "JA"]), + name: z.string(), + }), + ), + year: z.int().positive(), + seasonId: Season, + songLinks: z.array(AmqSongLink), + opCount: z.int().nonnegative(), + edCount: z.int().nonnegative(), + insertCount: z.int().nonnegative(), +}); diff --git a/src/lib/types/amq/artist.ts b/src/lib/types/amq/artist.ts new file mode 100644 index 0000000..21391a4 --- /dev/null +++ b/src/lib/types/amq/artist.ts @@ -0,0 +1,13 @@ +import { z } from "zod"; + +export const AmqArtistSchema = z.object({ + songArtistId: z.int().positive(), + name: z.string(), + inGroups: z.array(z.int().positive()), + altNames: z.array( + z.object({ + songArtistId: z.int().positive(), + name: z.string(), + }), + ), +}); diff --git a/src/lib/types/amq/group.ts b/src/lib/types/amq/group.ts new file mode 100644 index 0000000..6b70488 --- /dev/null +++ b/src/lib/types/amq/group.ts @@ -0,0 +1,14 @@ +import { z } from "zod"; + +export const AmqGroupSchema = z.object({ + songGroupId: z.int().positive(), + name: z.string(), + artistMembers: z.array(z.int().positive()), + groupMembers: z.array(z.int().positive()), + altNames: z.array( + z.object({ + songGroupId: z.int().positive(), + name: z.string(), + }), + ), +}); diff --git a/src/lib/types/amq/index.ts b/src/lib/types/amq/index.ts new file mode 100644 index 0000000..361e3c1 --- /dev/null +++ b/src/lib/types/amq/index.ts @@ -0,0 +1,5 @@ +export * from "./anime"; +export * from "./anime-extended"; +export * from "./artist"; +export * from "./group"; +export * from "./song"; diff --git a/src/lib/types/amq/song.ts b/src/lib/types/amq/song.ts new file mode 100644 index 0000000..8dbe25b --- /dev/null +++ b/src/lib/types/amq/song.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; + +export const AmqSongCategory = z.enum({ + none: 0, + instrumental: 1, + chanting: 2, + character: 3, + standard: 4, +}); + +export const AmqSongSchema = z.object({ + annSongId: z.int().positive(), + songId: z.int().positive(), + name: z.string(), + category: AmqSongCategory, + songArtistId: z.int().positive().nullable(), + songGroupId: z.int().positive().nullable(), + composerArtistId: z.int().positive().nullable(), + composerGroupId: z.int().positive().nullable(), + arrangerArtistId: z.int().positive().nullable(), + arrangerGroupId: z.int().positive().nullable(), + globalPercent: z.number().int().min(0).max(100), + fileName: z.string().nullable(), + fileNameMap: z.object({ + 0: z.string().optional(), + 480: z.string().optional(), + 720: z.string().optional(), + }), +});