Add drop:tmdb command for adding shows/movies to watched log

This commit is contained in:
Dan Jones 2026-03-06 13:58:55 -06:00
commit 88802020fe
10 changed files with 377 additions and 3 deletions

115
tmdb/drop.go Normal file
View file

@ -0,0 +1,115 @@
package tmdb
import (
"context"
"fmt"
"math/rand/v2"
"time"
"codeberg.org/danjones000/my-log/files"
"codeberg.org/danjones000/my-log/models"
sdk "github.com/cyruzin/golang-tmdb"
)
func Drop(ctx context.Context, url string, now time.Time) (models.Log, error) {
var log models.Log
select {
case <-ctx.Done():
return log, context.Cause(ctx)
default:
}
inf, err := Parse(url)
if err != nil {
return log, err
}
var entry models.Entry
if inf.Type == Movie {
det, err := FetchMovieDetails(inf)
if err != nil {
return log, err
}
entry, err = GetLogEntryForMovie(det)
if err != nil {
return log, err
}
} else if inf.Type == Series {
det, err := FetchEpisodeDetails(inf)
if err != nil {
return log, err
}
entry, err = GetLogEntryForEpisode(det)
if err != nil {
return log, err
}
} else {
return log, fmt.Errorf("unknown type: %s", inf.Type)
}
log.Name = "watched"
log.Entries = []models.Entry{entry}
files.Append(log)
return log, nil
}
func GetLogEntryForEpisode(det TVDetails) (ent models.Entry, err error) {
ent.Title = fmt.Sprintf("%s - %s %dx%02d", det.Episode.Name, det.Series.Name, det.Episode.SeasonNumber, det.Episode.EpisodeNumber)
ent.Date = time.Now().Local()
fields := &models.Metas{}
fields.AppendTo("tmdb", det.Series.ID)
fields.AppendTo("show", det.Series.Name)
fields.AppendTo("episode", det.Episode.Name)
fields.AppendTo("air_date", det.Episode.AirDate)
fields.AppendTo("id", fmt.Sprintf("tag:themoviedborg,%s:Episode/%d/season/%d/episode/%d/%d", det.Episode.AirDate, det.Series.ID, det.Episode.SeasonNumber, det.Episode.EpisodeNumber, rand.Int()))
fields.AppendTo("url", fmt.Sprintf("https://themoviedb.org/tv/%d/season/%d/episode/%d", det.Series.ID, det.Episode.SeasonNumber, det.Episode.EpisodeNumber))
fields.AppendTo("episode_num", det.Episode.EpisodeNumber)
fields.AppendTo("season_num", det.Episode.SeasonNumber)
fields.AppendTo("via", "drop-tmdb")
if det.Episode.Overview != "" {
fields.AppendTo("note", det.Episode.Overview)
}
ent.Fields = *fields
return
}
func GetLogEntryForMovie(det *sdk.MovieDetails) (ent models.Entry, err error) {
ent.Title = fmt.Sprintf("%s (%s)", det.Title, det.ReleaseDate)
ent.Date = time.Now().Local()
fields := &models.Metas{}
fields.AppendTo("tmdb", det.ID)
fields.AppendTo("id", fmt.Sprintf("tag:themoviedborg,%s:Movie/%d/%d", det.ReleaseDate, det.ID, rand.Int()))
fields.AppendTo("url", fmt.Sprintf("https://themoviedb.org/movie/%d", det.ID))
// url https://www.themoviedb.org/movie/776503
fields.AppendTo("release_date", det.ReleaseDate)
cts := det.OriginCountry
if len(cts) == 1 {
fields.AppendTo("country", cts[0])
} else if len(cts) > 1 {
fields.AppendTo("country", cts)
}
gens := make([]string, len(det.Genres))
for idx, g := range det.Genres {
gens[idx] = g.Name
}
if len(gens) == 1 {
fields.AppendTo("genre", gens[0])
} else if len(gens) > 1 {
fields.AppendTo("genre", gens)
}
// fields.AppendTo("mpaa")
if det.Overview != "" {
fields.AppendTo("note", det.Overview)
}
ent.Fields = *fields
return
}

79
tmdb/fetch.go Normal file
View file

