Episodes now have their own ID
This commit is contained in:
parent
5551dd176e
commit
3a15829831
@ -144,7 +144,7 @@ func (amv *AMV) MainAnime() *Anime {
|
||||
// ExtraAnime returns main anime for the AMV, if available.
|
||||
func (amv *AMV) ExtraAnime() []*Anime {
|
||||
objects := DB.GetMany("Anime", amv.ExtraAnimeIDs)
|
||||
animes := []*Anime{}
|
||||
animes := make([]*Anime, 0, len(amv.ExtraAnimeIDs))
|
||||
|
||||
for _, obj := range objects {
|
||||
if obj == nil {
|
||||
|
56
arn/Anime.go
56
arn/Anime.go
@ -83,6 +83,7 @@ type Anime struct {
|
||||
Rating *AnimeRating `json:"rating"`
|
||||
Popularity *AnimePopularity `json:"popularity"`
|
||||
Trailers []*ExternalMedia `json:"trailers" editable:"true"`
|
||||
EpisodeIDs []string `json:"episodes" editable:"true"`
|
||||
|
||||
// Mixins
|
||||
hasMappings
|
||||
@ -356,15 +357,20 @@ func (anime *Anime) EndDateTime() time.Time {
|
||||
return t
|
||||
}
|
||||
|
||||
// Episodes returns the anime episodes wrapper.
|
||||
func (anime *Anime) Episodes() *AnimeEpisodes {
|
||||
record, err := DB.Get("AnimeEpisodes", anime.ID)
|
||||
// Episodes returns the anime episodes.
|
||||
func (anime *Anime) Episodes() EpisodeList {
|
||||
objects := DB.GetMany("Episode", anime.EpisodeIDs)
|
||||
episodes := make([]*Episode, 0, len(anime.EpisodeIDs))
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
for _, obj := range objects {
|
||||
if obj == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
return record.(*AnimeEpisodes)
|
||||
episodes = append(episodes, obj.(*Episode))
|
||||
}
|
||||
|
||||
return episodes
|
||||
}
|
||||
|
||||
// UsersWatchingOrPlanned returns a list of users who are watching the anime right now.
|
||||
@ -387,13 +393,6 @@ func (anime *Anime) RefreshEpisodes() error {
|
||||
// Fetch episodes
|
||||
episodes := anime.Episodes()
|
||||
|
||||
if episodes == nil {
|
||||
episodes = &AnimeEpisodes{
|
||||
AnimeID: anime.ID,
|
||||
Items: []*AnimeEpisode{},
|
||||
}
|
||||
}
|
||||
|
||||
// Save number of available episodes for comparison later
|
||||
oldAvailableCount := episodes.AvailableCount()
|
||||
|
||||
@ -441,7 +440,7 @@ func (anime *Anime) RefreshEpisodes() error {
|
||||
// Number remaining episodes
|
||||
startNumber := 0
|
||||
|
||||
for _, episode := range episodes.Items {
|
||||
for _, episode := range episodes {
|
||||
if episode.Number != -1 {
|
||||
startNumber = episode.Number
|
||||
continue
|
||||
@ -456,7 +455,7 @@ func (anime *Anime) RefreshEpisodes() error {
|
||||
lastAiringDate := ""
|
||||
timeDifference := oneWeek
|
||||
|
||||
for _, episode := range episodes.Items {
|
||||
for _, episode := range episodes {
|
||||
if validate.DateTime(episode.AiringDate.Start) {
|
||||
if lastAiringDate != "" {
|
||||
a, _ := time.Parse(time.RFC3339, lastAiringDate)
|
||||
@ -485,12 +484,20 @@ func (anime *Anime) RefreshEpisodes() error {
|
||||
lastAiringDate = episode.AiringDate.Start
|
||||
}
|
||||
|
||||
episodes.Save()
|
||||
// Save new episode ID list
|
||||
episodeIDs := make([]string, len(episodes))
|
||||
|
||||
for index := range episodes {
|
||||
episodeIDs[index] = episodes[index].ID
|
||||
}
|
||||
|
||||
anime.EpisodeIDs = episodeIDs
|
||||
anime.Save()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShoboiEpisodes returns a slice of episode info from cal.syoboi.jp.
|
||||
func (anime *Anime) ShoboiEpisodes() ([]*AnimeEpisode, error) {
|
||||
func (anime *Anime) ShoboiEpisodes() (EpisodeList, error) {
|
||||
shoboiID := anime.GetMapping("shoboi/anime")
|
||||
|
||||
if shoboiID == "" {
|
||||
@ -503,7 +510,7 @@ func (anime *Anime) ShoboiEpisodes() ([]*AnimeEpisode, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
arnEpisodes := []*AnimeEpisode{}
|
||||
arnEpisodes := []*Episode{}
|
||||
shoboiEpisodes := shoboiAnime.Episodes()
|
||||
|
||||
for _, shoboiEpisode := range shoboiEpisodes {
|
||||
@ -529,7 +536,7 @@ func (anime *Anime) ShoboiEpisodes() ([]*AnimeEpisode, error) {
|
||||
}
|
||||
|
||||
// TwistEpisodes returns a slice of episode info from twist.moe.
|
||||
func (anime *Anime) TwistEpisodes() ([]*AnimeEpisode, error) {
|
||||
func (anime *Anime) TwistEpisodes() (EpisodeList, error) {
|
||||
idList, err := GetIDList("animetwist index")
|
||||
|
||||
if err != nil {
|
||||
@ -566,7 +573,7 @@ func (anime *Anime) TwistEpisodes() ([]*AnimeEpisode, error) {
|
||||
return episodes[a].Number < episodes[b].Number
|
||||
})
|
||||
|
||||
arnEpisodes := []*AnimeEpisode{}
|
||||
arnEpisodes := []*Episode{}
|
||||
|
||||
for _, episode := range episodes {
|
||||
arnEpisode := NewAnimeEpisode()
|
||||
@ -584,10 +591,9 @@ func (anime *Anime) TwistEpisodes() ([]*AnimeEpisode, error) {
|
||||
// UpcomingEpisodes ...
|
||||
func (anime *Anime) UpcomingEpisodes() []*UpcomingEpisode {
|
||||
var upcomingEpisodes []*UpcomingEpisode
|
||||
|
||||
now := time.Now().UTC().Format(time.RFC3339)
|
||||
|
||||
for _, episode := range anime.Episodes().Items {
|
||||
for _, episode := range anime.Episodes() {
|
||||
if episode.AiringDate.Start > now && validate.DateTime(episode.AiringDate.Start) {
|
||||
upcomingEpisodes = append(upcomingEpisodes, &UpcomingEpisode{
|
||||
Anime: anime,
|
||||
@ -603,7 +609,7 @@ func (anime *Anime) UpcomingEpisodes() []*UpcomingEpisode {
|
||||
func (anime *Anime) UpcomingEpisode() *UpcomingEpisode {
|
||||
now := time.Now().UTC().Format(time.RFC3339)
|
||||
|
||||
for _, episode := range anime.Episodes().Items {
|
||||
for _, episode := range anime.Episodes() {
|
||||
if episode.AiringDate.Start > now && validate.DateTime(episode.AiringDate.Start) {
|
||||
return &UpcomingEpisode{
|
||||
Anime: anime,
|
||||
@ -719,8 +725,8 @@ func (anime *Anime) CalculatedStatus() string {
|
||||
}
|
||||
|
||||
// EpisodeByNumber returns the episode with the given number.
|
||||
func (anime *Anime) EpisodeByNumber(number int) *AnimeEpisode {
|
||||
for _, episode := range anime.Episodes().Items {
|
||||
func (anime *Anime) EpisodeByNumber(number int) *Episode {
|
||||
for _, episode := range anime.Episodes() {
|
||||
if number == episode.Number {
|
||||
return episode
|
||||
}
|
||||
|
@ -1,78 +0,0 @@
|
||||
package arn
|
||||
|
||||
import "github.com/animenotifier/notify.moe/arn/validate"
|
||||
|
||||
// AnimeEpisode ...
|
||||
type AnimeEpisode struct {
|
||||
Number int `json:"number" editable:"true"`
|
||||
Title EpisodeTitle `json:"title" editable:"true"`
|
||||
AiringDate AiringDate `json:"airingDate" editable:"true"`
|
||||
Links map[string]string `json:"links"`
|
||||
}
|
||||
|
||||
// EpisodeTitle ...
|
||||
type EpisodeTitle struct {
|
||||
Romaji string `json:"romaji" editable:"true"`
|
||||
English string `json:"english" editable:"true"`
|
||||
Japanese string `json:"japanese" editable:"true"`
|
||||
}
|
||||
|
||||
// Available tells you whether the episode is available (triggered when it has a link).
|
||||
func (a *AnimeEpisode) Available() bool {
|
||||
return len(a.Links) > 0
|
||||
}
|
||||
|
||||
// AvailableOn tells you whether the episode is available on a given service.
|
||||
func (a *AnimeEpisode) AvailableOn(serviceName string) bool {
|
||||
return a.Links[serviceName] != ""
|
||||
}
|
||||
|
||||
// Merge combines the data of both episodes to one.
|
||||
func (a *AnimeEpisode) Merge(b *AnimeEpisode) {
|
||||
if b == nil {
|
||||
return
|
||||
}
|
||||
|
||||
a.Number = b.Number
|
||||
|
||||
// Titles
|
||||
if b.Title.Romaji != "" {
|
||||
a.Title.Romaji = b.Title.Romaji
|
||||
}
|
||||
|
||||
if b.Title.English != "" {
|
||||
a.Title.English = b.Title.English
|
||||
}
|
||||
|
||||
if b.Title.Japanese != "" {
|
||||
a.Title.Japanese = b.Title.Japanese
|
||||
}
|
||||
|
||||
// Airing date
|
||||
if validate.DateTime(b.AiringDate.Start) {
|
||||
a.AiringDate.Start = b.AiringDate.Start
|
||||
}
|
||||
|
||||
if validate.DateTime(b.AiringDate.End) {
|
||||
a.AiringDate.End = b.AiringDate.End
|
||||
}
|
||||
|
||||
// Links
|
||||
if a.Links == nil {
|
||||
a.Links = map[string]string{}
|
||||
}
|
||||
|
||||
for name, link := range b.Links {
|
||||
a.Links[name] = link
|
||||
}
|
||||
}
|
||||
|
||||
// NewAnimeEpisode creates an empty anime episode.
|
||||
func NewAnimeEpisode() *AnimeEpisode {
|
||||
return &AnimeEpisode{
|
||||
Number: -1,
|
||||
Title: EpisodeTitle{},
|
||||
AiringDate: AiringDate{},
|
||||
Links: map[string]string{},
|
||||
}
|
||||
}
|
@ -1,157 +0,0 @@
|
||||
package arn
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/aerogo/nano"
|
||||
)
|
||||
|
||||
// AnimeEpisodes is a list of episodes for an anime.
|
||||
type AnimeEpisodes struct {
|
||||
AnimeID AnimeID `json:"animeId" mainID:"true"`
|
||||
Items []*AnimeEpisode `json:"items" editable:"true"`
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// Link returns the link for that object.
|
||||
func (episodes *AnimeEpisodes) Link() string {
|
||||
return "/anime/" + episodes.AnimeID + "/episodes"
|
||||
}
|
||||
|
||||
// Sort sorts the episodes by episode number.
|
||||
func (episodes *AnimeEpisodes) Sort() {
|
||||
episodes.Lock()
|
||||
defer episodes.Unlock()
|
||||
|
||||
sort.Slice(episodes.Items, func(i, j int) bool {
|
||||
return episodes.Items[i].Number < episodes.Items[j].Number
|
||||
})
|
||||
}
|
||||
|
||||
// Find finds the given episode number.
|
||||
func (episodes *AnimeEpisodes) Find(episodeNumber int) (*AnimeEpisode, int) {
|
||||
episodes.Lock()
|
||||
defer episodes.Unlock()
|
||||
|
||||
for index, episode := range episodes.Items {
|
||||
if episode.Number == episodeNumber {
|
||||
return episode, index
|
||||
}
|
||||
}
|
||||
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
// Merge combines the data of both episode slices to one.
|
||||
func (episodes *AnimeEpisodes) Merge(b []*AnimeEpisode) {
|
||||
if b == nil {
|
||||
return
|
||||
}
|
||||
|
||||
episodes.Lock()
|
||||
defer episodes.Unlock()
|
||||
|
||||
for index, episode := range b {
|
||||
if index >= len(episodes.Items) {
|
||||
episodes.Items = append(episodes.Items, episode)
|
||||
} else {
|
||||
episodes.Items[index].Merge(episode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Last returns the last n items.
|
||||
func (episodes *AnimeEpisodes) Last(count int) []*AnimeEpisode {
|
||||
return episodes.Items[len(episodes.Items)-count:]
|
||||
}
|
||||
|
||||
// AvailableCount counts the number of available episodes.
|
||||
func (episodes *AnimeEpisodes) AvailableCount() int {
|
||||
episodes.Lock()
|
||||
defer episodes.Unlock()
|
||||
|
||||
available := 0
|
||||
|
||||
for _, episode := range episodes.Items {
|
||||
if len(episode.Links) > 0 {
|
||||
available++
|
||||
}
|
||||
}
|
||||
|
||||
return available
|
||||
}
|
||||
|
||||
// Anime returns the anime the episodes refer to.
|
||||
func (episodes *AnimeEpisodes) Anime() *Anime {
|
||||
anime, _ := GetAnime(episodes.AnimeID)
|
||||
return anime
|
||||
}
|
||||
|
||||
// String implements the default string serialization.
|
||||
func (episodes *AnimeEpisodes) String() string {
|
||||
return episodes.Anime().String()
|
||||
}
|
||||
|
||||
// GetID returns the anime ID.
|
||||
func (episodes *AnimeEpisodes) GetID() string {
|
||||
return episodes.AnimeID
|
||||
}
|
||||
|
||||
// TypeName returns the type name.
|
||||
func (episodes *AnimeEpisodes) TypeName() string {
|
||||
return "AnimeEpisodes"
|
||||
}
|
||||
|
||||
// Self returns the object itself.
|
||||
func (episodes *AnimeEpisodes) Self() Loggable {
|
||||
return episodes
|
||||
}
|
||||
|
||||
// ListString returns a text representation of the anime episodes.
|
||||
func (episodes *AnimeEpisodes) ListString() string {
|
||||
episodes.Lock()
|
||||
defer episodes.Unlock()
|
||||
|
||||
b := strings.Builder{}
|
||||
|
||||
for _, episode := range episodes.Items {
|
||||
b.WriteString(strconv.Itoa(episode.Number))
|
||||
b.WriteString(" | ")
|
||||
b.WriteString(episode.Title.Japanese)
|
||||
b.WriteString(" | ")
|
||||
b.WriteString(episode.AiringDate.StartDateHuman())
|
||||
b.WriteByte('\n')
|
||||
}
|
||||
|
||||
return strings.TrimRight(b.String(), "\n")
|
||||
}
|
||||
|
||||
// StreamAnimeEpisodes returns a stream of all anime episodes.
|
||||
func StreamAnimeEpisodes() <-chan *AnimeEpisodes {
|
||||
channel := make(chan *AnimeEpisodes, nano.ChannelBufferSize)
|
||||
|
||||
go func() {
|
||||
for obj := range DB.All("AnimeEpisodes") {
|
||||
channel <- obj.(*AnimeEpisodes)
|
||||
}
|
||||
|
||||
close(channel)
|
||||
}()
|
||||
|
||||
return channel
|
||||
}
|
||||
|
||||
// GetAnimeEpisodes ...
|
||||
func GetAnimeEpisodes(id string) (*AnimeEpisodes, error) {
|
||||
obj, err := DB.Get("AnimeEpisodes", id)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return obj.(*AnimeEpisodes), nil
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
package arn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/aerogo/aero"
|
||||
"github.com/aerogo/api"
|
||||
)
|
||||
|
||||
// Force interface implementations
|
||||
var (
|
||||
_ fmt.Stringer = (*AnimeEpisodes)(nil)
|
||||
_ api.Editable = (*AnimeEpisodes)(nil)
|
||||
_ api.ArrayEventListener = (*AnimeEpisodes)(nil)
|
||||
)
|
||||
|
||||
// Authorize returns an error if the given API POST request is not authorized.
|
||||
func (episodes *AnimeEpisodes) Authorize(ctx aero.Context, action string) error {
|
||||
user := GetUserFromContext(ctx)
|
||||
|
||||
if user == nil || (user.Role != "editor" && user.Role != "admin") {
|
||||
return errors.New("Not logged in or not authorized to edit")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Edit creates an edit log entry.
|
||||
func (episodes *AnimeEpisodes) Edit(ctx aero.Context, key string, value reflect.Value, newValue reflect.Value) (consumed bool, err error) {
|
||||
return edit(episodes, ctx, key, value, newValue)
|
||||
}
|
||||
|
||||
// OnAppend saves a log entry.
|
||||
func (episodes *AnimeEpisodes) OnAppend(ctx aero.Context, key string, index int, obj interface{}) {
|
||||
onAppend(episodes, ctx, key, index, obj)
|
||||
}
|
||||
|
||||
// OnRemove saves a log entry.
|
||||
func (episodes *AnimeEpisodes) OnRemove(ctx aero.Context, key string, index int, obj interface{}) {
|
||||
onRemove(episodes, ctx, key, index, obj)
|
||||
}
|
||||
|
||||
// Save saves the episodes in the database.
|
||||
func (episodes *AnimeEpisodes) Save() {
|
||||
DB.Set("AnimeEpisodes", episodes.AnimeID, episodes)
|
||||
}
|
||||
|
||||
// Delete deletes the episode list from the database.
|
||||
func (episodes *AnimeEpisodes) Delete() error {
|
||||
DB.Delete("AnimeEpisodes", episodes.AnimeID)
|
||||
return nil
|
||||
}
|
@ -20,7 +20,6 @@ var DB = Node.Namespace("arn").RegisterTypes(
|
||||
(*Analytics)(nil),
|
||||
(*Anime)(nil),
|
||||
(*AnimeCharacters)(nil),
|
||||
(*AnimeEpisodes)(nil),
|
||||
(*AnimeRelations)(nil),
|
||||
(*AnimeList)(nil),
|
||||
(*Character)(nil),
|
||||
@ -29,6 +28,7 @@ var DB = Node.Namespace("arn").RegisterTypes(
|
||||
(*DraftIndex)(nil),
|
||||
(*EditLogEntry)(nil),
|
||||
(*EmailToUser)(nil),
|
||||
(*Episode)(nil),
|
||||
(*FacebookToUser)(nil),
|
||||
(*GoogleToUser)(nil),
|
||||
(*Group)(nil),
|
||||
|
164
arn/Episode.go
Normal file
164
arn/Episode.go
Normal file
@ -0,0 +1,164 @@
|
||||
package arn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aerogo/nano"
|
||||
"github.com/animenotifier/notify.moe/arn/validate"
|
||||
)
|
||||
|
||||
// Episode represents a single episode for an anime.
|
||||
type Episode struct {
|
||||
ID string `json:"id"`
|
||||
AnimeID AnimeID `json:"animeId"`
|
||||
Number int `json:"number" editable:"true"`
|
||||
Title EpisodeTitle `json:"title" editable:"true"`
|
||||
AiringDate AiringDate `json:"airingDate" editable:"true"`
|
||||
Links map[string]string `json:"links"`
|
||||
}
|
||||
|
||||
// EpisodeTitle contains the title information for an anime episode.
|
||||
type EpisodeTitle struct {
|
||||
Romaji string `json:"romaji" editable:"true"`
|
||||
English string `json:"english" editable:"true"`
|
||||
Japanese string `json:"japanese" editable:"true"`
|
||||
}
|
||||
|
||||
// NewAnimeEpisode creates a new anime episode.
|
||||
func NewAnimeEpisode() *Episode {
|
||||
return &Episode{
|
||||
ID: GenerateID("Episode"),
|
||||
Number: -1,
|
||||
}
|
||||
}
|
||||
|
||||
// Anime returns the anime the episode refers to.
|
||||
func (episode *Episode) Anime() *Anime {
|
||||
anime, _ := GetAnime(episode.AnimeID)
|
||||
return anime
|
||||
}
|
||||
|
||||
// GetID returns the episode ID.
|
||||
func (episode *Episode) GetID() string {
|
||||
return episode.ID
|
||||
}
|
||||
|
||||
// TypeName returns the type name.
|
||||
func (episode *Episode) TypeName() string {
|
||||
return "Episode"
|
||||
}
|
||||
|
||||
// Self returns the object itself.
|
||||
func (episode *Episode) Self() Loggable {
|
||||
return episode
|
||||
}
|
||||
|
||||
// Link returns the permalink to the episode.
|
||||
func (episode *Episode) Link() string {
|
||||
return "/episode/" + episode.ID
|
||||
}
|
||||
|
||||
// Available tells you whether the episode is available (triggered when it has a link).
|
||||
func (episode *Episode) Available() bool {
|
||||
return len(episode.Links) > 0
|
||||
}
|
||||
|
||||
// AvailableOn tells you whether the episode is available on a given service.
|
||||
func (episode *Episode) AvailableOn(serviceName string) bool {
|
||||
return episode.Links[serviceName] != ""
|
||||
}
|
||||
|
||||
// Previous returns the previous episode, if available.
|
||||
func (episode *Episode) Previous() *Episode {
|
||||
episodes := episode.Anime().Episodes()
|
||||
_, index := episodes.Find(episode.Number)
|
||||
|
||||
if index > 0 {
|
||||
return episodes[index-1]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Next returns the next episode, if available.
|
||||
func (episode *Episode) Next() *Episode {
|
||||
episodes := episode.Anime().Episodes()
|
||||
_, index := episodes.Find(episode.Number)
|
||||
|
||||
if index != -1 && index+1 < len(episodes) {
|
||||
return episodes[index+1]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Merge combines the data of both episodes to one.
|
||||
func (episode *Episode) Merge(b *Episode) {
|
||||
if b == nil {
|
||||
return
|
||||
}
|
||||
|
||||
episode.Number = b.Number
|
||||
|
||||
// Titles
|
||||
if b.Title.Romaji != "" {
|
||||
episode.Title.Romaji = b.Title.Romaji
|
||||
}
|
||||
|
||||
if b.Title.English != "" {
|
||||
episode.Title.English = b.Title.English
|
||||
}
|
||||
|
||||
if b.Title.Japanese != "" {
|
||||
episode.Title.Japanese = b.Title.Japanese
|
||||
}
|
||||
|
||||
// Airing date
|
||||
if validate.DateTime(b.AiringDate.Start) {
|
||||
episode.AiringDate.Start = b.AiringDate.Start
|
||||
}
|
||||
|
||||
if validate.DateTime(b.AiringDate.End) {
|
||||
episode.AiringDate.End = b.AiringDate.End
|
||||
}
|
||||
|
||||
// Links
|
||||
if episode.Links == nil {
|
||||
episode.Links = map[string]string{}
|
||||
}
|
||||
|
||||
for name, link := range b.Links {
|
||||
episode.Links[name] = link
|
||||
}
|
||||
}
|
||||
|
||||
// String implements the default string serialization.
|
||||
func (episode *Episode) String() string {
|
||||
return fmt.Sprintf("%s ep. %d", episode.Anime().TitleByUser(nil), episode.Number)
|
||||
}
|
||||
|
||||
// StreamEpisodes returns a stream of all episodes.
|
||||
func StreamEpisodes() <-chan *Episode {
|
||||
channel := make(chan *Episode, nano.ChannelBufferSize)
|
||||
|
||||
go func() {
|
||||
for obj := range DB.All("Episode") {
|
||||
channel <- obj.(*Episode)
|
||||
}
|
||||
|
||||
close(channel)
|
||||
}()
|
||||
|
||||
return channel
|
||||
}
|
||||
|
||||
// GetEpisode returns the episode with the given ID.
|
||||
func GetEpisode(id string) (*Episode, error) {
|
||||
obj, err := DB.Get("Episode", id)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return obj.(*Episode), nil
|
||||
}
|
54
arn/EpisodeAPI.go
Normal file
54
arn/EpisodeAPI.go
Normal file
@ -0,0 +1,54 @@
|
||||
package arn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/aerogo/aero"
|
||||
"github.com/aerogo/api"
|
||||
)
|
||||
|
||||
// Force interface implementations
|
||||
var (
|
||||
_ fmt.Stringer = (*Episode)(nil)
|
||||
_ api.Editable = (*Episode)(nil)
|
||||
_ api.ArrayEventListener = (*Episode)(nil)
|
||||
)
|
||||
|
||||
// Authorize returns an error if the given API POST request is not authorized.
|
||||
func (episode *Episode) Authorize(ctx aero.Context, action string) error {
|
||||
user := GetUserFromContext(ctx)
|
||||
|
||||
if user == nil || (user.Role != "editor" && user.Role != "admin") {
|
||||
return errors.New("Not logged in or not authorized to edit")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Edit creates an edit log entry.
|
||||
func (episode *Episode) Edit(ctx aero.Context, key string, value reflect.Value, newValue reflect.Value) (consumed bool, err error) {
|
||||
return edit(episode, ctx, key, value, newValue)
|
||||
}
|
||||
|
||||
// OnAppend saves a log entry.
|
||||
func (episode *Episode) OnAppend(ctx aero.Context, key string, index int, obj interface{}) {
|
||||
onAppend(episode, ctx, key, index, obj)
|
||||
}
|
||||
|
||||
// OnRemove saves a log entry.
|
||||
func (episode *Episode) OnRemove(ctx aero.Context, key string, index int, obj interface{}) {
|
||||
onRemove(episode, ctx, key, index, obj)
|
||||
}
|
||||
|
||||
// Save saves the episode in the database.
|
||||
func (episode *Episode) Save() {
|
||||
DB.Set("Episode", episode.ID, episode)
|
||||
}
|
||||
|
||||
// Delete deletes the episode list from the database.
|
||||
func (episode *Episode) Delete() error {
|
||||
DB.Delete("Episode", episode.AnimeID)
|
||||
return nil
|
||||
}
|
68
arn/EpisodeList.go
Normal file
68
arn/EpisodeList.go
Normal file
@ -0,0 +1,68 @@
|
||||
package arn
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// EpisodeList is a list of episodes.
|
||||
type EpisodeList []*Episode
|
||||
|
||||
// Sort sorts the episodes by episode number.
|
||||
func (episodes EpisodeList) Sort() {
|
||||
sort.Slice(episodes, func(i, j int) bool {
|
||||
return episodes[i].Number < episodes[j].Number
|
||||
})
|
||||
}
|
||||
|
||||
// Find finds the given episode number.
|
||||
func (episodes EpisodeList) Find(episodeNumber int) (*Episode, int) {
|
||||
for index, episode := range episodes {
|
||||
if episode.Number == episodeNumber {
|
||||
return episode, index
|
||||
}
|
||||
}
|
||||
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
// Merge combines the data of both episode lists to one.
|
||||
func (episodes EpisodeList) Merge(b EpisodeList) {
|
||||
for index, episode := range b {
|
||||
if index >= len(episodes) {
|
||||
episodes = append(episodes, episode)
|
||||
} else {
|
||||
episodes[index].Merge(episode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HumanReadable returns a text representation of the anime episodes.
|
||||
func (episodes EpisodeList) HumanReadable() string {
|
||||
b := strings.Builder{}
|
||||
|
||||
for _, episode := range episodes {
|
||||
b.WriteString(strconv.Itoa(episode.Number))
|
||||
b.WriteString(" | ")
|
||||
b.WriteString(episode.Title.Japanese)
|
||||
b.WriteString(" | ")
|
||||
b.WriteString(episode.AiringDate.StartDateHuman())
|
||||
b.WriteByte('\n')
|
||||
}
|
||||
|
||||
return strings.TrimRight(b.String(), "\n")
|
||||
}
|
||||
|
||||
// AvailableCount counts the number of available episodes.
|
||||
func (episodes EpisodeList) AvailableCount() int {
|
||||
available := 0
|
||||
|
||||
for _, episode := range episodes {
|
||||
if episode.Available() {
|
||||
available++
|
||||
}
|
||||
}
|
||||
|
||||
return available
|
||||
}
|
@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
// NewAnimeFromKitsuAnime ...
|
||||
func NewAnimeFromKitsuAnime(kitsuAnime *kitsu.Anime) (*Anime, *AnimeCharacters, *AnimeRelations, *AnimeEpisodes) {
|
||||
func NewAnimeFromKitsuAnime(kitsuAnime *kitsu.Anime) (*Anime, *AnimeCharacters, *AnimeRelations) {
|
||||
anime := NewAnime()
|
||||
attr := kitsuAnime.Attributes
|
||||
|
||||
@ -82,16 +82,6 @@ func NewAnimeFromKitsuAnime(kitsuAnime *kitsu.Anime) (*Anime, *AnimeCharacters,
|
||||
}
|
||||
}
|
||||
|
||||
// Episodes
|
||||
episodes, _ := GetAnimeEpisodes(anime.ID)
|
||||
|
||||
if episodes == nil {
|
||||
episodes = &AnimeEpisodes{
|
||||
AnimeID: anime.ID,
|
||||
Items: []*AnimeEpisode{},
|
||||
}
|
||||
}
|
||||
|
||||
// Relations
|
||||
relations, _ := GetAnimeRelations(anime.ID)
|
||||
|
||||
@ -102,7 +92,7 @@ func NewAnimeFromKitsuAnime(kitsuAnime *kitsu.Anime) (*Anime, *AnimeCharacters,
|
||||
}
|
||||
}
|
||||
|
||||
return anime, characters, relations, episodes
|
||||
return anime, characters, relations
|
||||
}
|
||||
|
||||
// StreamKitsuAnime returns a stream of all Kitsu anime.
|
||||
|
@ -3,5 +3,5 @@ package arn
|
||||
// UpcomingEpisode is used in the user schedule.
|
||||
type UpcomingEpisode struct {
|
||||
Anime *Anime
|
||||
Episode *AnimeEpisode
|
||||
Episode *Episode
|
||||
}
|
||||
|
@ -154,7 +154,7 @@ func sync(anime *kitsu.Anime) {
|
||||
// if err != nil || episodes == nil {
|
||||
// episodes := &arn.AnimeEpisodes{
|
||||
// AnimeID: anime.ID,
|
||||
// Items: []*arn.AnimeEpisode{},
|
||||
// Items: []*arn.Episode{},
|
||||
// }
|
||||
|
||||
// arn.DB.Set("AnimeEpisodes", anime.ID, episodes)
|
||||
|
@ -72,7 +72,7 @@ func refreshQueue(queue []*arn.Anime) {
|
||||
func refresh(anime *arn.Anime) {
|
||||
fmt.Println(anime.ID, "|", anime.Title.Canonical, "|", anime.GetMapping("shoboi/anime"))
|
||||
|
||||
episodeCount := len(anime.Episodes().Items)
|
||||
episodeCount := len(anime.Episodes())
|
||||
availableEpisodeCount := anime.Episodes().AvailableCount()
|
||||
|
||||
err := anime.RefreshEpisodes()
|
||||
@ -87,8 +87,8 @@ func refresh(anime *arn.Anime) {
|
||||
faint := color.New(color.Faint).SprintFunc()
|
||||
episodes := anime.Episodes()
|
||||
|
||||
fmt.Println(faint(episodes.ListString()))
|
||||
fmt.Printf("+%d episodes | +%d available (%d total)\n", len(episodes.Items)-episodeCount, episodes.AvailableCount()-availableEpisodeCount, len(episodes.Items))
|
||||
fmt.Println(faint(episodes.HumanReadable()))
|
||||
fmt.Printf("+%d episodes | +%d available (%d total)\n", len(episodes)-episodeCount, episodes.AvailableCount()-availableEpisodeCount, len(episodes))
|
||||
println()
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ func main() {
|
||||
}
|
||||
|
||||
// Ok
|
||||
color.Green("Found %d episodes for anime %s (Kitsu: %s)", len(anime.Episodes().Items), anime.ID, kitsuID)
|
||||
color.Green("Found %d episodes for anime %s (Kitsu: %s)", len(anime.Episodes()), anime.ID, kitsuID)
|
||||
|
||||
// Wait for rate limiter
|
||||
<-rateLimiter.C
|
||||
|
@ -37,10 +37,10 @@ func Get(ctx aero.Context) error {
|
||||
}
|
||||
|
||||
// Episodes
|
||||
episodes := anime.Episodes().Items
|
||||
episodes := anime.Episodes()
|
||||
|
||||
if len(episodes) > maxEpisodes {
|
||||
episodes = anime.Episodes().Last(maxEpisodesLongSeries)
|
||||
episodes = episodes[len(episodes)-maxEpisodesLongSeries:]
|
||||
}
|
||||
|
||||
// Friends watching
|
||||
|
@ -1,11 +1,11 @@
|
||||
component Anime(anime *arn.Anime, listItem *arn.AnimeListItem, tracks []*arn.SoundTrack, amvs []*arn.AMV, amvAppearances []*arn.AMV, episodes []*arn.AnimeEpisode, friends []*arn.User, listItems map[*arn.User]*arn.AnimeListItem, episodeToFriends map[int][]*arn.User, user *arn.User)
|
||||
component Anime(anime *arn.Anime, listItem *arn.AnimeListItem, tracks []*arn.SoundTrack, amvs []*arn.AMV, amvAppearances []*arn.AMV, episodes []*arn.Episode, friends []*arn.User, listItems map[*arn.User]*arn.AnimeListItem, episodeToFriends map[int][]*arn.User, user *arn.User)
|
||||
.anime
|
||||
.anime-main-column
|
||||
AnimeMainColumn(anime, listItem, tracks, amvs, amvAppearances, episodes, episodeToFriends, user)
|
||||
.anime-side-column
|
||||
AnimeSideColumn(anime, friends, listItems, user)
|
||||
|
||||
component AnimeMainColumn(anime *arn.Anime, listItem *arn.AnimeListItem, tracks []*arn.SoundTrack, amvs []*arn.AMV, amvAppearances []*arn.AMV, episodes []*arn.AnimeEpisode, episodeToFriends map[int][]*arn.User, user *arn.User)
|
||||
component AnimeMainColumn(anime *arn.Anime, listItem *arn.AnimeListItem, tracks []*arn.SoundTrack, amvs []*arn.AMV, amvAppearances []*arn.AMV, episodes []*arn.Episode, episodeToFriends map[int][]*arn.User, user *arn.User)
|
||||
.anime-header(data-id=anime.ID)
|
||||
a.anime-image-container.mountable(href=anime.ImageLink("original"), target="_blank", rel="noopener", data-mountable-type="header")
|
||||
img.anime-cover-image.lazy(data-src=anime.ImageLink("large"), data-webp="true", data-color=anime.AverageColor(), alt=anime.Title.ByUser(user), importance="high")
|
||||
|
@ -109,11 +109,12 @@ func Episodes(ctx aero.Context) error {
|
||||
return ctx.Error(http.StatusNotFound, "Anime not found", err)
|
||||
}
|
||||
|
||||
animeEpisodes, err := arn.GetAnimeEpisodes(id)
|
||||
// episodes := anime.Episodes()
|
||||
|
||||
if err != nil {
|
||||
return ctx.Error(http.StatusNotFound, "Anime episodes not found", err)
|
||||
}
|
||||
// if err != nil {
|
||||
// return ctx.Error(http.StatusNotFound, "Anime episodes not found", err)
|
||||
// }
|
||||
|
||||
return ctx.HTML(components.EditAnimeTabs(anime) + editform.Render(animeEpisodes, "Edit anime episodes", user))
|
||||
// editform.Render(episodes, "Edit anime episodes", user)
|
||||
return ctx.HTML(components.EditAnimeTabs(anime) + "<p class='no-data mountable'>Temporarily disabled.</p>")
|
||||
}
|
||||
|
@ -38,5 +38,5 @@ func Episodes(ctx aero.Context) error {
|
||||
return ctx.Error(http.StatusNotFound, "Anime not found", err)
|
||||
}
|
||||
|
||||
return ctx.HTML(components.AnimeEpisodes(anime, anime.Episodes().Items, episodeToFriends, user, true))
|
||||
return ctx.HTML(components.AnimeEpisodes(anime, anime.Episodes(), episodeToFriends, user, true))
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
component AnimeEpisodes(anime *arn.Anime, episodes []*arn.AnimeEpisode, episodeToFriends map[int][]*arn.User, user *arn.User, standAlonePage bool)
|
||||
component AnimeEpisodes(anime *arn.Anime, episodes []*arn.Episode, episodeToFriends map[int][]*arn.User, user *arn.User, standAlonePage bool)
|
||||
if standAlonePage
|
||||
h1.mountable
|
||||
a(href=anime.Link())= anime.Title.ByUser(user)
|
||||
@ -6,11 +6,11 @@ component AnimeEpisodes(anime *arn.Anime, episodes []*arn.AnimeEpisode, episodeT
|
||||
if len(episodes) > 0
|
||||
.anime-section.mountable
|
||||
h3.anime-section-name
|
||||
a(href=anime.Episodes().Link()) Episodes
|
||||
a(href=fmt.Sprintf("/anime/%s/episodes", anime.ID)) Episodes
|
||||
|
||||
.episodes
|
||||
each episode in episodes
|
||||
a.episode.mountable(href=anime.Link() + "/episode/" + strconv.Itoa(episode.Number), data-mountable-type="episode", data-available=episode.Available())
|
||||
a.episode.mountable(href=episode.Link(), data-mountable-type="episode", data-available=episode.Available())
|
||||
.episode-number
|
||||
if episode.Number != -1
|
||||
span= episode.Number
|
||||
|
@ -31,7 +31,7 @@ func Kitsu(ctx aero.Context) error {
|
||||
kitsuAnime := kitsuAnimeObj.(*kitsu.Anime)
|
||||
|
||||
// Convert
|
||||
anime, characters, relations, episodes := arn.NewAnimeFromKitsuAnime(kitsuAnime)
|
||||
anime, characters, relations := arn.NewAnimeFromKitsuAnime(kitsuAnime)
|
||||
|
||||
// Add user ID to the anime
|
||||
anime.CreatedBy = user.ID
|
||||
@ -40,7 +40,6 @@ func Kitsu(ctx aero.Context) error {
|
||||
anime.Save()
|
||||
characters.Save()
|
||||
relations.Save()
|
||||
episodes.Save()
|
||||
|
||||
// Log
|
||||
fmt.Println(color.GreenString("✔"), anime.ID, anime.Title.Canonical)
|
||||
|
@ -49,12 +49,11 @@ func Get(ctx aero.Context) error {
|
||||
}
|
||||
|
||||
// Add anime episodes to the days
|
||||
for animeEpisodes := range arn.StreamAnimeEpisodes() {
|
||||
if animeEpisodes.Anime().Status == "finished" {
|
||||
for episode := range arn.StreamEpisodes() {
|
||||
if episode.Anime().Status == "finished" {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, episode := range animeEpisodes.Items {
|
||||
if !validate.DateTime(episode.AiringDate.Start) {
|
||||
continue
|
||||
}
|
||||
@ -73,7 +72,7 @@ func Get(ctx aero.Context) error {
|
||||
dayIndex := int(since / (24 * time.Hour))
|
||||
|
||||
entry := &utils.CalendarEntry{
|
||||
Anime: animeEpisodes.Anime(),
|
||||
Anime: episode.Anime(),
|
||||
Episode: episode,
|
||||
Added: false,
|
||||
}
|
||||
@ -88,7 +87,6 @@ func Get(ctx aero.Context) error {
|
||||
|
||||
days[dayIndex].Entries = append(days[dayIndex].Entries, entry)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < 7; i++ {
|
||||
// nolint:scopelint
|
||||
|
@ -15,39 +15,34 @@ import (
|
||||
func Get(ctx aero.Context) error {
|
||||
user := utils.GetUser(ctx)
|
||||
id := ctx.Get("id")
|
||||
episodeNumber, err := ctx.GetInt("episode-number")
|
||||
|
||||
// Get episode
|
||||
episode, err := arn.GetEpisode(id)
|
||||
|
||||
if err != nil {
|
||||
return ctx.Error(http.StatusBadRequest, "Episode is not a number", err)
|
||||
return ctx.Error(http.StatusNotFound, "Episode not found", err)
|
||||
}
|
||||
|
||||
// Get anime
|
||||
anime, err := arn.GetAnime(id)
|
||||
anime := episode.Anime()
|
||||
|
||||
if err != nil {
|
||||
if anime == nil {
|
||||
return ctx.Error(http.StatusNotFound, "Anime not found", err)
|
||||
}
|
||||
|
||||
// Get anime episodes
|
||||
animeEpisodes, err := arn.GetAnimeEpisodes(id)
|
||||
|
||||
if err != nil {
|
||||
return ctx.Error(http.StatusNotFound, "Anime episodes not found", err)
|
||||
}
|
||||
|
||||
// Does the episode exist?
|
||||
uploaded := false
|
||||
|
||||
if arn.Spaces != nil {
|
||||
stat, err := arn.Spaces.StatObject("arn", fmt.Sprintf("videos/anime/%s/%d.webm", anime.ID, episodeNumber), minio.StatObjectOptions{})
|
||||
stat, err := arn.Spaces.StatObject("arn", fmt.Sprintf("videos/anime/%s/%d.webm", anime.ID, episode.Number), minio.StatObjectOptions{})
|
||||
uploaded = (err == nil) && (stat.Size > 0)
|
||||
}
|
||||
|
||||
episode, episodeIndex := animeEpisodes.Find(episodeNumber)
|
||||
_, episodeIndex := anime.Episodes().Find(episode.Number)
|
||||
|
||||
if episode == nil {
|
||||
return ctx.Error(http.StatusNotFound, "Anime episode not found")
|
||||
}
|
||||
|
||||
return ctx.HTML(components.AnimeEpisode(anime, episode, episodeIndex, uploaded, user))
|
||||
return ctx.HTML(components.Episode(anime, episode, episodeIndex, uploaded, user))
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
component AnimeEpisode(anime *arn.Anime, episode *arn.AnimeEpisode, episodeIndex int, uploaded bool, user *arn.User)
|
||||
component Episode(anime *arn.Anime, episode *arn.Episode, episodeIndex int, uploaded bool, user *arn.User)
|
||||
h1
|
||||
a(href=anime.Link())= anime.Title.ByUser(user)
|
||||
|
||||
.episode-navigation-container
|
||||
if episodeIndex > 0
|
||||
.episode-arrow.episode-arrow-previous
|
||||
a.light-button(href=anime.Link() + "/episode/" + strconv.Itoa(anime.Episodes().Items[episodeIndex - 1].Number), title="Previous episode")
|
||||
a.light-button(href=episode.Previous().Link(), title="Previous episode")
|
||||
RawIcon("chevron-left")
|
||||
|
||||
.episode-video
|
||||
@ -22,9 +22,9 @@ component AnimeEpisode(anime *arn.Anime, episode *arn.AnimeEpisode, episodeIndex
|
||||
//- a(href=anime.Link(), title=anime.Title.ByUser(user))
|
||||
//- img.anime-cover-image.lazy(data-src=anime.ImageLink("large"), data-webp="true", data-color=anime.AverageColor(), alt=anime.Title.ByUser(user))
|
||||
|
||||
if episodeIndex < len(anime.Episodes().Items) - 1
|
||||
if episodeIndex < len(anime.Episodes()) - 1
|
||||
.episode-arrow.episode-arrow-next
|
||||
a.light-button(href=anime.Link() + "/episode/" + strconv.Itoa(anime.Episodes().Items[episodeIndex + 1].Number), title="Next episode")
|
||||
a.light-button(href=episode.Next().Link(), title="Next episode")
|
||||
RawIcon("chevron-right")
|
||||
|
||||
h3.episode-view-number= "Episode " + strconv.Itoa(episode.Number)
|
||||
|
@ -13,23 +13,25 @@ import (
|
||||
func Subtitles(ctx aero.Context) error {
|
||||
id := ctx.Get("id")
|
||||
language := ctx.Get("language")
|
||||
episodeNumber, err := ctx.GetInt("episode-number")
|
||||
|
||||
// Get episode
|
||||
episode, err := arn.GetEpisode(id)
|
||||
|
||||
if err != nil {
|
||||
return ctx.Error(http.StatusBadRequest, "Episode is not a number", err)
|
||||
return ctx.Error(http.StatusNotFound, "Episode not found", err)
|
||||
}
|
||||
|
||||
// Get anime
|
||||
anime, err := arn.GetAnime(id)
|
||||
anime := episode.Anime()
|
||||
|
||||
if err != nil {
|
||||
if anime == nil {
|
||||
return ctx.Error(http.StatusNotFound, "Anime not found", err)
|
||||
}
|
||||
|
||||
ctx.Response().SetHeader("Access-Control-Allow-Origin", "*")
|
||||
ctx.Response().SetHeader("Content-Type", "text/vtt; charset=utf-8")
|
||||
|
||||
obj, err := arn.Spaces.GetObject("arn", fmt.Sprintf("videos/anime/%s/%d.%s.vtt", anime.ID, episodeNumber, language), minio.GetObjectOptions{})
|
||||
obj, err := arn.Spaces.GetObject("arn", fmt.Sprintf("videos/anime/%s/%d.%s.vtt", anime.ID, episode.Number, language), minio.GetObjectOptions{})
|
||||
|
||||
if err != nil {
|
||||
return ctx.Error(http.StatusInternalServerError, err)
|
||||
|
@ -19,8 +19,8 @@ func Register(app *aero.Application) {
|
||||
page.Get(app, "/anime/:id/tracks", anime.Tracks)
|
||||
page.Get(app, "/anime/:id/relations", anime.Relations)
|
||||
page.Get(app, "/anime/:id/comments", anime.Comments)
|
||||
page.Get(app, "/anime/:id/episode/:episode-number", episode.Get)
|
||||
app.Get("/anime/:id/episode/:episode-number/subtitles/:language", episode.Subtitles)
|
||||
page.Get(app, "/episode/:id", episode.Get)
|
||||
app.Get("/episode/:id/subtitles/:language", episode.Subtitles)
|
||||
|
||||
// Anime redirects
|
||||
page.Get(app, "/kitsu/anime/:id", anime.RedirectByMapping("kitsu/anime"))
|
||||
|
@ -52,7 +52,7 @@ func getAnimeStats() []*arn.PieChart {
|
||||
rating[fmt.Sprint(int(anime.Rating.Overall+0.5))]++
|
||||
|
||||
found := false
|
||||
for _, episode := range anime.Episodes().Items {
|
||||
for _, episode := range anime.Episodes() {
|
||||
if episode.Links != nil && episode.Links["twist.moe"] != "" {
|
||||
found = true
|
||||
break
|
||||
|
22
patches/anime-episodes-upgrade/anime-episodes-upgrade.go
Normal file
22
patches/anime-episodes-upgrade/anime-episodes-upgrade.go
Normal file
@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import "github.com/animenotifier/notify.moe/arn"
|
||||
|
||||
func main() {
|
||||
defer arn.Node.Close()
|
||||
|
||||
for episodes := range arn.StreamAnimeEpisodes() {
|
||||
anime := episodes.Anime()
|
||||
anime.EpisodeIDs = nil
|
||||
|
||||
for _, episode := range episodes.Items {
|
||||
episode.ID = arn.GenerateID("Episode")
|
||||
episode.AnimeID = anime.ID
|
||||
episode.Save()
|
||||
|
||||
anime.EpisodeIDs = append(anime.EpisodeIDs, episode.ID)
|
||||
}
|
||||
|
||||
anime.Save()
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ func main() {
|
||||
modified := false
|
||||
|
||||
// Try to find incorrect airing dates
|
||||
for _, episode := range anime.Episodes().Items {
|
||||
for _, episode := range anime.Episodes() {
|
||||
if episode.AiringDate.Start == "" {
|
||||
continue
|
||||
}
|
||||
|
@ -12,6 +12,6 @@ type CalendarDay struct {
|
||||
// CalendarEntry is a calendar entry.
|
||||
type CalendarEntry struct {
|
||||
Anime *arn.Anime
|
||||
Episode *arn.AnimeEpisode
|
||||
Episode *arn.Episode
|
||||
Added bool
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user