diff --git a/channel.go b/channel.go index b2f5702..36de863 100644 --- a/channel.go +++ b/channel.go @@ -110,7 +110,7 @@ func (ch *Channel) messages() ([]discord.Message, error) { func (ch *Channel) guild() (*discord.Guild, error) { if ch.guildID.IsValid() { - return ch.session.Guild(ch.guildID) + return ch.session.Store.Guild(ch.guildID) } return nil, errors.New("channel not in a guild") } diff --git a/channel_memberlist.go b/channel_memberlist.go index 5f3fef4..bbf3086 100644 --- a/channel_memberlist.go +++ b/channel_memberlist.go @@ -3,18 +3,24 @@ package discord import ( "context" "fmt" - "strconv" + "strings" "github.com/diamondburned/arikawa/discord" "github.com/diamondburned/arikawa/gateway" "github.com/diamondburned/cchat" "github.com/diamondburned/cchat-discord/segments" + "github.com/diamondburned/cchat-discord/urlutils" "github.com/diamondburned/cchat/text" "github.com/diamondburned/ningen/states/member" ) func seekPrevGroup(l *member.List, ix int) (item, group gateway.GuildMemberListOpItem) { l.ViewItems(func(items []gateway.GuildMemberListOpItem) { + // Bound check. + if ix >= len(items) { + return + } + item = items[ix] // Search backwards. @@ -40,6 +46,10 @@ func (ch *Channel) ListMembers(ctx context.Context, c cchat.MemberListContainer) return // wat } + if l.GuildID() != u.GuildID || l.ID() != u.ID { + return + } + for _, ev := range u.Ops { switch ev.Op { case "SYNC": @@ -48,19 +58,22 @@ func (ch *Channel) ListMembers(ctx context.Context, c cchat.MemberListContainer) case "INSERT", "UPDATE": item, group := seekPrevGroup(l, ev.Index) if item.Member != nil && group.Group != nil { - c.SetMember(group.Group.ID, NewListMember(ev.Index, ch, item)) + c.SetMember(group.Group.ID, NewListMember(ch, item)) + ch.flushMemberGroups(l, c) } case "DELETE": _, group := seekPrevGroup(l, ev.Index-1) - if group.Group != nil { - c.RemoveMember(group.Group.ID, strconv.Itoa(ev.Index)) + if group.Group != nil && ev.Item.Member != nil { + c.RemoveMember(group.Group.ID, ev.Item.Member.User.ID.String()) + ch.flushMemberGroups(l, c) } } } }) - ch.session.MemberState.RequestMemberList(ch.guildID, ch.id, 0) + ch.checkSync(c) + return cancel, nil } @@ -71,86 +84,101 @@ func (ch *Channel) checkSync(c cchat.MemberListContainer) { return } - var sectionKeys []string - var sectionsMap map[string]*ListSection - - l.ViewGroups(func(groups []gateway.GuildMemberListGroup) { - sectionKeys = make([]string, 0, len(groups)) - sectionsMap = make(map[string]*ListSection, len(groups)) - - for _, group := range groups { - sectionKeys = append(sectionKeys, group.ID) - sectionsMap[group.ID] = NewListSection(l.ID(), ch, group) - } - - var sections = make([]cchat.MemberListSection, len(sectionKeys)) - for i, key := range sectionKeys { - sections[i] = sectionsMap[key] - } - - c.SetSections(sections) - }) + ch.flushMemberGroups(l, c) l.ViewItems(func(items []gateway.GuildMemberListOpItem) { var group gateway.GuildMemberListGroup - for i, item := range items { + for _, item := range items { switch { case item.Group != nil: group = *item.Group case item.Member != nil: - c.SetMember(group.ID, NewListMember(i, ch, item)) + c.SetMember(group.ID, NewListMember(ch, item)) } } }) } -type ListMember struct { - ix int // experiment with this being the index +func (ch *Channel) flushMemberGroups(l *member.List, c cchat.MemberListContainer) { + l.ViewGroups(func(groups []gateway.GuildMemberListGroup) { + var sections = make([]cchat.MemberListSection, len(groups)) + for i, group := range groups { + sections[i] = NewListSection(l.ID(), ch, group) + } + c.SetSections(sections) + }) +} + +type ListMember struct { // Keep stateful references to do on-demand loading. channel *Channel // constant states userID discord.UserID - roleID discord.RoleID origName string // use if cache is stale } -var _ cchat.ListMember = (*ListMember)(nil) +var ( + _ cchat.ListMember = (*ListMember)(nil) + _ cchat.Icon = (*ListMember)(nil) +) // NewListMember creates a new list member. it.Member must not be nil. -func NewListMember(ix int, ch *Channel, it gateway.GuildMemberListOpItem) *ListMember { - roleID, _ := discord.ParseSnowflake(it.Member.HoistedRole) - +func NewListMember(ch *Channel, it gateway.GuildMemberListOpItem) *ListMember { return &ListMember{ - ix: ix, - channel: ch, - userID: it.Member.User.ID, - roleID: discord.RoleID(roleID), + channel: ch, + userID: it.Member.User.ID, + origName: it.Member.User.Username, } } func (l *ListMember) ID() string { - return strconv.Itoa(l.ix) + return l.userID.String() } func (l *ListMember) Name() text.Rich { - n, err := l.channel.session.MemberDisplayName(l.channel.guildID, l.userID) + g, err := l.channel.guild() if err != nil { return text.Plain(l.origName) } - r, err := l.channel.session.State.Role(l.channel.guildID, l.roleID) + m, err := l.channel.session.Member(l.channel.guildID, l.userID) if err != nil { return text.Plain(l.origName) } - return text.Rich{ - Content: n, - Segments: []text.Segment{segments.NewColored(len(n), uint32(r.Color))}, + var name = m.User.Username + if m.Nick != "" { + name = m.Nick } + + mention := segments.MemberSegment(0, len(name), *g, *m) + mention.WithState(l.channel.session.State) + + var txt = text.Rich{ + Content: name, + Segments: []text.Segment{mention}, + } + + if c := discord.MemberColor(*g, *m); c != discord.DefaultMemberColor { + txt.Segments = append(txt.Segments, segments.NewColored(len(name), uint32(c))) + } + + return txt +} + +func (l *ListMember) Icon(ctx context.Context, c cchat.IconContainer) (func(), error) { + m, err := l.channel.session.Member(l.channel.guildID, l.userID) + if err != nil { + return nil, err + } + + c.SetIcon(urlutils.AvatarURL(m.User.AvatarURL())) + + return func() {}, nil } func (l *ListMember) Status() cchat.UserStatus { @@ -180,16 +208,57 @@ func (l *ListMember) Secondary() text.Rich { } if p.Game != nil { - return segments.FormatActivity(*p.Game) + return formatSmallActivity(*p.Game) } if len(p.Activities) > 0 { - return segments.FormatActivity(p.Activities[0]) + return formatSmallActivity(p.Activities[0]) } return text.Plain("") } +func formatSmallActivity(ac discord.Activity) text.Rich { + switch ac.Type { + case discord.GameActivity: + return text.Plain(fmt.Sprintf("Playing %s", ac.Name)) + + case discord.ListeningActivity: + return text.Plain(fmt.Sprintf("Listening to %s", ac.Name)) + + case discord.StreamingActivity: + return text.Plain(fmt.Sprintf("Streaming on %s", ac.Name)) + + case discord.CustomActivity: + var status strings.Builder + var segmts []text.Segment + + if ac.Emoji != nil { + if !ac.Emoji.ID.IsValid() { + status.WriteString(ac.Emoji.Name) + status.WriteByte(' ') + } else { + segmts = append(segmts, segments.EmojiSegment{ + Start: status.Len(), + Name: ac.Emoji.Name, + EmojiURL: ac.Emoji.EmojiURL() + "?size=64", + Large: ac.State == "", + }) + } + } + + status.WriteString(ac.State) + + return text.Rich{ + Content: status.String(), + Segments: segmts, + } + + default: + return text.Rich{} + } +} + type ListSection struct { // constant states listID string diff --git a/go.mod b/go.mod index fd3ae3e..f769e5d 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.14 require ( github.com/diamondburned/arikawa v1.1.6 github.com/diamondburned/cchat v0.0.48 - github.com/diamondburned/ningen v0.1.1-0.20200815214034-638820c48066 + github.com/diamondburned/ningen v0.1.1-0.20200816192443-0b6a02d498d2 github.com/dustin/go-humanize v1.0.0 github.com/go-test/deep v1.0.6 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 286cfcc..ba2ced1 100644 --- a/go.sum +++ b/go.sum @@ -101,6 +101,14 @@ github.com/diamondburned/ningen v0.1.1-0.20200815192814-e630cb3debe2 h1:9GZLZhVl github.com/diamondburned/ningen v0.1.1-0.20200815192814-e630cb3debe2/go.mod h1:PIsJWdDhjgN9OiR+qrDPD8KGQ8UyFuRVrgs3Ewu6a3c= github.com/diamondburned/ningen v0.1.1-0.20200815214034-638820c48066 h1:TOLTl0zLJ+idYB7i6C4oGfmVF1Y0AFoNfVQWU3hmc4A= github.com/diamondburned/ningen v0.1.1-0.20200815214034-638820c48066/go.mod h1:PIsJWdDhjgN9OiR+qrDPD8KGQ8UyFuRVrgs3Ewu6a3c= +github.com/diamondburned/ningen v0.1.1-0.20200816035718-70361fb41b6f h1:Wzns8I0VjMrcsH7N5lqHKTRkgBt2aCwx+7wAxvSFGjU= +github.com/diamondburned/ningen v0.1.1-0.20200816035718-70361fb41b6f/go.mod h1:PIsJWdDhjgN9OiR+qrDPD8KGQ8UyFuRVrgs3Ewu6a3c= +github.com/diamondburned/ningen v0.1.1-0.20200816040753-9595e584e6bc h1:pxFVcg7J7D9hYVVReXLdJurgv4uAo1a6jNHcYpdeZsE= +github.com/diamondburned/ningen v0.1.1-0.20200816040753-9595e584e6bc/go.mod h1:PIsJWdDhjgN9OiR+qrDPD8KGQ8UyFuRVrgs3Ewu6a3c= +github.com/diamondburned/ningen v0.1.1-0.20200816040956-857988325ce0 h1:c3G8NjcS7JZvQvY549r993C0P1ltEf7sFpfRD21HFhk= +github.com/diamondburned/ningen v0.1.1-0.20200816040956-857988325ce0/go.mod h1:PIsJWdDhjgN9OiR+qrDPD8KGQ8UyFuRVrgs3Ewu6a3c= +github.com/diamondburned/ningen v0.1.1-0.20200816192443-0b6a02d498d2 h1:PodX8lfv7ZffUHUsaQUdIjZ50ONY8uCCXXUL7yzoSMQ= +github.com/diamondburned/ningen v0.1.1-0.20200816192443-0b6a02d498d2/go.mod h1:PIsJWdDhjgN9OiR+qrDPD8KGQ8UyFuRVrgs3Ewu6a3c= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= diff --git a/segments/emoji.go b/segments/emoji.go index 35c591b..c69619a 100644 --- a/segments/emoji.go +++ b/segments/emoji.go @@ -12,10 +12,10 @@ const ( ) type EmojiSegment struct { - start int - name string - emojiURL string - large bool + Start int + Name string + EmojiURL string + Large bool } var _ text.Imager = (*EmojiSegment)(nil) @@ -23,10 +23,10 @@ var _ text.Imager = (*EmojiSegment)(nil) func (r *TextRenderer) emoji(n *md.Emoji, enter bool) ast.WalkStatus { if enter { r.append(EmojiSegment{ - start: r.buf.Len(), - name: n.Name, - large: n.Large, - emojiURL: n.EmojiURL() + "&size=64", + Start: r.buf.Len(), + Name: n.Name, + Large: n.Large, + EmojiURL: n.EmojiURL() + "&size=64", }) } @@ -34,22 +34,22 @@ func (r *TextRenderer) emoji(n *md.Emoji, enter bool) ast.WalkStatus { } func (e EmojiSegment) Bounds() (start, end int) { - return e.start, e.start + return e.Start, e.Start } func (e EmojiSegment) Image() string { - return e.emojiURL + return e.EmojiURL } // TODO: large emoji func (e EmojiSegment) ImageSize() (w, h int) { - if e.large { + if e.Large { return LargeEmojiSize, LargeEmojiSize } return InlineEmojiSize, InlineEmojiSize } func (e EmojiSegment) ImageText() string { - return ":" + e.name + ":" + return ":" + e.Name + ":" } diff --git a/segments/mention.go b/segments/mention.go index 1b11a14..c5a2d71 100644 --- a/segments/mention.go +++ b/segments/mention.go @@ -312,16 +312,6 @@ func formatSectionf(segment *text.Rich, content *bytes.Buffer, f string, argv .. segmentadd(segment, InlineSegment{start, end, text.AttrBold | text.AttrUnderline}) } -func FormatActivity(ac discord.Activity) text.Rich { - var rich text.Rich - var cbuf bytes.Buffer - - formatActivity(&rich, &cbuf, ac) - - rich.Content = cbuf.String() - return rich -} - func formatActivity(segment *text.Rich, content *bytes.Buffer, ac discord.Activity) { switch ac.Type { case discord.GameActivity: @@ -345,10 +335,10 @@ func formatActivity(segment *text.Rich, content *bytes.Buffer, ac discord.Activi content.WriteString(ac.Emoji.Name) } else { segmentadd(segment, EmojiSegment{ - start: content.Len(), - name: ac.Emoji.Name, - emojiURL: ac.Emoji.EmojiURL() + "&size=64", - large: ac.State == "", + Start: content.Len(), + Name: ac.Emoji.Name, + EmojiURL: ac.Emoji.EmojiURL() + "&size=64", + Large: ac.State == "", }) }