Browse Source

View boosts, favorites, blocking etc.

pull/8/head 0.0.6
Rasmus Lindroth 6 years ago
parent
commit
c772a892b5
  1. 8
      README.md
  2. 80
      api.go
  3. 177
      feed.go
  4. 60
      main.go
  5. 4
      messagebox.go
  6. 15
      statusview.go
  7. 11
      util.go

8
README.md

@ -11,7 +11,13 @@ You can find Linux binaries under [releases](https://github.com/RasmusLindroth/t
## Currently supported commands
* `:q` `:quit` exit
* `:timeline` home, local, federated, direct, notifications
* `:tl` h, l, f, d, n (a shorter form of the former)
* `:tl` h, l, f, d, n (shorter form)
* `:blocking` lists users that you have blocked
* `:boosts` lists users that boosted the toot
* `:compose` compose a new toot
* `:favorites` lists users that favorited the toot
* `:muting` lists users that you have muted
* `:profile` go to your profile
* `:tag` followed by the hashtag e.g. `:tag linux`
* `:user` followed by a username e.g. `:user rasmus` to narrow a search include
the instance like this `:user rasmus@mastodon.acc.sunet.se`.

80
api.go

@ -17,6 +17,18 @@ const (
TimelineFederated
)
type UserListType uint
const (
UserListSearch UserListType = iota
UserListBoosts
UserListFavorites
UserListFollowers
UserListFollowing
UserListBlocking
UserListMuting
)
type API struct {
Client *mastodon.Client
}
@ -147,13 +159,13 @@ func (api *API) GetNotificationsNewer(n *mastodon.Notification) ([]*mastodon.Not
return api.Client.GetNotifications(context.Background(), pg)
}
type UserSearchData struct {
type UserData struct {
User *mastodon.Account
Relationship *mastodon.Relationship
}
func (api *API) GetUsers(s string) ([]*UserSearchData, error) {
var ud []*UserSearchData
func (api *API) GetUsers(s string) ([]*UserData, error) {
var ud []*UserData
users, err := api.Client.AccountsSearch(context.Background(), s, 10)
if err != nil {
return nil, err
@ -163,12 +175,72 @@ func (api *API) GetUsers(s string) ([]*UserSearchData, error) {
if err != nil {
return ud, err
}
ud = append(ud, &UserSearchData{User: u, Relationship: r})
ud = append(ud, &UserData{User: u, Relationship: r})
}
return ud, nil
}
func (api *API) getUserList(t UserListType, id string, pg *mastodon.Pagination) ([]*UserData, error) {
var ud []*UserData
var users []*mastodon.Account
var err error
switch t {
case UserListSearch:
users, err = api.Client.AccountsSearch(context.Background(), id, 10)
case UserListBoosts:
users, err = api.Client.GetRebloggedBy(context.Background(), mastodon.ID(id), pg)
case UserListFavorites:
users, err = api.Client.GetFavouritedBy(context.Background(), mastodon.ID(id), pg)
case UserListFollowers:
users, err = api.Client.GetAccountFollowers(context.Background(), mastodon.ID(id), pg)
case UserListFollowing:
users, err = api.Client.GetAccountFollowing(context.Background(), mastodon.ID(id), pg)
case UserListBlocking:
users, err = api.Client.GetBlocks(context.Background(), pg)
case UserListMuting:
users, err = api.Client.GetMutes(context.Background(), pg)
}
if err != nil {
return ud, err
}
for _, u := range users {
r, err := api.UserRelation(*u)
if err != nil {
return ud, err
}
ud = append(ud, &UserData{User: u, Relationship: r})
}
return ud, nil
}
func (api *API) GetUserList(t UserListType, id string) ([]*UserData, error) {
return api.getUserList(t, id, nil)
}
func (api *API) GetUserListOlder(t UserListType, id string, user *mastodon.Account) ([]*UserData, error) {
if t == UserListSearch {
return []*UserData{}, nil
}
pg := &mastodon.Pagination{
MaxID: user.ID,
}
return api.getUserList(t, id, pg)
}
func (api *API) GetUserListNewer(t UserListType, id string, user *mastodon.Account) ([]*UserData, error) {
if t == UserListSearch {
return []*UserData{}, nil
}
pg := &mastodon.Pagination{
MinID: user.ID,
}
return api.getUserList(t, id, pg)
}
func (api *API) GetUserByID(id mastodon.ID) (*mastodon.Account, error) {
a, err := api.Client.GetAccount(context.Background(), id)
return a, err

177
feed.go

@ -16,6 +16,7 @@ const (
TimelineFeedType FeedType = iota
ThreadFeedType
UserFeedType
UserListFeedType
UserSearchFeedType
NotificationFeedType
TagFeedType
@ -29,6 +30,8 @@ type Feed interface {
DrawToot()
DrawSpoiler()
RedrawControls()
GetCurrentUser() *mastodon.Account
GetCurrentStatus() *mastodon.Status
FeedType() FeedType
GetSavedIndex() int
GetDesc() string
@ -53,9 +56,9 @@ func showTootOptions(app *App, status *mastodon.Status, showSensitive bool) (str
strippedContent, urls = cleanTootHTML(status.Content)
subtleColor := fmt.Sprintf("[#%x]", app.Config.Style.Subtle.Hex())
special1 := fmt.Sprintf("[#%x]", app.Config.Style.TextSpecial1.Hex())
special2 := fmt.Sprintf("[#%x]", app.Config.Style.TextSpecial2.Hex())
subtleColor := ColorMark(app.Config.Style.Subtle)
special1 := ColorMark(app.Config.Style.TextSpecial1)
special2 := ColorMark(app.Config.Style.TextSpecial2)
if status.Sensitive {
strippedSpoiler, u = cleanTootHTML(status.SpoilerText)
@ -167,7 +170,7 @@ func showTootOptions(app *App, status *mastodon.Status, showSensitive bool) (str
if len(urls)+len(status.Mentions)+len(status.Tags) > 0 {
info = append(info, ColorKey(app.Config.Style, "", "O", "pen"))
}
info = append(info, ColorKey(app.Config.Style, "", "A", "vatar"))
if status.Account.ID == app.Me.ID {
info = append(info, ColorKey(app.Config.Style, "", "D", "elete"))
}
@ -180,9 +183,9 @@ func showUser(app *App, user *mastodon.Account, relation *mastodon.Relationship,
var text string
var controls string
n := fmt.Sprintf("[#%x]", app.Config.Style.Text.Hex())
s1 := fmt.Sprintf("[#%x]", app.Config.Style.TextSpecial1.Hex())
s2 := fmt.Sprintf("[#%x]", app.Config.Style.TextSpecial2.Hex())
n := ColorMark(app.Config.Style.Text)
s1 := ColorMark(app.Config.Style.TextSpecial1)
s2 := ColorMark(app.Config.Style.TextSpecial2)
if user.DisplayName != "" {
text = fmt.Sprintf(s2+"%s\n", user.DisplayName)
@ -227,6 +230,7 @@ func showUser(app *App, user *mastodon.Account, relation *mastodon.Relationship,
if showUserControl {
controlItems = append(controlItems, ColorKey(app.Config.Style, "", "U", "ser"))
}
controlItems = append(controlItems, ColorKey(app.Config.Style, "", "A", "vatar"))
controls = strings.Join(controlItems, " ")
return text, controls
@ -417,6 +421,16 @@ func inputSimple(app *App, event *tcell.EventKey, controls ControlItem,
return
}
func userFromStatus(s *mastodon.Status) *mastodon.Account {
if s == nil {
return nil
}
if s.Reblog != nil {
s = s.Reblog
}
return &s.Account
}
func NewTimelineFeed(app *App, tl TimelineType) *TimelineFeed {
t := &TimelineFeed{
app: app,
@ -461,7 +475,11 @@ func (t *TimelineFeed) GetCurrentStatus() *mastodon.Status {
if index >= len(t.statuses) {
return nil
}
return t.statuses[t.app.UI.StatusView.GetCurrentItem()]
return t.statuses[index]
}
func (t *TimelineFeed) GetCurrentUser() *mastodon.Account {
return userFromStatus(t.GetCurrentStatus())
}
func (t *TimelineFeed) GetFeedList() <-chan string {
@ -607,6 +625,10 @@ func (t *ThreadFeed) GetCurrentStatus() *mastodon.Status {
return t.statuses[t.app.UI.StatusView.GetCurrentItem()]
}
func (t *ThreadFeed) GetCurrentUser() *mastodon.Account {
return userFromStatus(t.GetCurrentStatus())
}
func (t *ThreadFeed) GetFeedList() <-chan string {
return drawStatusList(t.statuses)
}
@ -732,6 +754,10 @@ func (u *UserFeed) GetCurrentStatus() *mastodon.Status {
return u.statuses[index-1]
}
func (u *UserFeed) GetCurrentUser() *mastodon.Account {
return &u.user
}
func (u *UserFeed) GetFeedList() <-chan string {
ch := make(chan string)
go func() {
@ -905,6 +931,22 @@ func (n *NotificationsFeed) GetCurrentNotification() *mastodon.Notification {
return n.notifications[index]
}
func (n *NotificationsFeed) GetCurrentStatus() *mastodon.Status {
notification := n.GetCurrentNotification()
if notification == nil {
return nil
}
return notification.Status
}
func (n *NotificationsFeed) GetCurrentUser() *mastodon.Account {
notification := n.GetCurrentNotification()
if notification == nil {
return nil
}
return &notification.Account
}
func (n *NotificationsFeed) GetFeedList() <-chan string {
ch := make(chan string)
notifications := n.notifications
@ -1097,6 +1139,10 @@ func (t *TagFeed) GetCurrentStatus() *mastodon.Status {
return t.statuses[t.app.UI.StatusView.GetCurrentItem()]
}
func (t *TagFeed) GetCurrentUser() *mastodon.Account {
return userFromStatus(t.GetCurrentStatus())
}
func (t *TagFeed) GetFeedList() <-chan string {
return drawStatusList(t.statuses)
}
@ -1202,11 +1248,13 @@ func (t *TagFeed) Input(event *tcell.EventKey) {
}
}
func NewUserSearchFeed(app *App, s string) *UserSearchFeed {
u := &UserSearchFeed{
app: app,
func NewUserListFeed(app *App, t UserListType, s string) *UserListFeed {
u := &UserListFeed{
app: app,
listType: t,
input: s,
}
users, err := app.API.GetUsers(s)
users, err := app.API.GetUserList(t, s)
if err != nil {
u.app.UI.CmdBar.ShowError(fmt.Sprintf("Couldn't load users. Error: %v\n", err))
return u
@ -1215,30 +1263,60 @@ func NewUserSearchFeed(app *App, s string) *UserSearchFeed {
return u
}
type UserSearchFeed struct {
app *App
users []*UserSearchData
index int
search string
type UserListFeed struct {
app *App
users []*UserData
index int
input string
listType UserListType
}
func (u *UserListFeed) FeedType() FeedType {
return UserListFeedType
}
func (u *UserSearchFeed) FeedType() FeedType {
return UserSearchFeedType
func (u *UserListFeed) GetDesc() string {
var output string
switch u.listType {
case UserListSearch:
output = "User search: " + u.input
case UserListBoosts:
output = "Boosts"
case UserListFavorites:
output = "Favorites"
case UserListFollowers:
output = "Followers"
case UserListFollowing:
output = "Following"
case UserListBlocking:
output = "Blocking"
case UserListMuting:
output = "Muting"
}
return output
}
func (u *UserSearchFeed) GetDesc() string {
return "User search: " + u.search
func (u *UserListFeed) GetCurrentStatus() *mastodon.Status {
return nil
}
func (u *UserSearchFeed) GetCurrentUser() *UserSearchData {
func (u *UserListFeed) GetCurrentUser() *mastodon.Account {
ud := u.GetCurrentUserData()
if ud == nil {
return nil
}
return ud.User
}
func (u *UserListFeed) GetCurrentUserData() *UserData {
index := u.app.UI.app.UI.StatusView.GetCurrentItem()
if index > 0 && index-1 >= len(u.users) {
if len(u.users) == 0 || index > len(u.users)-1 {
return nil
}
return u.users[index-1]
}
func (u *UserSearchFeed) GetFeedList() <-chan string {
func (u *UserListFeed) GetFeedList() <-chan string {
ch := make(chan string)
users := u.users
go func() {
@ -1256,27 +1334,58 @@ func (u *UserSearchFeed) GetFeedList() <-chan string {
return ch
}
func (u *UserSearchFeed) LoadNewer() int {
return 0
func (u *UserListFeed) LoadNewer() int {
var users []*UserData
var err error
if len(u.users) == 0 {
users, err = u.app.API.GetUserList(u.listType, u.input)
} else {
users, err = u.app.API.GetUserListNewer(u.listType, u.input, u.users[0].User)
}
if err != nil {
u.app.UI.CmdBar.ShowError(fmt.Sprintf("Couldn't load new users. Error: %v\n", err))
return 0
}
if len(users) == 0 {
return 0
}
old := u.users
u.users = append(users, old...)
return len(users)
}
func (u *UserSearchFeed) LoadOlder() int {
return 0
func (u *UserListFeed) LoadOlder() int {
var users []*UserData
var err error
if len(u.users) == 0 {
users, err = u.app.API.GetUserList(u.listType, u.input)
} else {
users, err = u.app.API.GetUserListOlder(u.listType, u.input, u.users[len(u.users)-1].User)
}
if err != nil {
u.app.UI.CmdBar.ShowError(fmt.Sprintf("Couldn't load more users. Error: %v\n", err))
return 0
}
if len(users) == 0 {
return 0
}
u.users = append(u.users, users...)
return len(users)
}
func (u *UserSearchFeed) DrawList() {
func (u *UserListFeed) DrawList() {
u.app.UI.StatusView.SetList(u.GetFeedList())
}
func (u *UserSearchFeed) RedrawControls() {
func (u *UserListFeed) RedrawControls() {
//Does not implement
}
func (u *UserSearchFeed) DrawSpoiler() {
func (u *UserListFeed) DrawSpoiler() {
//Does not implement
}
func (u *UserSearchFeed) DrawToot() {
func (u *UserListFeed) DrawToot() {
u.index = u.app.UI.StatusView.GetCurrentItem()
index := u.index
if index > len(u.users)-1 || len(u.users) == 0 {
@ -1290,11 +1399,11 @@ func (u *UserSearchFeed) DrawToot() {
u.app.UI.StatusView.SetControls(controls)
}
func (u *UserSearchFeed) GetSavedIndex() int {
func (u *UserListFeed) GetSavedIndex() int {
return u.index
}
func (u *UserSearchFeed) Input(event *tcell.EventKey) {
func (u *UserListFeed) Input(event *tcell.EventKey) {
index := u.GetSavedIndex()
if index > len(u.users)-1 || len(u.users) == 0 {
return

60
main.go

@ -206,7 +206,7 @@ func main() {
)
app.UI.CmdBar.Input.SetAutocompleteFunc(func(currentText string) (entries []string) {
words := strings.Split(":compose,:tag,:timeline,:tl,:user,:quit,:q", ",")
words := strings.Split(":blocking,:boosts,:compose,:favorites,:muting,:profile,:tag,:timeline,:tl,:user,:quit,:q", ",")
if currentText == "" {
return
}
@ -243,6 +243,62 @@ func main() {
case ":compose":
app.UI.NewToot()
app.UI.CmdBar.ClearInput()
case ":blocking":
app.UI.StatusView.AddFeed(NewUserListFeed(app, UserListBlocking, ""))
app.UI.SetFocus(LeftPaneFocus)
app.UI.CmdBar.ClearInput()
case ":boosts":
app.UI.CmdBar.ClearInput()
status := app.UI.StatusView.GetCurrentStatus()
if status == nil {
return
}
if status.Reblog != nil {
status = status.Reblog
}
app.UI.StatusView.AddFeed(NewUserListFeed(app, UserListBoosts, string(status.ID)))
app.UI.SetFocus(LeftPaneFocus)
case ":favorites":
app.UI.CmdBar.ClearInput()
status := app.UI.StatusView.GetCurrentStatus()
if status == nil {
return
}
if status.Reblog != nil {
status = status.Reblog
}
app.UI.StatusView.AddFeed(NewUserListFeed(app, UserListFavorites, string(status.ID)))
app.UI.SetFocus(LeftPaneFocus)
/*
case ":followers":
app.UI.CmdBar.ClearInput()
user := app.UI.StatusView.GetCurrentUser()
if user == nil {
return
}
app.UI.StatusView.AddFeed(NewUserListFeed(app, UserListFollowers, string(user.ID)))
app.UI.SetFocus(LeftPaneFocus)
case ":following":
app.UI.CmdBar.ClearInput()
user := app.UI.StatusView.GetCurrentUser()
if user == nil {
return
}
app.UI.StatusView.AddFeed(NewUserListFeed(app, UserListFollowing, string(user.ID)))
app.UI.SetFocus(LeftPaneFocus)
*/
case ":muting":
app.UI.StatusView.AddFeed(NewUserListFeed(app, UserListMuting, ""))
app.UI.SetFocus(LeftPaneFocus)
app.UI.CmdBar.ClearInput()
case ":profile":
app.UI.CmdBar.ClearInput()
if app.Me == nil {
return
}
app.UI.StatusView.AddFeed(NewUserFeed(app, *app.Me))
app.UI.SetFocus(LeftPaneFocus)
case ":timeline", ":tl":
if len(parts) < 2 {
break
@ -288,7 +344,7 @@ func main() {
if len(user) == 0 {
break
}
app.UI.StatusView.AddFeed(NewUserSearchFeed(app, user))
app.UI.StatusView.AddFeed(NewUserListFeed(app, UserListSearch, user))
app.UI.SetFocus(LeftPaneFocus)
app.UI.CmdBar.ClearInput()
}

4
messagebox.go

@ -134,8 +134,8 @@ func (m *MessageBox) Draw() {
var outputHead string
var output string
subtleColor := fmt.Sprintf("[#%x]", m.app.Config.Style.Subtle.Hex())
warningColor := fmt.Sprintf("[#%x]", m.app.Config.Style.WarningText.Hex())
subtleColor := ColorMark(m.app.Config.Style.Subtle)
warningColor := ColorMark(m.app.Config.Style.WarningText)
if m.currentToot.Status != nil {
var acct string
if m.currentToot.Status.Account.DisplayName != "" {

15
statusview.go

@ -5,6 +5,7 @@ import (
"time"
"github.com/gdamore/tcell"
"github.com/mattn/go-mastodon"
"github.com/rivo/tview"
)
@ -100,6 +101,20 @@ func (t *StatusView) GetCurrentItem() int {
return t.list.GetCurrentItem()
}
func (t *StatusView) GetCurrentStatus() *mastodon.Status {
if len(t.feeds) == 0 {
return nil
}
return t.feeds[len(t.feeds)-1].GetCurrentStatus()
}
func (t *StatusView) GetCurrentUser() *mastodon.Account {
if len(t.feeds) == 0 {
return nil
}
return t.feeds[len(t.feeds)-1].GetCurrentUser()
}
func (t *StatusView) ScrollToBeginning() {
t.text.ScrollToBeginning()
}

11
util.go

@ -12,6 +12,7 @@ import (
"regexp"
"strings"
"github.com/gdamore/tcell"
"github.com/mattn/go-mastodon"
"github.com/microcosm-cc/bluemonday"
"github.com/rivo/tview"
@ -242,13 +243,17 @@ func FindFiles(s string) []string {
}
func ColorKey(style StyleConfig, pre, key, end string) string {
color := fmt.Sprintf("[#%x]", style.TextSpecial2.Hex())
normal := fmt.Sprintf("[#%x]", style.Text.Hex())
color := ColorMark(style.TextSpecial2)
normal := ColorMark(style.Text)
key = tview.Escape("[" + key + "]")
text := fmt.Sprintf("%s%s%s%s%s", pre, color, key, normal, end)
return text
}
func ColorMark(color tcell.Color) string {
return fmt.Sprintf("[#%x]", color.Hex())
}
func FormatUsername(a mastodon.Account) string {
if a.DisplayName != "" {
return fmt.Sprintf("%s (%s)", a.DisplayName, a.Acct)
@ -257,6 +262,6 @@ func FormatUsername(a mastodon.Account) string {
}
func SublteText(style StyleConfig, text string) string {
subtle := fmt.Sprintf("[#%x]", style.Subtle.Hex())
subtle := ColorMark(style.Subtle)
return fmt.Sprintf("%s%s", subtle, text)
}

Loading…
Cancel
Save