待ち行列への登録と状態表示を追加(新着からの登録、/queue、DATABASE_URL)

This commit is contained in:
2026-10-03 19:07:53 +09:00
parent a71b915c81
commit 4a02526e76
20 changed files with 1146 additions and 14 deletions
+52
View File
@@ -9,6 +9,7 @@ import (
"regexp"
"time"
"gitea.kazu634.com/kazu634/tver/internal/store"
"gitea.kazu634.com/kazu634/tver/internal/tver"
)
@@ -34,6 +35,10 @@ type newerOptions struct {
}
type episodeView struct {
ID string
Genre string
Status string
StatusText string
URL string
ThumbnailURL string
SeriesTitle string
@@ -50,6 +55,7 @@ type newerView struct {
HidePreviews bool
Episodes []episodeView
Failed bool
ErrorText string
}
func parseNewerOptions(r *http.Request) (newerOptions, error) {
@@ -123,14 +129,34 @@ func (s *server) newerView(ctx context.Context, options newerOptions) newerView
if err != nil {
s.logger.Printf("TVer の新着取得に失敗(genre=%s): %s", options.genre, platformTokenPattern.ReplaceAllString(err.Error(), "${1}[REDACTED]"))
view.Failed = true
view.ErrorText = "新着を取得できませんでした"
return view
}
visible := make([]tver.Episode, 0, len(episodes))
for _, episode := range episodes {
if options.hidePreviews && episode.Duration <= previewMaxSeconds {
continue
}
visible = append(visible, episode)
}
ids := make([]string, 0, len(visible))
for _, episode := range visible {
ids = append(ids, episode.ID)
}
statuses, err := s.jobs.EpisodeStatuses(ctx, ids)
if err != nil {
s.logger.Printf("新着のジョブ状態取得に失敗: %v", err)
view.Failed = true
view.ErrorText = "状態を取得できませんでした"
return view
}
for _, episode := range visible {
endAt := time.Unix(episode.EndAt, 0).In(s.zone)
view.Episodes = append(view.Episodes, episodeView{
ID: episode.ID,
Genre: options.genre,
Status: statuses[episode.ID],
StatusText: statusText(statuses[episode.ID]),
URL: "https://tver.jp/episodes/" + url.PathEscape(episode.ID),
ThumbnailURL: "https://statics.tver.jp" + episode.ThumbnailPath,
SeriesTitle: episode.SeriesTitle,
@@ -145,6 +171,32 @@ func (s *server) newerView(ctx context.Context, options newerOptions) newerView
return view
}
func (s *server) cachedEpisode(genre, id string) (tver.Episode, bool) {
s.cacheMu.Lock()
defer s.cacheMu.Unlock()
entry, ok := s.cache[genre]
if !ok || !s.now().Before(entry.expiresAt) {
return tver.Episode{}, false
}
for _, ep := range entry.episodes {
if ep.ID == id {
return ep, true
}
}
return tver.Episode{}, false
}
func storeEpisode(ep tver.Episode) store.Episode {
var endAt *time.Time
if ep.EndAt > 0 {
end := time.Unix(ep.EndAt, 0)
endAt = &end
}
return store.Episode{ID: ep.ID, SeriesID: ep.SeriesID, SeriesTitle: ep.SeriesTitle,
Title: ep.Title, BroadcasterName: ep.BroadcasterName, DurationSeconds: int(ep.Duration),
EndAt: endAt, ThumbnailPath: ep.ThumbnailPath}
}
func durationMinutes(seconds int64) int64 {
minutes := seconds / 60
if seconds%60 != 0 {