Added member list support

This commit is contained in:
diamondburned 2020-08-16 16:47:47 -07:00
parent cdd9b68043
commit 9647ad0709
6 changed files with 140 additions and 73 deletions

View File

@ -110,7 +110,7 @@ func (ch *Channel) messages() ([]discord.Message, error) {
func (ch *Channel) guild() (*discord.Guild, error) { func (ch *Channel) guild() (*discord.Guild, error) {
if ch.guildID.IsValid() { 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") return nil, errors.New("channel not in a guild")
} }

View File

@ -3,18 +3,24 @@ package discord
import ( import (
"context" "context"
"fmt" "fmt"
"strconv" "strings"
"github.com/diamondburned/arikawa/discord" "github.com/diamondburned/arikawa/discord"
"github.com/diamondburned/arikawa/gateway" "github.com/diamondburned/arikawa/gateway"
"github.com/diamondburned/cchat" "github.com/diamondburned/cchat"
"github.com/diamondburned/cchat-discord/segments" "github.com/diamondburned/cchat-discord/segments"
"github.com/diamondburned/cchat-discord/urlutils"
"github.com/diamondburned/cchat/text" "github.com/diamondburned/cchat/text"
"github.com/diamondburned/ningen/states/member" "github.com/diamondburned/ningen/states/member"
) )
func seekPrevGroup(l *member.List, ix int) (item, group gateway.GuildMemberListOpItem) { func seekPrevGroup(l *member.List, ix int) (item, group gateway.GuildMemberListOpItem) {
l.ViewItems(func(items []gateway.GuildMemberListOpItem) { l.ViewItems(func(items []gateway.GuildMemberListOpItem) {
// Bound check.
if ix >= len(items) {
return
}
item = items[ix] item = items[ix]
// Search backwards. // Search backwards.
@ -40,6 +46,10 @@ func (ch *Channel) ListMembers(ctx context.Context, c cchat.MemberListContainer)
return // wat return // wat
} }
if l.GuildID() != u.GuildID || l.ID() != u.ID {
return
}
for _, ev := range u.Ops { for _, ev := range u.Ops {
switch ev.Op { switch ev.Op {
case "SYNC": case "SYNC":
@ -48,19 +58,22 @@ func (ch *Channel) ListMembers(ctx context.Context, c cchat.MemberListContainer)
case "INSERT", "UPDATE": case "INSERT", "UPDATE":
item, group := seekPrevGroup(l, ev.Index) item, group := seekPrevGroup(l, ev.Index)
if item.Member != nil && group.Group != nil { 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": case "DELETE":
_, group := seekPrevGroup(l, ev.Index-1) _, group := seekPrevGroup(l, ev.Index-1)
if group.Group != nil { if group.Group != nil && ev.Item.Member != nil {
c.RemoveMember(group.Group.ID, strconv.Itoa(ev.Index)) 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 return cancel, nil
} }
@ -71,86 +84,101 @@ func (ch *Channel) checkSync(c cchat.MemberListContainer) {
return return
} }
var sectionKeys []string ch.flushMemberGroups(l, c)
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)
})
l.ViewItems(func(items []gateway.GuildMemberListOpItem) { l.ViewItems(func(items []gateway.GuildMemberListOpItem) {
var group gateway.GuildMemberListGroup var group gateway.GuildMemberListGroup
for i, item := range items { for _, item := range items {
switch { switch {
case item.Group != nil: case item.Group != nil:
group = *item.Group group = *item.Group
case item.Member != nil: case item.Member != nil:
c.SetMember(group.ID, NewListMember(i, ch, item)) c.SetMember(group.ID, NewListMember(ch, item))
} }
} }
}) })
} }
type ListMember struct { func (ch *Channel) flushMemberGroups(l *member.List, c cchat.MemberListContainer) {
ix int // experiment with this being the index 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. // Keep stateful references to do on-demand loading.
channel *Channel channel *Channel
// constant states // constant states
userID discord.UserID userID discord.UserID
roleID discord.RoleID
origName string // use if cache is stale 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. // NewListMember creates a new list member. it.Member must not be nil.
func NewListMember(ix int, ch *Channel, it gateway.GuildMemberListOpItem) *ListMember { func NewListMember(ch *Channel, it gateway.GuildMemberListOpItem) *ListMember {
roleID, _ := discord.ParseSnowflake(it.Member.HoistedRole)
return &ListMember{ return &ListMember{
ix: ix, channel: ch,
channel: ch, userID: it.Member.User.ID,
userID: it.Member.User.ID, origName: it.Member.User.Username,
roleID: discord.RoleID(roleID),
} }
} }
func (l *ListMember) ID() string { func (l *ListMember) ID() string {
return strconv.Itoa(l.ix) return l.userID.String()
} }
func (l *ListMember) Name() text.Rich { 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 { if err != nil {
return text.Plain(l.origName) 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 { if err != nil {
return text.Plain(l.origName) return text.Plain(l.origName)
} }
return text.Rich{ var name = m.User.Username
Content: n, if m.Nick != "" {
Segments: []text.Segment{segments.NewColored(len(n), uint32(r.Color))}, 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 { func (l *ListMember) Status() cchat.UserStatus {
@ -180,16 +208,57 @@ func (l *ListMember) Secondary() text.Rich {
} }
if p.Game != nil { if p.Game != nil {
return segments.FormatActivity(*p.Game) return formatSmallActivity(*p.Game)
} }
if len(p.Activities) > 0 { if len(p.Activities) > 0 {
return segments.FormatActivity(p.Activities[0]) return formatSmallActivity(p.Activities[0])
} }
return text.Plain("") 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 { type ListSection struct {
// constant states // constant states
listID string listID string

2
go.mod
View File

@ -5,7 +5,7 @@ go 1.14
require ( require (
github.com/diamondburned/arikawa v1.1.6 github.com/diamondburned/arikawa v1.1.6
github.com/diamondburned/cchat v0.0.48 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/dustin/go-humanize v1.0.0
github.com/go-test/deep v1.0.6 github.com/go-test/deep v1.0.6
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1

8
go.sum
View File

@ -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.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 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.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 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 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= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=

View File

@ -12,10 +12,10 @@ const (
) )
type EmojiSegment struct { type EmojiSegment struct {
start int Start int
name string Name string
emojiURL string EmojiURL string
large bool Large bool
} }
var _ text.Imager = (*EmojiSegment)(nil) 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 { func (r *TextRenderer) emoji(n *md.Emoji, enter bool) ast.WalkStatus {
if enter { if enter {
r.append(EmojiSegment{ r.append(EmojiSegment{
start: r.buf.Len(), Start: r.buf.Len(),
name: n.Name, Name: n.Name,
large: n.Large, Large: n.Large,
emojiURL: n.EmojiURL() + "&size=64", 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) { func (e EmojiSegment) Bounds() (start, end int) {
return e.start, e.start return e.Start, e.Start
} }
func (e EmojiSegment) Image() string { func (e EmojiSegment) Image() string {
return e.emojiURL return e.EmojiURL
} }
// TODO: large emoji // TODO: large emoji
func (e EmojiSegment) ImageSize() (w, h int) { func (e EmojiSegment) ImageSize() (w, h int) {
if e.large { if e.Large {
return LargeEmojiSize, LargeEmojiSize return LargeEmojiSize, LargeEmojiSize
} }
return InlineEmojiSize, InlineEmojiSize return InlineEmojiSize, InlineEmojiSize
} }
func (e EmojiSegment) ImageText() string { func (e EmojiSegment) ImageText() string {
return ":" + e.name + ":" return ":" + e.Name + ":"
} }

View File

@ -312,16 +312,6 @@ func formatSectionf(segment *text.Rich, content *bytes.Buffer, f string, argv ..
segmentadd(segment, InlineSegment{start, end, text.AttrBold | text.AttrUnderline}) 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) { func formatActivity(segment *text.Rich, content *bytes.Buffer, ac discord.Activity) {
switch ac.Type { switch ac.Type {
case discord.GameActivity: case discord.GameActivity:
@ -345,10 +335,10 @@ func formatActivity(segment *text.Rich, content *bytes.Buffer, ac discord.Activi
content.WriteString(ac.Emoji.Name) content.WriteString(ac.Emoji.Name)
} else { } else {
segmentadd(segment, EmojiSegment{ segmentadd(segment, EmojiSegment{
start: content.Len(), Start: content.Len(),
name: ac.Emoji.Name, Name: ac.Emoji.Name,
emojiURL: ac.Emoji.EmojiURL() + "&size=64", EmojiURL: ac.Emoji.EmojiURL() + "&size=64",
large: ac.State == "", Large: ac.State == "",
}) })
} }