From cdd9b6804361bbd558fbf09c30242c98e311e4ba Mon Sep 17 00:00:00 2001 From: diamondburned Date: Sat, 15 Aug 2020 15:37:44 -0700 Subject: [PATCH] Added member list support --- category.go | 4 +- channel.go | 32 ++--- channel_completion.go | 8 +- channel_memberlist.go | 275 ++++++++++++++++++++++++++++++++++++++++++ go.mod | 6 +- go.sum | 18 +++ guild.go | 9 +- message.go | 18 +-- segments/embed.go | 2 +- segments/mention.go | 25 +++- service.go | 4 +- typer.go | 2 +- urlutils/urlutils.go | 2 +- 13 files changed, 356 insertions(+), 49 deletions(-) diff --git a/category.go b/category.go index febb56f..b283b71 100644 --- a/category.go +++ b/category.go @@ -10,8 +10,8 @@ import ( ) type Category struct { - id discord.Snowflake - guildID discord.Snowflake + id discord.ChannelID + guildID discord.GuildID session *Session } diff --git a/channel.go b/channel.go index 6b1ce8f..b2f5702 100644 --- a/channel.go +++ b/channel.go @@ -37,16 +37,16 @@ func filterAccessible(s *Session, chs []discord.Channel) []discord.Channel { return filtered } -func filterCategory(chs []discord.Channel, catID discord.Snowflake) []discord.Channel { +func filterCategory(chs []discord.Channel, catID discord.ChannelID) []discord.Channel { var filtered = chs[:0] - var catvalid = catID.Valid() + var catvalid = catID.IsValid() for _, ch := range chs { switch { // If the given ID is not valid, then we look for channels with // similarly invalid category IDs, because yes, Discord really sends // inconsistent responses. - case !catvalid && !ch.CategoryID.Valid(): + case !catvalid && !ch.CategoryID.IsValid(): fallthrough // Basic comparison. case ch.CategoryID == catID: @@ -60,8 +60,8 @@ func filterCategory(chs []discord.Channel, catID discord.Snowflake) []discord.Ch } type Channel struct { - id discord.Snowflake - guildID discord.Snowflake + id discord.ChannelID + guildID discord.GuildID session *Session } @@ -109,7 +109,7 @@ func (ch *Channel) messages() ([]discord.Message, error) { } func (ch *Channel) guild() (*discord.Guild, error) { - if ch.guildID.Valid() { + if ch.guildID.IsValid() { return ch.session.Guild(ch.guildID) } return nil, errors.New("channel not in a guild") @@ -134,7 +134,7 @@ func (ch *Channel) Name() text.Rich { func (ch *Channel) Nickname(ctx context.Context, labeler cchat.LabelContainer) (func(), error) { // We don't have a nickname if we're not in a guild. - if !ch.guildID.Valid() { + if !ch.guildID.IsValid() { return func() {}, nil } @@ -199,7 +199,7 @@ func (ch *Channel) JoinServer(ctx context.Context, ct cchat.MessagesContainer) ( var constructor func(discord.Message) cchat.MessageCreate - if ch.guildID.Valid() { + if ch.guildID.IsValid() { // Create the backlog without any member information. g, err := state.Guild(ch.guildID) if err != nil { @@ -295,7 +295,7 @@ func (ch *Channel) MessageEditable(id string) bool { return false } - m, err := ch.session.Store.Message(ch.id, s) + m, err := ch.session.Store.Message(ch.id, discord.MessageID(s)) if err != nil { return false } @@ -310,7 +310,7 @@ func (ch *Channel) RawMessageContent(id string) (string, error) { return "", errors.Wrap(err, "Failed to parse ID") } - m, err := ch.session.Store.Message(ch.id, s) + m, err := ch.session.Store.Message(ch.id, discord.MessageID(s)) if err != nil { return "", errors.Wrap(err, "Failed to get the message") } @@ -325,7 +325,7 @@ func (ch *Channel) EditMessage(id, content string) error { return errors.Wrap(err, "Failed to parse ID") } - _, err = ch.session.EditText(ch.id, s, content) + _, err = ch.session.EditText(ch.id, discord.MessageID(s), content) return err } @@ -343,7 +343,7 @@ func (ch *Channel) DoMessageAction(action, id string) error { switch action { case ActionDelete: - return ch.session.DeleteMessage(ch.id, s) + return ch.session.DeleteMessage(ch.id, discord.MessageID(s)) default: return ErrUnknownAction } @@ -355,7 +355,7 @@ func (ch *Channel) MessageActions(id string) []string { return nil } - m, err := ch.session.Store.Message(ch.id, s) + m, err := ch.session.Store.Message(ch.id, discord.MessageID(s)) if err != nil { return nil } @@ -384,9 +384,9 @@ func (ch *Channel) MessageActions(id string) []string { // canManageMessages returns whether or not the user is allowed to manage // messages. -func (ch *Channel) canManageMessages(userID discord.Snowflake) bool { +func (ch *Channel) canManageMessages(userID discord.UserID) bool { // If we're not in a guild, then clearly we cannot. - if !ch.guildID.Valid() { + if !ch.guildID.IsValid() { return false } @@ -438,7 +438,7 @@ func (ch *Channel) TypingSubscribe(ti cchat.TypingIndicator) (func(), error) { // muted returns if this channel is muted. This includes the channel's category // and guild. func (ch *Channel) muted() bool { - return (ch.guildID.Valid() && ch.session.MutedState.Guild(ch.guildID, false)) || + return (ch.guildID.IsValid() && ch.session.MutedState.Guild(ch.guildID, false)) || ch.session.MutedState.Channel(ch.id) || ch.session.MutedState.Category(ch.id) } diff --git a/channel_completion.go b/channel_completion.go index e70d714..84e86f4 100644 --- a/channel_completion.go +++ b/channel_completion.go @@ -40,7 +40,7 @@ func (ch *SendableChannel) completeMentions(word string) (entries []cchat.Comple // Keep track of the number of authors. // TODO: fix excess allocations - var authors = make(map[discord.Snowflake]struct{}, MaxCompletion) + var authors = make(map[discord.UserID]struct{}, MaxCompletion) for _, msg := range msgs { // If we've already added the author into the list, then skip. @@ -65,7 +65,7 @@ func (ch *SendableChannel) completeMentions(word string) (entries []cchat.Comple var match = strings.ToLower(word) // If we're not in a guild, then we can check the list of recipients. - if !ch.guildID.Valid() { + if !ch.guildID.IsValid() { c, err := ch.self() if err != nil { return @@ -127,7 +127,7 @@ func (ch *SendableChannel) completeChannels(word string) (entries []cchat.Comple } // Ignore if we're not in a guild. - if !ch.guildID.Valid() { + if !ch.guildID.IsValid() { return } @@ -144,7 +144,7 @@ func (ch *SendableChannel) completeChannels(word string) (entries []cchat.Comple } var category string - if channel.CategoryID.Valid() { + if channel.CategoryID.IsValid() { if c, _ := ch.session.Store.Channel(channel.CategoryID); c != nil { category = c.Name } diff --git a/channel_memberlist.go b/channel_memberlist.go index d9f7d22..5f3fef4 100644 --- a/channel_memberlist.go +++ b/channel_memberlist.go @@ -1 +1,276 @@ package discord + +import ( + "context" + "fmt" + "strconv" + + "github.com/diamondburned/arikawa/discord" + "github.com/diamondburned/arikawa/gateway" + "github.com/diamondburned/cchat" + "github.com/diamondburned/cchat-discord/segments" + "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) { + item = items[ix] + + // Search backwards. + for i := ix; i >= 0; i-- { + if items[i].Group != nil { + group = items[i] + return + } + } + }) + + return +} + +func (ch *Channel) ListMembers(ctx context.Context, c cchat.MemberListContainer) (func(), error) { + if !ch.guildID.IsValid() { + return func() {}, nil + } + + cancel := ch.session.AddHandler(func(u *gateway.GuildMemberListUpdate) { + l, err := ch.session.MemberState.GetMemberList(ch.guildID, ch.id) + if err != nil { + return // wat + } + + for _, ev := range u.Ops { + switch ev.Op { + case "SYNC": + ch.checkSync(c) + + 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)) + } + + case "DELETE": + _, group := seekPrevGroup(l, ev.Index-1) + if group.Group != nil { + c.RemoveMember(group.Group.ID, strconv.Itoa(ev.Index)) + } + } + } + }) + + ch.session.MemberState.RequestMemberList(ch.guildID, ch.id, 0) + return cancel, nil +} + +func (ch *Channel) checkSync(c cchat.MemberListContainer) { + l, err := ch.session.MemberState.GetMemberList(ch.guildID, ch.id) + if err != nil { + ch.session.MemberState.RequestMemberList(ch.guildID, ch.id, 0) + 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) + }) + + l.ViewItems(func(items []gateway.GuildMemberListOpItem) { + var group gateway.GuildMemberListGroup + + for i, item := range items { + switch { + case item.Group != nil: + group = *item.Group + + case item.Member != nil: + c.SetMember(group.ID, NewListMember(i, ch, item)) + } + } + }) +} + +type ListMember struct { + ix int // experiment with this being the index + + // 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) + +// 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) + + return &ListMember{ + ix: ix, + channel: ch, + userID: it.Member.User.ID, + roleID: discord.RoleID(roleID), + } +} + +func (l *ListMember) ID() string { + return strconv.Itoa(l.ix) +} + +func (l *ListMember) Name() text.Rich { + n, err := l.channel.session.MemberDisplayName(l.channel.guildID, l.userID) + if err != nil { + return text.Plain(l.origName) + } + + r, err := l.channel.session.State.Role(l.channel.guildID, l.roleID) + if err != nil { + return text.Plain(l.origName) + } + + return text.Rich{ + Content: n, + Segments: []text.Segment{segments.NewColored(len(n), uint32(r.Color))}, + } +} + +func (l *ListMember) Status() cchat.UserStatus { + p, err := l.channel.session.State.Presence(l.channel.guildID, l.userID) + if err != nil { + return cchat.UnknownStatus + } + + switch p.Status { + case discord.OnlineStatus: + return cchat.OnlineStatus + case discord.DoNotDisturbStatus: + return cchat.BusyStatus + case discord.IdleStatus: + return cchat.AwayStatus + case discord.OfflineStatus, discord.InvisibleStatus: + return cchat.OfflineStatus + default: + return cchat.UnknownStatus + } +} + +func (l *ListMember) Secondary() text.Rich { + p, err := l.channel.session.State.Presence(l.channel.guildID, l.userID) + if err != nil { + return text.Plain("") + } + + if p.Game != nil { + return segments.FormatActivity(*p.Game) + } + + if len(p.Activities) > 0 { + return segments.FormatActivity(p.Activities[0]) + } + + return text.Plain("") +} + +type ListSection struct { + // constant states + listID string + id string // roleID or online or offline + name string + total int + + channel *Channel +} + +var ( + _ cchat.MemberListSection = (*ListSection)(nil) + _ cchat.MemberListDynamicSection = (*ListSection)(nil) +) + +func NewListSection(listID string, ch *Channel, group gateway.GuildMemberListGroup) *ListSection { + var name string + + switch group.ID { + case "online": + name = "Online" + case "offline": + name = "Offline" + default: + p, err := discord.ParseSnowflake(group.ID) + if err != nil { + name = group.ID + } else { + r, err := ch.session.Role(ch.guildID, discord.RoleID(p)) + if err != nil { + name = fmt.Sprintf("<@#%s>", p.String()) + } else { + name = r.Name + } + } + } + + return &ListSection{ + listID: listID, + channel: ch, + id: group.ID, + name: name, + total: int(group.Count), + } +} + +func (s *ListSection) ID() string { + return s.id + // return fmt.Sprintf("%s-%s", s.listID, s.name) +} + +func (s *ListSection) Name() text.Rich { + return text.Rich{Content: s.name} +} + +func (s *ListSection) Total() int { + return s.total +} + +// TODO: document that Load{More,Less} works more like a shifting window. + +func (s *ListSection) LoadMore() bool { + // This variable is here purely to make lines shorter. + var memstate = s.channel.session.MemberState + + chunk := memstate.GetMemberListChunk(s.channel.guildID, s.channel.id) + if chunk < 0 { + chunk = 0 + } + + return memstate.RequestMemberList(s.channel.guildID, s.channel.id, chunk) != nil +} + +func (s *ListSection) LoadLess() bool { + var memstate = s.channel.session.MemberState + + chunk := memstate.GetMemberListChunk(s.channel.guildID, s.channel.id) + if chunk <= 0 { + return false + } + + memstate.RequestMemberList(s.channel.guildID, s.channel.id, chunk-1) + return true +} diff --git a/go.mod b/go.mod index 549491d..fd3ae3e 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,9 @@ module github.com/diamondburned/cchat-discord go 1.14 require ( - github.com/diamondburned/arikawa v0.12.4 - github.com/diamondburned/cchat v0.0.46 - github.com/diamondburned/ningen v0.1.1-0.20200717072304-e483f86c08e6 + 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/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 2d61f24..286cfcc 100644 --- a/go.sum +++ b/go.sum @@ -33,6 +33,10 @@ github.com/diamondburned/arikawa v0.10.5 h1:o5lBopooA+8cXlKZdct5qF0xztuZZ35phvQr github.com/diamondburned/arikawa v0.10.5/go.mod h1:nIhVIatzTQhPUa7NB8w4koG1RF9gYbpAr8Fj8sKq660= github.com/diamondburned/arikawa v0.12.4 h1:lhWJqcGkIIMiOYWdsoEuGlri2UbMkzMeh+VfuJPkXt4= github.com/diamondburned/arikawa v0.12.4/go.mod h1:nIhVIatzTQhPUa7NB8w4koG1RF9gYbpAr8Fj8sKq660= +github.com/diamondburned/arikawa v1.1.4 h1:+fctDxzPCF84DFL+POBQgG4CuSpSepE09mdR3Y4vNRQ= +github.com/diamondburned/arikawa v1.1.4/go.mod h1:nIhVIatzTQhPUa7NB8w4koG1RF9gYbpAr8Fj8sKq660= +github.com/diamondburned/arikawa v1.1.6 h1:Y/ioTYipS2v/NXfcAEhCnMTzrpxDjWlkjLKKcX29n6o= +github.com/diamondburned/arikawa v1.1.6/go.mod h1:nIhVIatzTQhPUa7NB8w4koG1RF9gYbpAr8Fj8sKq660= github.com/diamondburned/cchat v0.0.34 h1:BGiVxMRA9dmW3rLilIldBvjVan7eTTpaWCCfX9IKBYU= github.com/diamondburned/cchat v0.0.34/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU= github.com/diamondburned/cchat v0.0.35 h1:WiMGl8BQJgbP9E4xRxgLGlqUsHpTcJgDKDt8/6a7lBk= @@ -57,6 +61,10 @@ github.com/diamondburned/cchat v0.0.45 h1:HMVSKx1h6lh2OenWaBTvMSK531hWaXAW7I0tKZ github.com/diamondburned/cchat v0.0.45/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU= github.com/diamondburned/cchat v0.0.46 h1:fzm2XA9uGasX0uaic1AFfUMGA53PlO+GGmkYbx49A5k= github.com/diamondburned/cchat v0.0.46/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU= +github.com/diamondburned/cchat v0.0.47 h1:qU2TNeXlqru8za4qAgMPWTw6k8HGGOyic08GPPZJ6r8= +github.com/diamondburned/cchat v0.0.47/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU= +github.com/diamondburned/cchat v0.0.48 h1:MAzGzKY20JBh/LnirOZVPwbMq07xfqu4Lb4XsV9/sXQ= +github.com/diamondburned/cchat v0.0.48/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU= github.com/diamondburned/ningen v0.1.1-0.20200621014632-6babb812b249 h1:yP7kJ+xCGpDz6XbcfACJcju4SH1XDPwlrvbofz3lP8I= github.com/diamondburned/ningen v0.1.1-0.20200621014632-6babb812b249/go.mod h1:xW9hpBZsGi8KpAh10TyP+YQlYBo+Xc+2w4TR6N0951A= github.com/diamondburned/ningen v0.1.1-0.20200708085949-b64e350f3b8c h1:3h/kyk6HplYZF3zLi106itjYJWjbuMK/twijeGLEy2M= @@ -83,6 +91,16 @@ github.com/diamondburned/ningen v0.1.1-0.20200717070406-6dc482b394c0 h1:DkzeHISw github.com/diamondburned/ningen v0.1.1-0.20200717070406-6dc482b394c0/go.mod h1:Sunqp1b9Tc0+DtWKslhf83Zepgj/TELB6h8J9HZCPqQ= github.com/diamondburned/ningen v0.1.1-0.20200717072304-e483f86c08e6 h1:YN0cj0aOCa+tKmx0aD5qsbSYaIJnyrA0/+eygMKP+/w= github.com/diamondburned/ningen v0.1.1-0.20200717072304-e483f86c08e6/go.mod h1:Sunqp1b9Tc0+DtWKslhf83Zepgj/TELB6h8J9HZCPqQ= +github.com/diamondburned/ningen v0.1.1-0.20200814175234-6d546fac7724 h1:889IwdXZdIJlwlARBHZ2xxZ05Qfjd0yrVnpivyjfxTo= +github.com/diamondburned/ningen v0.1.1-0.20200814175234-6d546fac7724/go.mod h1:Lcgkgu/QfaTqnBQqYb2LlnhM9aR509uEt07y61UQNGg= +github.com/diamondburned/ningen v0.1.1-0.20200814183847-6722b606d3e1 h1:t3UPOrtdabeRqlHN+KIIht9MI6BdyCil7r5A8kz9hq4= +github.com/diamondburned/ningen v0.1.1-0.20200814183847-6722b606d3e1/go.mod h1:Lcgkgu/QfaTqnBQqYb2LlnhM9aR509uEt07y61UQNGg= +github.com/diamondburned/ningen v0.1.1-0.20200815001935-477d5b58b555 h1:NGxGSTnUWlNL8rUlU6hKxaOCbpM8Jrgwh/51pBu28UQ= +github.com/diamondburned/ningen v0.1.1-0.20200815001935-477d5b58b555/go.mod h1:Lcgkgu/QfaTqnBQqYb2LlnhM9aR509uEt07y61UQNGg= +github.com/diamondburned/ningen v0.1.1-0.20200815192814-e630cb3debe2 h1:9GZLZhVlqqTSwKX9CTf+fmj+/kpF8B1p+uYLdaUAHVI= +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/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/guild.go b/guild.go index 0da76a5..044a80a 100644 --- a/guild.go +++ b/guild.go @@ -3,6 +3,7 @@ package discord import ( "context" "sort" + "strconv" "strings" "github.com/diamondburned/arikawa/discord" @@ -44,7 +45,7 @@ func NewGuildFolder(s *Session, gf gateway.GuildFolder) *GuildFolder { } func (gf *GuildFolder) ID() string { - return gf.GuildFolder.ID.String() + return strconv.FormatInt(int64(gf.GuildFolder.ID), 10) } func (gf *GuildFolder) Name() text.Rich { @@ -80,7 +81,7 @@ func (gf *GuildFolder) Servers(container cchat.ServersContainer) error { } type Guild struct { - id discord.Snowflake + id discord.GuildID session *Session } @@ -97,7 +98,7 @@ func NewGuild(s *Session, g *discord.Guild) *Guild { } } -func NewGuildFromID(s *Session, gID discord.Snowflake) (*Guild, error) { +func NewGuildFromID(s *Session, gID discord.GuildID) (*Guild, error) { g, err := s.Guild(gID) if err != nil { return nil, err @@ -156,7 +157,7 @@ func (g *Guild) Servers(container cchat.ServersContainer) error { } // Only get top-level channels (those with category ID being null). - var toplevels = filterAccessible(g.session, filterCategory(c, discord.NullSnowflake)) + var toplevels = filterAccessible(g.session, filterCategory(c, 0)) // Sort so that positions are correct. sort.SliceStable(toplevels, func(i, j int) bool { diff --git a/message.go b/message.go index 56e3f4e..ecf7f74 100644 --- a/message.go +++ b/message.go @@ -12,10 +12,10 @@ import ( ) type messageHeader struct { - id discord.Snowflake + id discord.MessageID time discord.Timestamp - channelID discord.Snowflake - guildID discord.Snowflake + channelID discord.ChannelID + guildID discord.GuildID nonce string } @@ -29,7 +29,7 @@ func newHeader(msg discord.Message) messageHeader { guildID: msg.GuildID, nonce: msg.Nonce, } - if msg.EditedTimestamp.Valid() { + if msg.EditedTimestamp.IsValid() { h.time = msg.EditedTimestamp } return h @@ -58,7 +58,7 @@ func AvatarURL(URL string) string { } type Author struct { - id discord.Snowflake + id discord.UserID name text.Rich avatar string } @@ -198,7 +198,7 @@ func NewMessageCreate(c *gateway.MessageCreateEvent, s *Session) Message { func NewBacklogMessage(m discord.Message, s *Session, g discord.Guild) Message { // If the message doesn't have a guild, then we don't need all the // complicated member fetching process. - if !m.GuildID.Valid() { + if !m.GuildID.IsValid() { return NewMessage(m, s, NewUser(m.Author, s)) } @@ -220,7 +220,7 @@ func NewMessage(m discord.Message, s *Session, author Author) Message { var content = segments.ParseMessage(&m, s.Store) // Request members in mentions if we're in a guild. - if m.GuildID.Valid() { + if m.GuildID.IsValid() { for _, segment := range content.Segments { if mention, ok := segment.(*segments.MentionSegment); ok { // If this is not a user mention, then skip. @@ -231,7 +231,7 @@ func NewMessage(m discord.Message, s *Session, author Author) Message { // If we already have a member, then skip. We could check this // using the timestamp, as we might have a user set into the // member field - if m := mention.GuildUser.Member; m != nil && m.Joined.Valid() { + if m := mention.GuildUser.Member; m != nil && m.Joined.IsValid() { continue } @@ -249,7 +249,7 @@ func NewMessage(m discord.Message, s *Session, author Author) Message { } func (m Message) Author() cchat.MessageAuthor { - if !m.author.id.Valid() { + if !m.author.id.IsValid() { return nil } return m.author diff --git a/segments/embed.go b/segments/embed.go index 24fae7f..4b8c4a2 100644 --- a/segments/embed.go +++ b/segments/embed.go @@ -115,7 +115,7 @@ func (r *TextRenderer) renderEmbed(embed discord.Embed, m *discord.Message, s st r.ensureBreak() } - if embed.Timestamp.Valid() { + if embed.Timestamp.IsValid() { if embed.Footer != nil { r.buf.WriteString(" - ") } diff --git a/segments/mention.go b/segments/mention.go index 8ab5518..1b11a14 100644 --- a/segments/mention.go +++ b/segments/mention.go @@ -70,7 +70,7 @@ type MentionSegment struct { *md.Mention store state.Store - guild discord.Snowflake + guild discord.GuildID } var ( @@ -241,7 +241,7 @@ func userInfo(guild discord.Guild, member discord.Member, state *ningen.State) t } // Write extra information if any, but only if we have the guild state. - if len(member.RoleIDs) > 0 && guild.ID.Valid() { + if len(member.RoleIDs) > 0 && guild.ID.IsValid() { // Write a prepended new line, as role writes will always prepend a new // line. This is to prevent a trailing new line. formatSectionf(&segment, &content, "Roles") @@ -275,7 +275,7 @@ func userInfo(guild discord.Guild, member discord.Member, state *ningen.State) t formatActivity(&segment, &content, ac) content.WriteString("\n\n") } - } else if guild.ID.Valid() { + } else if guild.ID.IsValid() { // If we're still in a guild, then we can ask Discord for that // member with their presence attached. state.MemberState.RequestMember(guild.ID, member.User.ID) @@ -312,6 +312,16 @@ 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: @@ -331,7 +341,7 @@ func formatActivity(segment *text.Rich, content *bytes.Buffer, ac discord.Activi content.WriteByte('\n') if ac.Emoji != nil { - if !ac.Emoji.ID.Valid() { + if !ac.Emoji.ID.IsValid() { content.WriteString(ac.Emoji.Name) } else { segmentadd(segment, EmojiSegment{ @@ -367,7 +377,10 @@ func formatActivity(segment *text.Rich, content *bytes.Buffer, ac discord.Activi } } -func getPresence(state *ningen.State, guildID, userID discord.Snowflake) *discord.Activity { +func getPresence( + state *ningen.State, + guildID discord.GuildID, userID discord.UserID) *discord.Activity { + p, err := state.Presence(guildID, userID) if err != nil { return nil @@ -380,7 +393,7 @@ func getPresence(state *ningen.State, guildID, userID discord.Snowflake) *discor return p.Game } -func findRole(roles []discord.Role, id discord.Snowflake) (discord.Role, bool) { +func findRole(roles []discord.Role, id discord.RoleID) (discord.Role, bool) { for _, role := range roles { if role.ID == id { return role, true diff --git a/service.go b/service.go index 0b260f5..7f8a477 100644 --- a/service.go +++ b/service.go @@ -72,7 +72,7 @@ func (Authenticator) Authenticate(form []string) (cchat.Session, error) { type Session struct { *ningen.State - userID discord.Snowflake + userID discord.UserID } var ( @@ -168,7 +168,7 @@ func (s *Session) Servers(container cchat.ServersContainer) error { for _, folder := range s.Ready.Settings.GuildFolders { // TODO: correct. switch { - case folder.ID.Valid(): + case folder.ID > 0: fallthrough case len(folder.GuildIDs) > 1: toplevels = append(toplevels, NewGuildFolder(s, folder)) diff --git a/typer.go b/typer.go index 6b993e3..072f735 100644 --- a/typer.go +++ b/typer.go @@ -24,7 +24,7 @@ func NewTyperAuthor(author Author, ev *gateway.TypingStartEvent) Typer { } func NewTyper(s *Session, ev *gateway.TypingStartEvent) (*Typer, error) { - if ev.GuildID.Valid() { + if ev.GuildID.IsValid() { g, err := s.Store.Guild(ev.GuildID) if err != nil { return nil, err diff --git a/urlutils/urlutils.go b/urlutils/urlutils.go index 8a3f6da..f55d099 100644 --- a/urlutils/urlutils.go +++ b/urlutils/urlutils.go @@ -59,7 +59,7 @@ func ExtIs(URL string, exts []string) bool { } // AssetURL generates the image URL from the given asset image ID. -func AssetURL(appID discord.Snowflake, imageID string) string { +func AssetURL(appID discord.AppID, imageID string) string { if strings.HasPrefix(imageID, "spotify:") { return "https://i.scdn.co/image/" + strings.TrimPrefix(imageID, "spotify:") }