162 lines
5.4 KiB
Go
162 lines
5.4 KiB
Go
package web
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"net/url"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"gitea.kazu634.com/kazu634/tver/internal/store"
|
|
"gitea.kazu634.com/kazu634/tver/internal/tver"
|
|
)
|
|
|
|
const seriesURLInputError = "番組ページ(https://tver.jp/series/…)の URL を入力してください"
|
|
|
|
var seriesURLPathPattern = regexp.MustCompile(`^/series/([A-Za-z0-9]+)/?$`)
|
|
var seriesIDPattern = regexp.MustCompile(`^[A-Za-z0-9]+$`)
|
|
|
|
type subscriptionURLResultView struct {
|
|
SeriesID, Title, ErrorText string
|
|
EpisodeCount int
|
|
Subscribed bool
|
|
}
|
|
|
|
type subscriptionURLRegisteredView struct {
|
|
Subscriptions subscriptionsView
|
|
}
|
|
|
|
func parseSeriesURL(raw string) (string, error) {
|
|
parsed, err := url.Parse(strings.TrimSpace(raw))
|
|
if err != nil || parsed.Scheme != "https" || parsed.User != nil || (!strings.EqualFold(parsed.Host, "tver.jp") && !strings.EqualFold(parsed.Host, "www.tver.jp")) {
|
|
return "", errors.New(seriesURLInputError)
|
|
}
|
|
match := seriesURLPathPattern.FindStringSubmatch(parsed.Path)
|
|
if len(match) != 2 {
|
|
return "", errors.New(seriesURLInputError)
|
|
}
|
|
return match[1], nil
|
|
}
|
|
|
|
func (s *server) confirmSubscriptionURL(w http.ResponseWriter, r *http.Request) {
|
|
if r.Header.Get("HX-Request") != "true" {
|
|
http.Redirect(w, r, "/subscriptions", http.StatusSeeOther)
|
|
return
|
|
}
|
|
if err := r.ParseForm(); err != nil {
|
|
http.Error(w, "入力が不正です", http.StatusBadRequest)
|
|
return
|
|
}
|
|
seriesID, err := parseSeriesURL(r.Form.Get("series_url"))
|
|
if err != nil {
|
|
s.render(w, "subscription_url_result", subscriptionURLResultView{ErrorText: seriesURLInputError})
|
|
return
|
|
}
|
|
title, episodes, ok := s.fetchSubscriptionSeries(w, r, seriesID)
|
|
if !ok {
|
|
return
|
|
}
|
|
subscribed, err := s.isSubscribed(r.Context(), seriesID)
|
|
if err != nil {
|
|
s.logger.Printf("番組の登録状態取得に失敗: %v", err)
|
|
http.Error(w, "登録状態を取得できませんでした", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
s.render(w, "subscription_url_result", subscriptionURLResultView{
|
|
SeriesID: seriesID, Title: title, EpisodeCount: len(episodes), Subscribed: subscribed,
|
|
})
|
|
}
|
|
|
|
func (s *server) registerSubscriptionURL(w http.ResponseWriter, r *http.Request) {
|
|
if err := r.ParseForm(); err != nil {
|
|
http.Error(w, "入力が不正です", http.StatusBadRequest)
|
|
return
|
|
}
|
|
seriesID := r.Form.Get("series_id")
|
|
if !seriesIDPattern.MatchString(seriesID) {
|
|
http.Error(w, "番組 ID が不正です", http.StatusBadRequest)
|
|
return
|
|
}
|
|
choice := r.Form.Get("download_existing")
|
|
if choice != "" && choice != "1" {
|
|
http.Error(w, "公開中の回の選択が不正です", http.StatusBadRequest)
|
|
return
|
|
}
|
|
subscribed, err := s.isSubscribed(r.Context(), seriesID)
|
|
if err != nil {
|
|
s.logger.Printf("番組の登録状態取得に失敗: %v", err)
|
|
http.Error(w, "登録状態を取得できませんでした", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if subscribed {
|
|
s.subscriptionURLRegisteredResponse(w, r)
|
|
return
|
|
}
|
|
title, episodes, ok := s.fetchSubscriptionSeries(w, r, seriesID)
|
|
if !ok {
|
|
return
|
|
}
|
|
items := make([]store.Episode, 0, len(episodes))
|
|
for _, episode := range episodes {
|
|
items = append(items, storeEpisode(episode))
|
|
}
|
|
_, err = s.jobs.RegisterSubscription(r.Context(), seriesID, title, items, choice == "1")
|
|
if err != nil {
|
|
s.logger.Printf("番組URLからの登録に失敗: %v", err)
|
|
s.subscriptionURLFailure(w, r, "番組を登録できませんでした。再試行してください")
|
|
return
|
|
}
|
|
s.subscriptionURLRegisteredResponse(w, r)
|
|
}
|
|
|
|
func (s *server) cancelSubscriptionURL(w http.ResponseWriter, _ *http.Request) {
|
|
s.render(w, "subscription_url_result", subscriptionURLResultView{})
|
|
}
|
|
|
|
func (s *server) fetchSubscriptionSeries(w http.ResponseWriter, r *http.Request, seriesID string) (string, []tver.Episode, bool) {
|
|
title, err := s.fetcher.FetchSeries(r.Context(), seriesID)
|
|
if err != nil {
|
|
if errors.Is(err, tver.ErrSeriesNotFound) {
|
|
s.subscriptionURLFailure(w, r, "番組が見つかりません")
|
|
return "", nil, false
|
|
}
|
|
s.logSubscriptionFetchError(err)
|
|
s.subscriptionURLFailure(w, r, "番組情報を取得できませんでした。再試行してください")
|
|
return "", nil, false
|
|
}
|
|
episodes, err := s.fetcher.FetchSeriesEpisodes(r.Context(), seriesID)
|
|
if err != nil {
|
|
s.logSubscriptionFetchError(err)
|
|
s.subscriptionURLFailure(w, r, "番組情報を取得できませんでした。再試行してください")
|
|
return "", nil, false
|
|
}
|
|
return title, episodes, true
|
|
}
|
|
|
|
func (s *server) logSubscriptionFetchError(err error) {
|
|
s.logger.Printf("番組URLの情報取得に失敗: %s", platformTokenPattern.ReplaceAllString(err.Error(), "${1}[REDACTED]"))
|
|
}
|
|
|
|
func (s *server) subscriptionURLFailure(w http.ResponseWriter, r *http.Request, message string) {
|
|
if r.Header.Get("HX-Request") != "true" {
|
|
http.Redirect(w, r, "/subscriptions", http.StatusSeeOther)
|
|
return
|
|
}
|
|
s.render(w, "subscription_url_result", subscriptionURLResultView{ErrorText: message})
|
|
}
|
|
|
|
func (s *server) subscriptionURLRegisteredResponse(w http.ResponseWriter, r *http.Request) {
|
|
if r.Header.Get("HX-Request") != "true" {
|
|
http.Redirect(w, r, "/subscriptions", http.StatusSeeOther)
|
|
return
|
|
}
|
|
view, err := s.loadSubscriptions(r.Context())
|
|
if err != nil {
|
|
s.logger.Printf("登録番組の取得に失敗: %v", err)
|
|
http.Error(w, "登録番組を取得できませんでした", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
view.OutOfBand = true
|
|
s.render(w, "subscription_url_registered", subscriptionURLRegisteredView{Subscriptions: view})
|
|
}
|