244 lines
5.1 KiB
Go
244 lines
5.1 KiB
Go
package arn
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"sort"
|
|
|
|
"github.com/aerogo/nano"
|
|
"github.com/akyoto/color"
|
|
)
|
|
|
|
// QuoteID represents a quote ID.
|
|
type QuoteID = ID
|
|
|
|
// Quote is a quote made by a character in an anime.
|
|
type Quote struct {
|
|
Text QuoteText `json:"text" editable:"true"`
|
|
CharacterID CharacterID `json:"characterId" editable:"true"`
|
|
AnimeID AnimeID `json:"animeId" editable:"true"`
|
|
EpisodeNumber int `json:"episode" editable:"true"`
|
|
Time int `json:"time" editable:"true"`
|
|
|
|
hasID
|
|
hasPosts
|
|
hasCreator
|
|
hasEditor
|
|
hasLikes
|
|
hasDraft
|
|
}
|
|
|
|
// IsMainQuote returns true if the quote is the main quote of the character.
|
|
func (quote *Quote) IsMainQuote() bool {
|
|
return quote.CharacterID != "" && quote.Character().MainQuoteID == quote.ID
|
|
}
|
|
|
|
// TitleByUser returns the preferred title for the given user.
|
|
func (quote *Quote) TitleByUser(user *User) string {
|
|
character := quote.Character()
|
|
|
|
if character == nil {
|
|
return "Quote"
|
|
}
|
|
|
|
return fmt.Sprintf("%s's quote", character.Name.ByUser(user))
|
|
}
|
|
|
|
// Link returns a single quote.
|
|
func (quote *Quote) Link() string {
|
|
return "/quote/" + quote.ID
|
|
}
|
|
|
|
// Publish checks the quote and publishes it when no errors were found.
|
|
func (quote *Quote) Publish() error {
|
|
// No quote text
|
|
if quote.Text.English == "" {
|
|
return errors.New("English quote text is required")
|
|
}
|
|
|
|
// No character
|
|
if quote.CharacterID == "" {
|
|
return errors.New("A character is required")
|
|
}
|
|
|
|
// No anime
|
|
if quote.AnimeID == "" {
|
|
return errors.New("An anime is required")
|
|
}
|
|
|
|
// // No episode number
|
|
// if quote.EpisodeNumber == -1 {
|
|
// return errors.New("An episode number is required")
|
|
// }
|
|
|
|
// // No time
|
|
// if quote.Time == -1 {
|
|
// return errors.New("Time in minutes is required")
|
|
// }
|
|
|
|
// Invalid anime ID
|
|
anime := quote.Anime()
|
|
|
|
if anime == nil {
|
|
return errors.New("Invalid anime ID")
|
|
}
|
|
|
|
// Invalid character ID
|
|
character := quote.Character()
|
|
|
|
if character == nil {
|
|
return errors.New("Invalid character ID")
|
|
}
|
|
|
|
// Invalid episode number
|
|
maxEpisodes := anime.EpisodeCount
|
|
|
|
if maxEpisodes != 0 && quote.EpisodeNumber > maxEpisodes {
|
|
return errors.New("Invalid episode number")
|
|
}
|
|
|
|
return publish(quote)
|
|
}
|
|
|
|
// Unpublish ...
|
|
func (quote *Quote) Unpublish() error {
|
|
return unpublish(quote)
|
|
}
|
|
|
|
// TypeName returns the type name.
|
|
func (quote *Quote) TypeName() string {
|
|
return "Quote"
|
|
}
|
|
|
|
// Self returns the object itself.
|
|
func (quote *Quote) Self() Loggable {
|
|
return quote
|
|
}
|
|
|
|
// OnLike is called when the quote receives a like.
|
|
func (quote *Quote) OnLike(likedBy *User) {
|
|
if !quote.IsValid() {
|
|
color.Red("Invalid quote: %s", quote.ID)
|
|
return
|
|
}
|
|
|
|
if likedBy.ID == quote.CreatedBy {
|
|
return
|
|
}
|
|
|
|
if !quote.Creator().Settings().Notification.QuoteLikes {
|
|
return
|
|
}
|
|
|
|
go func() {
|
|
quote.Creator().SendNotification(&PushNotification{
|
|
Title: likedBy.Nick + " liked your " + quote.Character().Name.Canonical + " quote",
|
|
Message: quote.Text.English,
|
|
Icon: "https:" + likedBy.AvatarLink("large"),
|
|
Link: "https://notify.moe" + likedBy.Link(),
|
|
Type: NotificationTypeLike,
|
|
})
|
|
}()
|
|
}
|
|
|
|
// IsValid tests the field values and returns true if everything is okay.
|
|
func (quote *Quote) IsValid() bool {
|
|
return quote.Character() != nil && quote.Anime() != nil && quote.Creator() != nil
|
|
}
|
|
|
|
// String implements the default string serialization.
|
|
func (quote *Quote) String() string {
|
|
return quote.Text.English
|
|
}
|
|
|
|
// GetQuote returns a single quote.
|
|
func GetQuote(id QuoteID) (*Quote, error) {
|
|
obj, err := DB.Get("Quote", id)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return obj.(*Quote), nil
|
|
}
|
|
|
|
// StreamQuotes returns a stream of all quotes.
|
|
func StreamQuotes() <-chan *Quote {
|
|
channel := make(chan *Quote, nano.ChannelBufferSize)
|
|
|
|
go func() {
|
|
for obj := range DB.All("Quote") {
|
|
channel <- obj.(*Quote)
|
|
}
|
|
|
|
close(channel)
|
|
}()
|
|
|
|
return channel
|
|
}
|
|
|
|
// AllQuotes returns a slice of all quotes.
|
|
func AllQuotes() []*Quote {
|
|
all := make([]*Quote, 0, DB.Collection("Quote").Count())
|
|
|
|
stream := StreamQuotes()
|
|
|
|
for obj := range stream {
|
|
all = append(all, obj)
|
|
}
|
|
|
|
return all
|
|
}
|
|
|
|
// Character returns the character cited in the quote
|
|
func (quote *Quote) Character() *Character {
|
|
character, _ := GetCharacter(quote.CharacterID)
|
|
return character
|
|
}
|
|
|
|
// Anime fetches the anime where the quote is said.
|
|
func (quote *Quote) Anime() *Anime {
|
|
anime, err := GetAnime(quote.AnimeID)
|
|
|
|
if err != nil && !quote.IsDraft {
|
|
color.Red("Error fetching anime: %v", err)
|
|
}
|
|
|
|
return anime
|
|
}
|
|
|
|
// SortQuotesLatestFirst ...
|
|
func SortQuotesLatestFirst(quotes []*Quote) {
|
|
sort.Slice(quotes, func(i, j int) bool {
|
|
return quotes[i].Created > quotes[j].Created
|
|
})
|
|
}
|
|
|
|
// SortQuotesPopularFirst ...
|
|
func SortQuotesPopularFirst(quotes []*Quote) {
|
|
sort.Slice(quotes, func(i, j int) bool {
|
|
aLikes := len(quotes[i].Likes)
|
|
bLikes := len(quotes[j].Likes)
|
|
|
|
if aLikes == bLikes {
|
|
return quotes[i].Created > quotes[j].Created
|
|
}
|
|
|
|
return aLikes > bLikes
|
|
})
|
|
}
|
|
|
|
// FilterQuotes filters all quotes by a custom function.
|
|
func FilterQuotes(filter func(*Quote) bool) []*Quote {
|
|
var filtered []*Quote
|
|
|
|
for obj := range StreamQuotes() {
|
|
if filter(obj) {
|
|
filtered = append(filtered, obj)
|
|
}
|
|
}
|
|
|
|
return filtered
|
|
}
|