@ -0,0 +1,79 @@
package tmdb
import (
"errors"
"fmt"
"math/rand/v2"
"os"
"strconv"
sdk "github.com/cyruzin/golang-tmdb"
)
var ErrMissingKey = errors.New("missing TMDB key")
func FetchMovieDetails(inf Info) (*sdk.MovieDetails, error) {
if inf.Type != Movie {
return nil, fmt.Errorf("%s is not a movie", inf.Type)
}
if inf.ID < 1 {
return nil, fmt.Errorf("%d is not a valid TMDB id", inf.ID)
}
sdk, err := GetClient()
if err != nil {
return nil, err
}
return sdk.GetMovieDetails(inf.ID, map[string]string{
"cache": strconv.Itoa(rand.Int()),
})
}
func FetchEpisodeDetails(inf Info) (TVDetails, error) {
var det TVDetails
if inf.Type != Series {
return det, fmt.Errorf("%s is not an episode", inf.Type)
}
if inf.ID < 1 {
return det, fmt.Errorf("%d is not a valid TMDB id", inf.ID)
}
if inf.Season < 1 {
return det, fmt.Errorf("%d is not a valid season", inf.Season)
}
if inf.Episode < 1 {
return det, fmt.Errorf("%d is not a valid episode", inf.Episode)
}
sdk, err := GetClient()
if err != nil {
return det, err
}
epDet, err := sdk.GetTVEpisodeDetails(inf.ID, inf.Season, inf.Episode, map[string]string{
"cache": strconv.Itoa(rand.Int()),
})
if err != nil {
return det, err
}
showDet, err := sdk.GetTVDetails(inf.ID, map[string]string{
"cache": strconv.Itoa(rand.Int()),
})
if err != nil {
return det, err
}
det.Episode = epDet
det.Series = showDet
return det, nil
}
func GetClient() (*sdk.Client, error) {
key := os.Getenv("TMDB_KEY")
if key == "" {
return nil, ErrMissingKey
}
return sdk.Init(key)
}

52
tmdb/parse.go Normal file
View file

@ -0,0 +1,52 @@
package tmdb
import (
"fmt"
"regexp"
"strconv"
)
var EpisodeRegex = regexp.MustCompile(`https?://(?:www\.)?themoviedb\.org/tv/([0-9]+)(?:-[A-Za-z0-9-]+)?/season/([0-9]+)/episode/([0-9]+)`)
var MovieRegex = regexp.MustCompile(`https?://(?:www\.)?themoviedb\.org/movie/([0-9]+)(?:-[A-Za-z0-9-]+)?/?`)
func Parse(url string) (inf Info, err error) {
if m := EpisodeRegex.FindStringSubmatch(url); len(m) > 0 {
var got int
got, err = strconv.Atoi(m[1])
if err != nil {
return
}
inf.ID = got
got, err = strconv.Atoi(m[2])
if err != nil {
return
}
inf.Season = got
got, err = strconv.Atoi(m[3])
if err != nil {
return
}
inf.Episode = got
inf.Type = Series
return
}
if m := MovieRegex.FindStringSubmatch(url); len(m) > 0 {
var got int
got, err = strconv.Atoi(m[1])
if err != nil {
return
}
inf.ID = got
inf.Type = Movie
return
}
return inf, fmt.Errorf("failed to parse %s", url)
}

24
tmdb/type.go Normal file
View file

@ -0,0 +1,24 @@
package tmdb
import sdk "github.com/cyruzin/golang-tmdb"
//go:generate stringer -type=Type
type Type uint8
const (
Unknown Type = iota
Movie
Series
)
type TVDetails struct {
Series *sdk.TVDetails
Episode *sdk.TVEpisodeDetails
}
type Info struct {
ID int
Type Type
Season int
Episode int
}

26
tmdb/type_string.go Normal file
View file

@ -0,0 +1,26 @@
// Code generated by "stringer -type=Type"; DO NOT EDIT.
package tmdb
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[Unknown-0]
_ = x[Movie-1]
_ = x[Series-2]
}
const _Type_name = "UnknownMovieSeries"
var _Type_index = [...]uint8{0, 7, 12, 18}
func (i Type) String() string {
idx := int(i) - 0
if i < 0 || idx >= len(_Type_index)-1 {
return "Type(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Type_name[_Type_index[idx]:_Type_index[idx+1]]
}