From f43fec0b8da3eb5e70f8c28d4e3609c345d56344 Mon Sep 17 00:00:00 2001 From: Rasmus Lindroth Date: Sat, 14 May 2022 16:03:49 +0200 Subject: [PATCH] 1.0.6 (#143) * fix streams not closing * prepare for filters * support for filters * wrap words * support for tag leader * update config * bump version --- api/feed.go | 70 +++++++++--- api/item.go | 70 ++++++++++-- api/stream.go | 34 +++--- api/types.go | 1 + config.example.ini | 6 +- config/config.go | 19 +++- config/default_config.go | 6 +- feed/feed.go | 227 +++++++++++++++++++++++---------------- main.go | 2 +- ui/commands.go | 6 ++ ui/feed.go | 1 + ui/input.go | 5 + ui/tutview.go | 2 + 13 files changed, 316 insertions(+), 133 deletions(-) diff --git a/api/feed.go b/api/feed.go index 417ad03..2a65403 100644 --- a/api/feed.go +++ b/api/feed.go @@ -15,7 +15,10 @@ func (ac *AccountClient) GetTimeline(pg *mastodon.Pagination) ([]Item, error) { return items, err } for _, s := range statuses { - items = append(items, NewStatusItem(s)) + item, filtered := NewStatusItem(s, ac.Filters, "home") + if !filtered { + items = append(items, item) + } } return items, nil } @@ -27,7 +30,10 @@ func (ac *AccountClient) GetTimelineFederated(pg *mastodon.Pagination) ([]Item, return items, err } for _, s := range statuses { - items = append(items, NewStatusItem(s)) + item, filtered := NewStatusItem(s, ac.Filters, "public") + if !filtered { + items = append(items, item) + } } return items, nil } @@ -39,7 +45,10 @@ func (ac *AccountClient) GetTimelineLocal(pg *mastodon.Pagination) ([]Item, erro return items, err } for _, s := range statuses { - items = append(items, NewStatusItem(s)) + item, filtered := NewStatusItem(s, ac.Filters, "public") + if !filtered { + items = append(items, item) + } } return items, nil } @@ -61,10 +70,12 @@ func (ac *AccountClient) GetNotifications(pg *mastodon.Pagination) ([]Item, erro for _, n := range notifications { for _, r := range rel { if n.Account.ID == r.ID { - items = append(items, NewNotificationItem(n, &User{ - Data: &n.Account, - Relation: r, - })) + item, filtered := NewNotificationItem(n, &User{ + Data: &n.Account, Relation: r, + }, ac.Filters) + if !filtered { + items = append(items, item) + } break } } @@ -79,11 +90,20 @@ func (ac *AccountClient) GetThread(status *mastodon.Status) ([]Item, int, error) return items, 0, err } for _, s := range statuses.Ancestors { - items = append(items, NewStatusItem(s)) + item, filtered := NewStatusItem(s, ac.Filters, "thread") + if !filtered { + items = append(items, item) + } + } + item, filtered := NewStatusItem(status, ac.Filters, "thread") + if !filtered { + items = append(items, item) } - items = append(items, NewStatusItem(status)) for _, s := range statuses.Descendants { - items = append(items, NewStatusItem(s)) + item, filtered := NewStatusItem(s, ac.Filters, "thread") + if !filtered { + items = append(items, item) + } } return items, len(statuses.Ancestors), nil } @@ -95,7 +115,10 @@ func (ac *AccountClient) GetFavorites(pg *mastodon.Pagination) ([]Item, error) { return items, err } for _, s := range statuses { - items = append(items, NewStatusItem(s)) + item, filtered := NewStatusItem(s, ac.Filters, "home") + if !filtered { + items = append(items, item) + } } return items, nil } @@ -107,7 +130,10 @@ func (ac *AccountClient) GetBookmarks(pg *mastodon.Pagination) ([]Item, error) { return items, err } for _, s := range statuses { - items = append(items, NewStatusItem(s)) + item, filtered := NewStatusItem(s, ac.Filters, "home") + if !filtered { + items = append(items, item) + } } return items, nil } @@ -119,7 +145,10 @@ func (ac *AccountClient) GetConversations(pg *mastodon.Pagination) ([]Item, erro return items, err } for _, c := range conversations { - items = append(items, NewStatusItem(c.LastStatus)) + item, filtered := NewStatusItem(c.LastStatus, ac.Filters, "thread") + if !filtered { + items = append(items, item) + } } return items, nil } @@ -229,7 +258,10 @@ func (ac *AccountClient) GetUser(pg *mastodon.Pagination, id mastodon.ID) ([]Ite return items, err } for _, s := range statuses { - items = append(items, NewStatusItem(s)) + item, filtered := NewStatusItem(s, ac.Filters, "account") + if !filtered { + items = append(items, item) + } } return items, nil } @@ -253,7 +285,10 @@ func (ac *AccountClient) GetListStatuses(pg *mastodon.Pagination, id mastodon.ID return items, err } for _, s := range statuses { - items = append(items, NewStatusItem(s)) + item, filtered := NewStatusItem(s, ac.Filters, "home") + if !filtered { + items = append(items, item) + } } return items, nil } @@ -265,7 +300,10 @@ func (ac *AccountClient) GetTag(pg *mastodon.Pagination, search string) ([]Item, return items, err } for _, s := range statuses { - items = append(items, NewStatusItem(s)) + item, filtered := NewStatusItem(s, ac.Filters, "public") + if !filtered { + items = append(items, item) + } } return items, nil } diff --git a/api/item.go b/api/item.go index 127bd18..ef53ea9 100644 --- a/api/item.go +++ b/api/item.go @@ -1,7 +1,9 @@ package api import ( + "strings" "sync" + "unicode" "github.com/RasmusLindroth/go-mastodon" "github.com/RasmusLindroth/tut/util" @@ -26,8 +28,63 @@ type Item interface { URLs() ([]util.URL, []mastodon.Mention, []mastodon.Tag, int) } -func NewStatusItem(item *mastodon.Status) Item { - return &StatusItem{id: newID(), item: item, showSpoiler: false} +func NewStatusItem(item *mastodon.Status, filters []*mastodon.Filter, timeline string) (sitem Item, filtered bool) { + filtered = false + if item == nil { + return &StatusItem{id: newID(), item: item, showSpoiler: false}, false + } + s := util.StatusOrReblog(item) + content := s.Content + if s.Sensitive { + content += "\n" + s.SpoilerText + } + content = strings.ToLower(content) + for _, f := range filters { + apply := false + for _, c := range f.Context { + if timeline == c { + apply = true + break + } + } + if !apply { + continue + } + if f.WholeWord { + lines := strings.Split(content, "\n") + var stripped []string + for _, l := range lines { + var words []string + words = append(words, strings.Split(l, " ")...) + for _, w := range words { + ns := strings.TrimSpace(w) + ns = strings.TrimFunc(ns, func(r rune) bool { + return !unicode.IsLetter(r) && !unicode.IsNumber(r) + }) + stripped = append(stripped, ns) + } + } + filter := strings.Split(strings.ToLower(f.Phrase), " ") + for i := 0; i+len(filter)-1 < len(stripped); i++ { + if strings.ToLower(f.Phrase) == strings.Join(stripped[i:i+len(filter)], " ") { + filtered = true + break + } + } + } else { + if strings.Contains(s.Content, strings.ToLower(f.Phrase)) { + filtered = true + } + if strings.Contains(s.SpoilerText, strings.ToLower(f.Phrase)) { + filtered = true + } + } + if filtered { + break + } + } + sitem = &StatusItem{id: newID(), item: item, showSpoiler: false} + return sitem, filtered } type StatusItem struct { @@ -128,16 +185,17 @@ func (u *UserItem) URLs() ([]util.URL, []mastodon.Mention, []mastodon.Tag, int) return urls, []mastodon.Mention{}, []mastodon.Tag{}, len(urls) } -func NewNotificationItem(item *mastodon.Notification, user *User) Item { - n := &NotificationItem{ +func NewNotificationItem(item *mastodon.Notification, user *User, filters []*mastodon.Filter) (nitem Item, filtred bool) { + status, filtred := NewStatusItem(item.Status, filters, "notifications") + nitem = &NotificationItem{ id: newID(), item: item, showSpoiler: false, user: NewUserItem(user, false), - status: NewStatusItem(item.Status), + status: status, } - return n + return nitem, filtred } type NotificationItem struct { diff --git a/api/stream.go b/api/stream.go index c8522e9..db28f43 100644 --- a/api/stream.go +++ b/api/stream.go @@ -153,28 +153,34 @@ func (ac *AccountClient) NewGenericStream(st StreamType, data string) (rec *Rece return rec, nil } -func (ac *AccountClient) NewHomeStream() (*Receiver, error) { - return ac.NewGenericStream(HomeStream, "") +func (ac *AccountClient) NewHomeStream() (*Receiver, string, error) { + rec, err := ac.NewGenericStream(HomeStream, "") + return rec, "home", err } -func (ac *AccountClient) NewLocalStream() (*Receiver, error) { - return ac.NewGenericStream(LocalStream, "") +func (ac *AccountClient) NewLocalStream() (*Receiver, string, error) { + rec, err := ac.NewGenericStream(LocalStream, "") + return rec, "public", err } -func (ac *AccountClient) NewFederatedStream() (*Receiver, error) { - return ac.NewGenericStream(FederatedStream, "") +func (ac *AccountClient) NewFederatedStream() (*Receiver, string, error) { + rec, err := ac.NewGenericStream(FederatedStream, "") + return rec, "public", err } -func (ac *AccountClient) NewDirectStream() (*Receiver, error) { - return ac.NewGenericStream(DirectStream, "") +func (ac *AccountClient) NewDirectStream() (*Receiver, string, error) { + rec, err := ac.NewGenericStream(DirectStream, "") + return rec, "public", err } -func (ac *AccountClient) NewListStream(id mastodon.ID) (*Receiver, error) { - return ac.NewGenericStream(ListStream, string(id)) +func (ac *AccountClient) NewListStream(id mastodon.ID) (*Receiver, string, error) { + rec, err := ac.NewGenericStream(ListStream, string(id)) + return rec, "home", err } -func (ac *AccountClient) NewTagStream(tag string) (*Receiver, error) { - return ac.NewGenericStream(TagStream, tag) +func (ac *AccountClient) NewTagStream(tag string) (*Receiver, string, error) { + rec, err := ac.NewGenericStream(TagStream, tag) + return rec, "public", err } func (ac *AccountClient) RemoveGenericReceiver(rec *Receiver, st StreamType, data string) { @@ -214,6 +220,10 @@ func (ac *AccountClient) RemoveLocalReceiver(rec *Receiver) { ac.RemoveGenericReceiver(rec, LocalStream, "") } +func (ac *AccountClient) RemoveConversationReceiver(rec *Receiver) { + ac.RemoveGenericReceiver(rec, DirectStream, "") +} + func (ac *AccountClient) RemoveFederatedReceiver(rec *Receiver) { ac.RemoveGenericReceiver(rec, FederatedStream, "") } diff --git a/api/types.go b/api/types.go index 2064d18..89dc9ae 100644 --- a/api/types.go +++ b/api/types.go @@ -11,6 +11,7 @@ type AccountClient struct { Client *mastodon.Client Streams map[string]*Stream Me *mastodon.Account + Filters []*mastodon.Filter } type User struct { diff --git a/config.example.ini b/config.example.ini index fb75160..33f2b49 100644 --- a/config.example.ini +++ b/config.example.ini @@ -115,17 +115,19 @@ leader-timeout=1000 # # Available commands: home, direct, local, federated, compose, blocking, # bookmarks, saved, favorited, boosts, favorites, following, followers, muting, -# profile, notifications, lists +# profile, notifications, lists, tag # # The shortcuts are up to you, but keep them quite short and make sure they # don't collide. If you have one shortcut that is "f" and an other one that is -# "fav", the one with "f" will always run and "fav" will never run. +# "fav", the one with "f" will always run and "fav" will never run. Tag is +# special as you need to add the tag after, see the example below. # # Some examples: # leader-action=local,lo # leader-action=lists,li # leader-action=federated,fed # leader-action=direct,d +# leader-action=tag linux,tl # diff --git a/config/config.go b/config/config.go index 35e25fe..9e6dcc6 100644 --- a/config/config.go +++ b/config/config.go @@ -42,8 +42,9 @@ type Config struct { } type LeaderAction struct { - Command LeaderCommand - Shortcut string + Command LeaderCommand + Subaction string + Shortcut string } type LeaderCommand uint @@ -67,6 +68,8 @@ const ( LeaderProfile LeaderNotifications LeaderLists + LeaderTag + LeaderUser ) type General struct { @@ -600,8 +603,15 @@ func parseGeneral(cfg *ini.File) General { for i, p := range parts { parts[i] = strings.TrimSpace(p) } + cmd := parts[0] + var subaction string + if strings.Contains(parts[0], " ") { + p := strings.Split(cmd, " ") + cmd = p[0] + subaction = strings.Join(p[1:], " ") + } la := LeaderAction{} - switch parts[0] { + switch cmd { case "home": la.Command = LeaderHome case "direct": @@ -636,6 +646,9 @@ func parseGeneral(cfg *ini.File) General { la.Command = LeaderNotifications case "lists": la.Command = LeaderLists + case "tag": + la.Command = LeaderTag + la.Subaction = subaction default: fmt.Printf("leader-action %s is invalid\n", parts[0]) os.Exit(1) diff --git a/config/default_config.go b/config/default_config.go index d9742c7..800f429 100644 --- a/config/default_config.go +++ b/config/default_config.go @@ -117,17 +117,19 @@ leader-timeout=1000 # # Available commands: home, direct, local, federated, compose, blocking, # bookmarks, saved, favorited, boosts, favorites, following, followers, muting, -# profile, notifications, lists +# profile, notifications, lists, tag # # The shortcuts are up to you, but keep them quite short and make sure they # don't collide. If you have one shortcut that is "f" and an other one that is -# "fav", the one with "f" will always run and "fav" will never run. +# "fav", the one with "f" will always run and "fav" will never run. Tag is +# special as you need to add the tag after, see the example below. # # Some examples: # leader-action=local,lo # leader-action=lists,li # leader-action=federated,fed # leader-action=direct,d +# leader-action=tag linux,tl # diff --git a/feed/feed.go b/feed/feed.go index 10622d4..22561e6 100644 --- a/feed/feed.go +++ b/feed/feed.go @@ -180,113 +180,134 @@ func (f *Feed) singleThread(fn apiThreadFunc, status *mastodon.Status) { } func (f *Feed) normalNewer(fn apiFunc) { - f.itemsMux.RLock() pg := mastodon.Pagination{} - if len(f.items) > 0 { - switch item := f.items[0].Raw().(type) { - case *mastodon.Status: - pg.MinID = item.ID - case *api.NotificationData: - pg.MinID = item.Item.ID - } + f.apiDataMux.Lock() + if f.apiData.MinID != mastodon.ID("") { + pg.MinID = f.apiData.MinID } - f.itemsMux.RUnlock() items, err := fn(&pg) if err != nil { + f.apiDataMux.Unlock() return } f.itemsMux.Lock() if len(items) > 0 { + switch item := items[0].Raw().(type) { + case *mastodon.Status: + f.apiData.MinID = item.ID + case *api.NotificationData: + f.apiData.MinID = item.Item.ID + } + if f.apiData.MaxID == mastodon.ID("") { + switch item := items[len(items)-1].Raw().(type) { + case *mastodon.Status: + f.apiData.MaxID = item.ID + case *api.NotificationData: + f.apiData.MaxID = item.Item.ID + } + } f.items = append(items, f.items...) f.Updated(DekstopNotificationNone) } f.itemsMux.Unlock() + f.apiDataMux.Unlock() } func (f *Feed) normalOlder(fn apiFunc) { - f.itemsMux.RLock() pg := mastodon.Pagination{} - if len(f.items) > 0 { - switch item := f.items[len(f.items)-1].Raw().(type) { - case *mastodon.Status: - pg.MaxID = item.ID - case *api.NotificationData: - pg.MaxID = item.Item.ID - } + f.apiDataMux.Lock() + if f.apiData.MaxID == mastodon.ID("") { + f.apiDataMux.Unlock() + f.loadNewer() + return } - f.itemsMux.RUnlock() + pg.MaxID = f.apiData.MaxID items, err := fn(&pg) if err != nil { + f.apiDataMux.Unlock() return } f.itemsMux.Lock() if len(items) > 0 { + switch item := items[len(items)-1].Raw().(type) { + case *mastodon.Status: + f.apiData.MaxID = item.ID + case *api.NotificationData: + f.apiData.MaxID = item.Item.ID + } f.items = append(f.items, items...) f.Updated(DekstopNotificationNone) } f.itemsMux.Unlock() + f.apiDataMux.Unlock() } func (f *Feed) newerSearchPG(fn apiSearchPGFunc, search string) { - f.itemsMux.RLock() pg := mastodon.Pagination{} - if len(f.items) > 0 { - switch item := f.items[0].Raw().(type) { - case *mastodon.Status: - pg.MinID = item.ID - } + f.apiDataMux.Lock() + if f.apiData.MinID != mastodon.ID("") { + pg.MinID = f.apiData.MinID } - f.itemsMux.RUnlock() items, err := fn(&pg, search) if err != nil { + f.apiDataMux.Unlock() return } f.itemsMux.Lock() if len(items) > 0 { + item := items[0].Raw().(*mastodon.Status) + f.apiData.MinID = item.ID f.items = append(items, f.items...) f.Updated(DekstopNotificationNone) + if f.apiData.MaxID == mastodon.ID("") { + item = items[len(items)-1].Raw().(*mastodon.Status) + f.apiData.MaxID = item.ID + } } f.itemsMux.Unlock() + f.apiDataMux.Unlock() } func (f *Feed) olderSearchPG(fn apiSearchPGFunc, search string) { - f.itemsMux.RLock() pg := mastodon.Pagination{} - if len(f.items) > 0 { - switch item := f.items[len(f.items)-1].Raw().(type) { - case *mastodon.Status: - pg.MaxID = item.ID - } + f.apiDataMux.Lock() + if f.apiData.MaxID == mastodon.ID("") { + f.apiDataMux.Unlock() + f.loadNewer() + return } - f.itemsMux.RUnlock() + pg.MaxID = f.apiData.MaxID items, err := fn(&pg, search) if err != nil { + f.apiDataMux.Unlock() return } f.itemsMux.Lock() if len(items) > 0 { + item := items[len(items)-1].Raw().(*mastodon.Status) + f.apiData.MaxID = item.ID f.items = append(f.items, items...) f.Updated(DekstopNotificationNone) } f.itemsMux.Unlock() + f.apiDataMux.Unlock() } func (f *Feed) normalNewerUser(fn apiIDFunc, id mastodon.ID) { - f.itemsMux.RLock() pg := mastodon.Pagination{} - if len(f.items) > 1 { - switch item := f.items[1].Raw().(type) { - case *mastodon.Status: - pg.MinID = item.ID - } + f.apiDataMux.Lock() + if f.apiData.MinID != mastodon.ID("") { + pg.MinID = f.apiData.MinID } - f.itemsMux.RUnlock() items, err := fn(&pg, id) if err != nil { + f.apiDataMux.Unlock() return } f.itemsMux.Lock() if len(items) > 0 { + item := items[0].Raw().(*mastodon.Status) + f.apiData.MinID = item.ID newItems := []api.Item{f.items[0]} newItems = append(newItems, items...) if len(f.items) > 1 { @@ -294,74 +315,89 @@ func (f *Feed) normalNewerUser(fn apiIDFunc, id mastodon.ID) { } f.items = newItems f.Updated(DekstopNotificationNone) + if f.apiData.MaxID == mastodon.ID("") { + item = items[len(items)-1].Raw().(*mastodon.Status) + f.apiData.MaxID = item.ID + } } f.itemsMux.Unlock() + f.apiDataMux.Unlock() } func (f *Feed) normalOlderUser(fn apiIDFunc, id mastodon.ID) { - f.itemsMux.RLock() pg := mastodon.Pagination{} - if len(f.items) > 1 { - switch item := f.items[len(f.items)-1].Raw().(type) { - case *mastodon.Status: - pg.MaxID = item.ID - } + f.apiDataMux.Lock() + if f.apiData.MaxID == mastodon.ID("") { + f.apiDataMux.Unlock() + f.loadNewer() + return } - f.itemsMux.RUnlock() + pg.MaxID = f.apiData.MaxID items, err := fn(&pg, id) if err != nil { + f.apiDataMux.Unlock() return } f.itemsMux.Lock() if len(items) > 0 { + item := items[len(items)-1].Raw().(*mastodon.Status) + f.apiData.MaxID = item.ID f.items = append(f.items, items...) f.Updated(DekstopNotificationNone) } f.itemsMux.Unlock() + f.apiDataMux.Unlock() } func (f *Feed) normalNewerID(fn apiIDFunc, id mastodon.ID) { - f.itemsMux.RLock() pg := mastodon.Pagination{} - if len(f.items) > 0 { - switch item := f.items[0].Raw().(type) { - case *mastodon.Status: - pg.MinID = item.ID - } + f.apiDataMux.Lock() + if f.apiData.MinID != mastodon.ID("") { + pg.MinID = f.apiData.MinID } - f.itemsMux.RUnlock() items, err := fn(&pg, id) if err != nil { + f.apiDataMux.Unlock() return } f.itemsMux.Lock() if len(items) > 0 { + item := items[0].Raw().(*mastodon.Status) + f.apiData.MinID = item.ID f.items = append(items, f.items...) f.Updated(DekstopNotificationNone) + if f.apiData.MaxID == mastodon.ID("") { + item = items[len(items)-1].Raw().(*mastodon.Status) + f.apiData.MaxID = item.ID + } } f.itemsMux.Unlock() + f.apiDataMux.Unlock() } func (f *Feed) normalOlderID(fn apiIDFunc, id mastodon.ID) { - f.itemsMux.RLock() pg := mastodon.Pagination{} - if len(f.items) > 0 { - switch item := f.items[len(f.items)-1].Raw().(type) { - case *mastodon.Status: - pg.MaxID = item.ID - } + f.apiDataMux.Lock() + if f.apiData.MaxID == mastodon.ID("") { + f.apiDataMux.Unlock() + f.loadNewer() + return } - f.itemsMux.RUnlock() + pg.MaxID = f.apiData.MaxID items, err := fn(&pg, id) if err != nil { + f.apiDataMux.Unlock() return } f.itemsMux.Lock() if len(items) > 0 { + item := items[len(items)-1].Raw().(*mastodon.Status) + f.apiData.MaxID = item.ID f.items = append(f.items, items...) f.Updated(DekstopNotificationNone) } f.itemsMux.Unlock() + f.apiDataMux.Unlock() } func (f *Feed) normalEmpty(fn apiEmptyFunc) { @@ -479,7 +515,7 @@ func (f *Feed) linkOlderID(fn apiIDFunc, id mastodon.ID) { f.itemsMux.Unlock() } -func (f *Feed) startStream(rec *api.Receiver, err error) { +func (f *Feed) startStream(rec *api.Receiver, timeline string, err error) { if err != nil { log.Fatalln("Couldn't open stream") } @@ -488,17 +524,19 @@ func (f *Feed) startStream(rec *api.Receiver, err error) { for e := range f.stream.Ch { switch t := e.(type) { case *mastodon.UpdateEvent: - s := api.NewStatusItem(t.Status) - f.itemsMux.Lock() - f.items = append([]api.Item{s}, f.items...) - f.Updated(DesktopNotificationPost) - f.itemsMux.Unlock() + s, filtered := api.NewStatusItem(t.Status, f.accountClient.Filters, timeline) + if !filtered { + f.itemsMux.Lock() + f.items = append([]api.Item{s}, f.items...) + f.Updated(DesktopNotificationPost) + f.itemsMux.Unlock() + } } } }() } -func (f *Feed) startStreamNotification(rec *api.Receiver, err error) { +func (f *Feed) startStreamNotification(rec *api.Receiver, timeline string, err error) { if err != nil { log.Fatalln("Couldn't open stream") } @@ -515,30 +553,32 @@ func (f *Feed) startStreamNotification(rec *api.Receiver, err error) { log.Fatalln(t.Notification.Account.Acct) continue } - s := api.NewNotificationItem(t.Notification, + s, filtered := api.NewNotificationItem(t.Notification, &api.User{ Data: &t.Notification.Account, Relation: rel[0], - }) - f.itemsMux.Lock() - f.items = append([]api.Item{s}, f.items...) - nft := DekstopNotificationNone - switch t.Notification.Type { - case "follow", "follow_request": - nft = DesktopNotificationFollower - case "favourite": - nft = DesktopNotificationFollower - case "reblog": - nft = DesktopNotificationBoost - case "mention": - nft = DesktopNotificationMention - case "status": - nft = DesktopNotificationPost - case "poll": - nft = DesktopNotificationPoll + }, f.accountClient.Filters) + if !filtered { + f.itemsMux.Lock() + f.items = append([]api.Item{s}, f.items...) + nft := DekstopNotificationNone + switch t.Notification.Type { + case "follow", "follow_request": + nft = DesktopNotificationFollower + case "favourite": + nft = DesktopNotificationFollower + case "reblog": + nft = DesktopNotificationBoost + case "mention": + nft = DesktopNotificationMention + case "status": + nft = DesktopNotificationPost + case "poll": + nft = DesktopNotificationPoll + } + f.Updated(nft) + f.itemsMux.Unlock() } - f.Updated(nft) - f.itemsMux.Unlock() } } }() @@ -599,7 +639,7 @@ func NewTimelineLocal(ac *api.AccountClient) *Feed { feed.loadNewer = func() { feed.normalNewer(feed.accountClient.GetTimelineLocal) } feed.loadOlder = func() { feed.normalOlder(feed.accountClient.GetTimelineLocal) } feed.startStream(feed.accountClient.NewLocalStream()) - feed.close = func() { feed.accountClient.RemoveFederatedReceiver(feed.stream) } + feed.close = func() { feed.accountClient.RemoveLocalReceiver(feed.stream) } return feed } @@ -619,7 +659,7 @@ func NewConversations(ac *api.AccountClient) *Feed { feed.loadNewer = func() { feed.normalNewer(feed.accountClient.GetConversations) } feed.loadOlder = func() { feed.normalOlder(feed.accountClient.GetConversations) } feed.startStream(feed.accountClient.NewDirectStream()) - feed.close = func() { feed.accountClient.RemoveFederatedReceiver(feed.stream) } + feed.close = func() { feed.accountClient.RemoveConversationReceiver(feed.stream) } return feed } @@ -763,8 +803,13 @@ func NewListList(ac *api.AccountClient) *Feed { loadingNewer: &LoadingLock{}, loadingOlder: &LoadingLock{}, } - - feed.loadNewer = func() { feed.normalEmpty(feed.accountClient.GetLists) } + once := true + feed.loadNewer = func() { + if once { + feed.normalEmpty(feed.accountClient.GetLists) + } + once = false + } return feed } diff --git a/main.go b/main.go index e9c43a1..b666e95 100644 --- a/main.go +++ b/main.go @@ -10,7 +10,7 @@ import ( "github.com/rivo/tview" ) -const version = "1.0.5" +const version = "1.0.6" func main() { util.MakeDirs() diff --git a/ui/commands.go b/ui/commands.go index 12fe1f3..6ee566f 100644 --- a/ui/commands.go +++ b/ui/commands.go @@ -71,6 +71,12 @@ func (tv *TutView) ListsCommand() { ) } +func (tv *TutView) TagCommand(tag string) { + tv.Timeline.AddFeed( + NewTagFeed(tv, tag), + ) +} + func (tv *TutView) BoostsCommand() { item, itemErr := tv.GetCurrentItem() if itemErr != nil { diff --git a/ui/feed.go b/ui/feed.go index ae3591f..2d7bf63 100644 --- a/ui/feed.go +++ b/ui/feed.go @@ -510,6 +510,7 @@ type FeedContent struct { func NewFeedContent(t *Tut) *FeedContent { m := NewTextView(t.Config) + m.SetWordWrap(true) if t.Config.General.MaxWidth > 0 { mw := t.Config.General.MaxWidth diff --git a/ui/input.go b/ui/input.go index 5a859f9..c797278 100644 --- a/ui/input.go +++ b/ui/input.go @@ -80,10 +80,12 @@ func (tv *TutView) InputLeaderKey(event *tcell.EventKey) *tcell.EventKey { tv.Leader.AddRune(event.Rune()) } action := config.LeaderNone + var subaction string content := tv.Leader.Content() for _, la := range tv.tut.Config.General.LeaderActions { if la.Shortcut == content { action = la.Command + subaction = la.Subaction break } } @@ -123,6 +125,9 @@ func (tv *TutView) InputLeaderKey(event *tcell.EventKey) *tcell.EventKey { tv.NotificationsCommand() case config.LeaderLists: tv.ListsCommand() + case config.LeaderTag: + tv.TagCommand(subaction) + } tv.Leader.ResetInactive() return nil diff --git a/ui/tutview.go b/ui/tutview.go index 73a2fe6..031f92f 100644 --- a/ui/tutview.go +++ b/ui/tutview.go @@ -145,10 +145,12 @@ func (tv *TutView) loggedIn(acc auth.Account) { tv.tut.App.Stop() os.Exit(1) } + filters, _ := client.GetFilters(context.Background()) ac := &api.AccountClient{ Me: me, Client: client, Streams: make(map[string]*api.Stream), + Filters: filters, } tv.tut.Client = ac