|
|
|
|
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
|
|
|
|
|
}
|