You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
496 lines
10 KiB
496 lines
10 KiB
package api |
|
|
|
import ( |
|
"strings" |
|
"sync" |
|
|
|
"github.com/RasmusLindroth/go-mastodon" |
|
"github.com/RasmusLindroth/tut/config" |
|
"github.com/RasmusLindroth/tut/util" |
|
"golang.org/x/exp/slices" |
|
) |
|
|
|
var id uint = 0 |
|
var idMux sync.Mutex |
|
|
|
func newID() uint { |
|
idMux.Lock() |
|
defer idMux.Unlock() |
|
id = id + 1 |
|
return id |
|
} |
|
|
|
type Item interface { |
|
ID() uint |
|
Type() MastodonType |
|
ToggleCW() |
|
ShowCW() bool |
|
Raw() interface{} |
|
URLs() ([]util.URL, []mastodon.Mention, []mastodon.Tag, int) |
|
Filtered(config.FeedType) (bool, string, string, bool) |
|
ForceViewFilter() |
|
Pinned() bool |
|
Refetch(*AccountClient) bool |
|
} |
|
|
|
type filtered struct { |
|
InUse bool |
|
Filters []filter |
|
} |
|
|
|
type filter struct { |
|
Values []string |
|
Where []string |
|
Type string |
|
} |
|
|
|
func getUrlsStatus(status *mastodon.Status) ([]util.URL, []mastodon.Mention, []mastodon.Tag, int) { |
|
if status.Reblog != nil { |
|
status = status.Reblog |
|
} |
|
_, urls := util.CleanHTML(status.Content) |
|
if status.Sensitive { |
|
_, u := util.CleanHTML(status.SpoilerText) |
|
urls = append(urls, u...) |
|
} |
|
|
|
realUrls := []util.URL{} |
|
for _, url := range urls { |
|
isNotMention := true |
|
for _, mention := range status.Mentions { |
|
if mention.URL == url.URL { |
|
isNotMention = false |
|
} |
|
} |
|
if isNotMention { |
|
realUrls = append(realUrls, url) |
|
} |
|
} |
|
|
|
length := len(realUrls) + len(status.Mentions) + len(status.Tags) |
|
return realUrls, status.Mentions, status.Tags, length |
|
} |
|
func getUrlsUser(user *mastodon.Account) ([]util.URL, []mastodon.Mention, []mastodon.Tag, int) { |
|
var urls []util.URL |
|
user.Note, urls = util.CleanHTML(user.Note) |
|
for _, f := range user.Fields { |
|
_, fu := util.CleanHTML(f.Value) |
|
urls = append(urls, fu...) |
|
} |
|
|
|
return urls, []mastodon.Mention{}, []mastodon.Tag{}, len(urls) |
|
} |
|
|
|
func NewStatusItem(item *mastodon.Status, pinned bool) (sitem Item) { |
|
filtered := filtered{InUse: false} |
|
if item == nil { |
|
return &StatusItem{id: newID(), item: item, showSpoiler: false, filtered: filtered, pinned: pinned} |
|
} |
|
s := util.StatusOrReblog(item) |
|
for _, f := range s.Filtered { |
|
filtered.InUse = true |
|
filtered.Filters = append(filtered.Filters, |
|
filter{ |
|
Type: f.Filter.FilterAction, |
|
Values: f.KeywordMatches, |
|
Where: f.Filter.Context, |
|
}) |
|
} |
|
sitem = &StatusItem{id: newID(), item: item, showSpoiler: false, filtered: filtered, pinned: pinned} |
|
return sitem |
|
} |
|
|
|
func NewStatusItemID(item *mastodon.Status, pinned bool, id uint) (sitem Item) { |
|
sitem = NewStatusItem(item, pinned) |
|
sitem.(*StatusItem).id = id |
|
return sitem |
|
} |
|
|
|
type StatusItem struct { |
|
id uint |
|
item *mastodon.Status |
|
showSpoiler bool |
|
forceView bool |
|
filtered filtered |
|
pinned bool |
|
} |
|
|
|
func (s *StatusItem) ID() uint { |
|
return s.id |
|
} |
|
|
|
func (s *StatusItem) Type() MastodonType { |
|
return StatusType |
|
} |
|
|
|
func (s *StatusItem) ToggleCW() { |
|
s.showSpoiler = !s.showSpoiler |
|
} |
|
|
|
func (s *StatusItem) ShowCW() bool { |
|
return s.showSpoiler |
|
} |
|
|
|
func (s *StatusItem) Raw() interface{} { |
|
return s.item |
|
} |
|
|
|
func (s *StatusItem) URLs() ([]util.URL, []mastodon.Mention, []mastodon.Tag, int) { |
|
return getUrlsStatus(s.item) |
|
} |
|
|
|
func (s *StatusItem) Filtered(tl config.FeedType) (bool, string, string, bool) { |
|
if !s.filtered.InUse || s.forceView { |
|
return false, "", "", true |
|
} |
|
words := []string{} |
|
t := "" |
|
for _, f := range s.filtered.Filters { |
|
used := false |
|
for _, w := range f.Where { |
|
switch w { |
|
case "home": |
|
if tl == config.TimelineHome || tl == config.List || tl == config.TimelineHomeSpecial { |
|
used = true |
|
} |
|
case "thread": |
|
if tl == config.Thread || tl == config.Conversations { |
|
used = true |
|
} |
|
case "notifications": |
|
if tl == config.Notifications || tl == config.Mentions { |
|
used = true |
|
} |
|
case "account": |
|
if tl == config.User { |
|
used = true |
|
} |
|
case "public": |
|
where := []config.FeedType{ |
|
config.TimelineFederated, |
|
config.TimelineLocal, |
|
config.Tag, |
|
} |
|
if slices.Contains(where, tl) { |
|
used = true |
|
} |
|
} |
|
if used { |
|
words = append(words, f.Values...) |
|
if t == "" || t == "warn" { |
|
t = f.Type |
|
} |
|
break |
|
} |
|
} |
|
} |
|
return len(words) > 0, t, strings.Join(words, ", "), s.forceView |
|
} |
|
|
|
func (s *StatusItem) ForceViewFilter() { |
|
s.forceView = true |
|
} |
|
|
|
func (s *StatusItem) Pinned() bool { |
|
return s.pinned |
|
} |
|
|
|
func (s *StatusItem) Refetch(ac *AccountClient) bool { |
|
ns, err := ac.GetStatus(s.item.ID) |
|
if err != nil { |
|
return false |
|
} |
|
nsi := NewStatusItemID(ns, s.pinned, s.id) |
|
*s = *nsi.(*StatusItem) |
|
return true |
|
} |
|
|
|
func NewStatusHistoryItem(item *mastodon.StatusHistory) (sitem Item) { |
|
return &StatusHistoryItem{id: newID(), item: item, showSpoiler: false} |
|
} |
|
|
|
type StatusHistoryItem struct { |
|
id uint |
|
item *mastodon.StatusHistory |
|
showSpoiler bool |
|
} |
|
|
|
func (s *StatusHistoryItem) ID() uint { |
|
return s.id |
|
} |
|
|
|
func (s *StatusHistoryItem) Type() MastodonType { |
|
return StatusHistoryType |
|
} |
|
|
|
func (s *StatusHistoryItem) ToggleCW() { |
|
s.showSpoiler = !s.showSpoiler |
|
} |
|
|
|
func (s *StatusHistoryItem) ShowCW() bool { |
|
return s.showSpoiler |
|
} |
|
|
|
func (s *StatusHistoryItem) Raw() interface{} { |
|
return s.item |
|
} |
|
|
|
func (s *StatusHistoryItem) URLs() ([]util.URL, []mastodon.Mention, []mastodon.Tag, int) { |
|
status := mastodon.Status{ |
|
Content: s.item.Content, |
|
SpoilerText: s.item.SpoilerText, |
|
Account: s.item.Account, |
|
Sensitive: s.item.Sensitive, |
|
CreatedAt: s.item.CreatedAt, |
|
Emojis: s.item.Emojis, |
|
MediaAttachments: s.item.MediaAttachments, |
|
} |
|
return getUrlsStatus(&status) |
|
} |
|
|
|
func (s *StatusHistoryItem) Filtered(config.FeedType) (bool, string, string, bool) { |
|
return false, "", "", true |
|
} |
|
|
|
func (t *StatusHistoryItem) ForceViewFilter() {} |
|
|
|
func (s *StatusHistoryItem) Pinned() bool { |
|
return false |
|
} |
|
|
|
func (s *StatusHistoryItem) Refetch(ac *AccountClient) bool { |
|
return false |
|
} |
|
|
|
func NewUserItem(item *User, profile bool) Item { |
|
return &UserItem{id: newID(), item: item, profile: profile} |
|
} |
|
|
|
type UserItem struct { |
|
id uint |
|
item *User |
|
profile bool |
|
} |
|
|
|
func (u *UserItem) ID() uint { |
|
return u.id |
|
} |
|
|
|
func (u *UserItem) Type() MastodonType { |
|
if u.profile { |
|
return ProfileType |
|
} |
|
return UserType |
|
} |
|
|
|
func (u *UserItem) ToggleCW() { |
|
} |
|
|
|
func (u *UserItem) ShowCW() bool { |
|
return false |
|
} |
|
|
|
func (u *UserItem) Raw() interface{} { |
|
return u.item |
|
} |
|
|
|
func (u *UserItem) URLs() ([]util.URL, []mastodon.Mention, []mastodon.Tag, int) { |
|
return getUrlsUser(u.item.Data) |
|
} |
|
|
|
func (u *UserItem) Filtered(config.FeedType) (bool, string, string, bool) { |
|
return false, "", "", true |
|
} |
|
|
|
func (u *UserItem) ForceViewFilter() {} |
|
|
|
func (u *UserItem) Pinned() bool { |
|
return false |
|
} |
|
|
|
func (u *UserItem) Refetch(ac *AccountClient) bool { |
|
return false |
|
} |
|
|
|
func NewNotificationItem(item *mastodon.Notification, user *User) (nitem Item) { |
|
status := NewStatusItem(item.Status, false) |
|
nitem = &NotificationItem{ |
|
id: newID(), |
|
item: item, |
|
showSpoiler: false, |
|
user: NewUserItem(user, false), |
|
status: status, |
|
} |
|
|
|
return nitem |
|
} |
|
|
|
type NotificationItem struct { |
|
id uint |
|
item *mastodon.Notification |
|
showSpoiler bool |
|
status Item |
|
user Item |
|
} |
|
|
|
type NotificationData struct { |
|
Item *mastodon.Notification |
|
Status Item |
|
User Item |
|
} |
|
|
|
func (n *NotificationItem) ID() uint { |
|
return n.id |
|
} |
|
|
|
func (n *NotificationItem) Type() MastodonType { |
|
return NotificationType |
|
} |
|
|
|
func (n *NotificationItem) ToggleCW() { |
|
n.showSpoiler = !n.showSpoiler |
|
} |
|
|
|
func (n *NotificationItem) ShowCW() bool { |
|
return n.showSpoiler |
|
} |
|
|
|
func (n *NotificationItem) Raw() interface{} { |
|
return &NotificationData{ |
|
Item: n.item, |
|
Status: n.status, |
|
User: n.user, |
|
} |
|
} |
|
|
|
func (n *NotificationItem) URLs() ([]util.URL, []mastodon.Mention, []mastodon.Tag, int) { |
|
nd := n.Raw().(*NotificationData) |
|
switch n.item.Type { |
|
case "favourite": |
|
return getUrlsStatus(nd.Status.Raw().(*mastodon.Status)) |
|
case "reblog": |
|
return getUrlsStatus(nd.Status.Raw().(*mastodon.Status)) |
|
case "mention": |
|
return getUrlsStatus(nd.Status.Raw().(*mastodon.Status)) |
|
case "status": |
|
return getUrlsStatus(nd.Status.Raw().(*mastodon.Status)) |
|
case "poll": |
|
return getUrlsStatus(nd.Status.Raw().(*mastodon.Status)) |
|
case "update": |
|
return getUrlsStatus(nd.Status.Raw().(*mastodon.Status)) |
|
case "follow": |
|
return getUrlsUser(nd.User.Raw().(*User).Data) |
|
case "follow_request": |
|
return getUrlsUser(nd.User.Raw().(*User).Data) |
|
default: |
|
return []util.URL{}, []mastodon.Mention{}, []mastodon.Tag{}, 0 |
|
} |
|
} |
|
|
|
func (n *NotificationItem) Filtered(config.FeedType) (bool, string, string, bool) { |
|
return false, "", "", true |
|
} |
|
|
|
func (n *NotificationItem) ForceViewFilter() {} |
|
|
|
func (n *NotificationItem) Pinned() bool { |
|
return false |
|
} |
|
|
|
func (n *NotificationItem) Refetch(ac *AccountClient) bool { |
|
return false |
|
} |
|
|
|
func NewListsItem(item *mastodon.List) Item { |
|
return &ListItem{id: newID(), item: item, showSpoiler: true} |
|
} |
|
|
|
type ListItem struct { |
|
id uint |
|
item *mastodon.List |
|
showSpoiler bool |
|
} |
|
|
|
func (s *ListItem) ID() uint { |
|
return s.id |
|
} |
|
|
|
func (s *ListItem) Type() MastodonType { |
|
return ListsType |
|
} |
|
|
|
func (s *ListItem) ToggleCW() { |
|
} |
|
|
|
func (s *ListItem) ShowCW() bool { |
|
return true |
|
} |
|
|
|
func (s *ListItem) Raw() interface{} { |
|
return s.item |
|
} |
|
|
|
func (s *ListItem) URLs() ([]util.URL, []mastodon.Mention, []mastodon.Tag, int) { |
|
return nil, nil, nil, 0 |
|
} |
|
|
|
func (s *ListItem) Filtered(config.FeedType) (bool, string, string, bool) { |
|
return false, "", "", true |
|
} |
|
|
|
func (l *ListItem) ForceViewFilter() {} |
|
|
|
func (n *ListItem) Pinned() bool { |
|
return false |
|
} |
|
|
|
func (l *ListItem) Refetch(ac *AccountClient) bool { |
|
return false |
|
} |
|
|
|
func NewTagItem(item *mastodon.Tag) Item { |
|
return &TagItem{id: newID(), item: item, showSpoiler: true} |
|
} |
|
|
|
type TagItem struct { |
|
id uint |
|
item *mastodon.Tag |
|
showSpoiler bool |
|
} |
|
|
|
func (t *TagItem) ID() uint { |
|
return t.id |
|
} |
|
|
|
func (t *TagItem) Type() MastodonType { |
|
return TagType |
|
} |
|
|
|
func (t *TagItem) ToggleCW() { |
|
} |
|
|
|
func (t *TagItem) ShowCW() bool { |
|
return true |
|
} |
|
|
|
func (t *TagItem) Raw() interface{} { |
|
return t.item |
|
} |
|
|
|
func (t *TagItem) URLs() ([]util.URL, []mastodon.Mention, []mastodon.Tag, int) { |
|
return nil, nil, nil, 0 |
|
} |
|
|
|
func (t *TagItem) Filtered(config.FeedType) (bool, string, string, bool) { |
|
return false, "", "", true |
|
} |
|
|
|
func (t *TagItem) ForceViewFilter() {} |
|
|
|
func (t *TagItem) Pinned() bool { |
|
return false |
|
} |
|
|
|
func (t *TagItem) Refetch(ac *AccountClient) bool { |
|
return false |
|
}
|
|
|