Files
silo-server/internal/librarykind/librarykind.go
866392fecd feat(notifications): announce new audiobooks and ebooks on server channels (#260)
Audiobook and ebook libraries previously never entered the Recently
Added pipeline: availability detection only ran for TV/movie/mixed
libraries and release_events only knew episode/movie kinds, so server
channels (Discord/generic webhooks) could not announce new audiobooks
or ebooks.

Generalize the movie path into a flat-item-kind registry
(internal/notifications/item_kind.go) driving availability detection,
recording, channel toggles, payload rendering, test fixtures, and the
admin backfill seeder. New kinds share a kind-discriminated
item_availability table; movie_availability stays as-is. Channels gain
notify_new_audiobooks/notify_new_ebooks toggles (default on, additive
API fields) and embeds carry the author from item_people. Flood-safe by
construction: existing libraries seed silently on their first
post-upgrade full scan.

Extract internal/librarykind to replace the is*LibraryType helper
copies that had drifted across scanner, libraryingest, and metadata
(metadata's movie check silently included mixed; now spelled
explicitly).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 11:42:28 -04:00

104 lines
2.8 KiB
Go

// Package librarykind is the single home of media_folders.type
// classification. The type column is free text ("movies", "TV", " Audiobooks "
// ...), so every consumer must apply the same normalization and vocabulary;
// scanner, libraryingest, and metadata previously kept private copies of these
// predicates that drifted apart.
//
// Predicates are strict: IsMovie does not include mixed libraries. Call sites
// that treat mixed libraries as movie-bearing must say so explicitly
// (IsMovie(t) || IsMixed(t)).
package librarykind
import "strings"
// Kinds reports every classification of one media_folders.type value.
// Movie/TV/Mixed feed the video scan pipeline; Audiobook/Ebook/Podcast/Manga
// are single-purpose library types with dedicated scan pipelines and are
// never part of a mixed library.
type Kinds struct {
Movie bool
TV bool
Mixed bool
Audiobook bool
Ebook bool
Podcast bool
Manga bool
}
// Of resolves every predicate for one library type in a single call.
func Of(libraryType string) Kinds {
return Kinds{
Movie: IsMovie(libraryType),
TV: IsTV(libraryType),
Mixed: IsMixed(libraryType),
Audiobook: IsAudiobook(libraryType),
Ebook: IsEbook(libraryType),
Podcast: IsPodcast(libraryType),
Manga: IsManga(libraryType),
}
}
func normalize(libraryType string) string {
return strings.ToLower(strings.TrimSpace(libraryType))
}
// IsMovie reports whether the library type is a dedicated movie library.
func IsMovie(libraryType string) bool {
switch normalize(libraryType) {
case "movie", "movies":
return true
default:
return false
}
}
// IsTV reports whether the library type is a dedicated TV/series library.
func IsTV(libraryType string) bool {
switch normalize(libraryType) {
case "series", "tv", "show", "tvshows":
return true
default:
return false
}
}
// IsMixed reports whether the library type mixes movies and TV.
func IsMixed(libraryType string) bool {
return normalize(libraryType) == "mixed"
}
// IsAudiobook reports whether the library type is an audiobook library.
func IsAudiobook(libraryType string) bool {
switch normalize(libraryType) {
case "audiobook", "audiobooks":
return true
default:
return false
}
}
// IsEbook reports whether the library type is an ebook library.
func IsEbook(libraryType string) bool {
switch normalize(libraryType) {
case "ebook", "ebooks":
return true
default:
return false
}
}
// IsPodcast reports whether the library type is a podcast library.
func IsPodcast(libraryType string) bool {
switch normalize(libraryType) {
case "podcast", "podcasts":
return true
default:
return false
}
}
// IsManga reports whether the library type is a manga library.
func IsManga(libraryType string) bool {
return normalize(libraryType) == "manga"
}