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) {
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")
}

View File

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

2
go.mod
View File

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

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.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=

View File

@ -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 + ":"
}

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})
}
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 == "",
})
}