2020-10-05 03:45:34 +00:00
|
|
|
package mention
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"strings"
|
|
|
|
|
2020-12-20 05:44:26 +00:00
|
|
|
"github.com/diamondburned/arikawa/v2/discord"
|
2021-01-06 02:40:01 +00:00
|
|
|
"github.com/diamondburned/arikawa/v2/gateway"
|
2020-12-20 05:44:26 +00:00
|
|
|
"github.com/diamondburned/arikawa/v2/state/store"
|
2020-10-05 03:45:34 +00:00
|
|
|
"github.com/diamondburned/cchat-discord/internal/segments/colored"
|
|
|
|
"github.com/diamondburned/cchat-discord/internal/segments/inline"
|
|
|
|
"github.com/diamondburned/cchat-discord/internal/segments/segutil"
|
2020-12-31 02:58:36 +00:00
|
|
|
"github.com/diamondburned/cchat-discord/internal/urlutils"
|
2020-10-05 03:45:34 +00:00
|
|
|
"github.com/diamondburned/cchat/text"
|
|
|
|
"github.com/diamondburned/cchat/utils/empty"
|
2020-12-20 05:44:26 +00:00
|
|
|
"github.com/diamondburned/ningen/v2"
|
2020-10-05 03:45:34 +00:00
|
|
|
)
|
|
|
|
|
2021-01-06 02:40:01 +00:00
|
|
|
// NameSegment represents a clickable member name.
|
2020-10-05 03:45:34 +00:00
|
|
|
type NameSegment struct {
|
|
|
|
empty.TextSegment
|
|
|
|
start int
|
|
|
|
end int
|
|
|
|
um User
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ text.Segment = (*NameSegment)(nil)
|
|
|
|
|
2021-01-06 02:40:01 +00:00
|
|
|
func NewSegment(start, end int, user *User) NameSegment {
|
2020-10-05 03:45:34 +00:00
|
|
|
return NameSegment{
|
|
|
|
start: start,
|
|
|
|
end: end,
|
2021-01-06 02:40:01 +00:00
|
|
|
um: *user,
|
2020-10-05 03:45:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m NameSegment) Bounds() (start, end int) {
|
|
|
|
return m.start, m.end
|
|
|
|
}
|
|
|
|
|
2020-10-14 20:22:11 +00:00
|
|
|
func (m NameSegment) AsMentioner() text.Mentioner { return &m.um }
|
|
|
|
func (m NameSegment) AsAvatarer() text.Avatarer { return &m.um }
|
2020-10-05 03:45:34 +00:00
|
|
|
|
2020-10-14 20:22:11 +00:00
|
|
|
// AsColorer only returns User if the user actually has a colored role.
|
|
|
|
func (m NameSegment) AsColorer() text.Colorer {
|
|
|
|
if m.um.HasColor() {
|
2021-01-01 08:42:33 +00:00
|
|
|
return &m.um
|
2020-10-14 20:22:11 +00:00
|
|
|
}
|
|
|
|
return nil
|
2020-10-05 03:45:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type User struct {
|
2021-01-06 02:40:01 +00:00
|
|
|
user discord.User
|
|
|
|
guildID discord.GuildID
|
|
|
|
|
2020-12-20 05:44:26 +00:00
|
|
|
store store.Cabinet
|
2021-01-06 02:40:01 +00:00
|
|
|
ningen *ningen.State
|
|
|
|
|
|
|
|
// optional prefetching
|
2020-12-20 05:44:26 +00:00
|
|
|
|
2021-01-06 02:40:01 +00:00
|
|
|
guild *discord.Guild
|
|
|
|
member *discord.Member
|
|
|
|
presence *gateway.Presence
|
2021-01-01 08:42:33 +00:00
|
|
|
|
|
|
|
color uint32
|
|
|
|
hasColor bool
|
|
|
|
fetchedColor bool
|
2020-10-05 03:45:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
_ text.Colorer = (*User)(nil)
|
|
|
|
_ text.Avatarer = (*User)(nil)
|
|
|
|
_ text.Mentioner = (*User)(nil)
|
|
|
|
)
|
|
|
|
|
2020-12-31 02:58:36 +00:00
|
|
|
// NewUser creates a new user mention.
|
2021-01-06 02:40:01 +00:00
|
|
|
func NewUser(u discord.User) *User {
|
|
|
|
return &User{
|
|
|
|
user: u,
|
|
|
|
store: store.NoopCabinet,
|
2020-10-05 03:45:34 +00:00
|
|
|
}
|
2021-01-06 02:40:01 +00:00
|
|
|
}
|
2020-10-05 03:45:34 +00:00
|
|
|
|
2021-01-06 02:40:01 +00:00
|
|
|
// User returns the internal user.
|
|
|
|
func (um *User) User() discord.User {
|
|
|
|
return um.user
|
|
|
|
}
|
2020-10-05 03:45:34 +00:00
|
|
|
|
2021-01-06 02:40:01 +00:00
|
|
|
// UserID returns the user ID.
|
|
|
|
func (um *User) UserID() discord.UserID {
|
|
|
|
return um.user.ID
|
|
|
|
}
|
2020-10-05 03:45:34 +00:00
|
|
|
|
2021-01-06 02:40:01 +00:00
|
|
|
// SetGuildID sets the user's guild ID.
|
2021-01-06 04:53:49 +00:00
|
|
|
func (um *User) WithGuildID(guildID discord.GuildID) {
|
2021-01-06 02:40:01 +00:00
|
|
|
um.guildID = guildID
|
|
|
|
}
|
|
|
|
|
2021-01-06 04:53:49 +00:00
|
|
|
// WithGuild sets the user's guild.
|
|
|
|
func (um *User) WithGuild(guild discord.Guild) {
|
|
|
|
um.guildID = guild.ID
|
|
|
|
um.guild = &guild
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithMember sets the internal member to reduce roundtrips or cache hits. m can
|
2021-01-06 02:40:01 +00:00
|
|
|
// be nil.
|
2021-01-06 04:53:49 +00:00
|
|
|
func (um *User) WithMember(m discord.Member) {
|
|
|
|
um.member = &m
|
2021-01-06 02:40:01 +00:00
|
|
|
}
|
|
|
|
|
2021-01-06 04:53:49 +00:00
|
|
|
// WithPresence sets the internal presence to reduce roundtrips or cache hits.
|
|
|
|
func (um *User) WithPresence(p gateway.Presence) {
|
2021-01-06 02:40:01 +00:00
|
|
|
um.presence = &p
|
2020-10-05 03:45:34 +00:00
|
|
|
}
|
|
|
|
|
2021-01-06 02:40:01 +00:00
|
|
|
// WithState sets the internal state for usage.
|
2020-12-20 05:44:26 +00:00
|
|
|
func (um *User) WithState(state *ningen.State) {
|
|
|
|
um.ningen = state
|
2020-12-31 02:58:36 +00:00
|
|
|
um.store = state.Cabinet
|
2021-01-06 04:53:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Prefetch prefetches everything in User.
|
|
|
|
func (um *User) Prefetch() {
|
|
|
|
um.HasColor()
|
|
|
|
um.getPresence()
|
2021-01-06 02:40:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// DisplayName returns either the nickname or the username.
|
|
|
|
func (um *User) DisplayName() string {
|
|
|
|
if um.guildID.IsValid() {
|
|
|
|
m, err := um.store.Member(um.guildID, um.user.ID)
|
|
|
|
if err == nil && m.Nick != "" {
|
|
|
|
return m.Nick
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return um.user.Username
|
2020-12-20 05:44:26 +00:00
|
|
|
}
|
|
|
|
|
2020-10-14 20:22:11 +00:00
|
|
|
// HasColor returns true if the current user has a color.
|
2021-01-01 08:42:33 +00:00
|
|
|
func (um *User) HasColor() bool {
|
|
|
|
if um.fetchedColor {
|
|
|
|
return um.hasColor
|
|
|
|
}
|
|
|
|
|
2020-10-14 20:22:11 +00:00
|
|
|
// We don't have any member color if we have neither the member nor guild.
|
2021-01-06 02:40:01 +00:00
|
|
|
if !um.guildID.IsValid() || !um.user.ID.IsValid() {
|
2021-01-01 08:42:33 +00:00
|
|
|
um.fetchedColor = true
|
2020-10-14 20:22:11 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-01-06 02:40:01 +00:00
|
|
|
// We do have a valid GuildID, but the store might be a Noop, so we
|
|
|
|
// shouldn't mark it as fetched.
|
|
|
|
guild := um.getGuild()
|
|
|
|
member := um.getMember()
|
|
|
|
|
|
|
|
if guild == nil || member == nil {
|
2020-10-14 20:22:11 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-01-01 08:42:33 +00:00
|
|
|
um.fetchedColor = true
|
2021-01-06 02:40:01 +00:00
|
|
|
um.color, um.hasColor = MemberColor(*guild, *member)
|
2020-10-14 20:22:11 +00:00
|
|
|
|
2021-01-01 08:42:33 +00:00
|
|
|
return um.hasColor
|
|
|
|
}
|
2020-10-14 20:22:11 +00:00
|
|
|
|
2021-01-01 08:42:33 +00:00
|
|
|
func (um *User) Color() uint32 {
|
|
|
|
if um.HasColor() {
|
|
|
|
return text.SolidColor(um.color)
|
2020-10-05 03:45:34 +00:00
|
|
|
}
|
|
|
|
|
2021-01-01 08:42:33 +00:00
|
|
|
return colored.Blurple
|
2020-10-05 03:45:34 +00:00
|
|
|
}
|
|
|
|
|
2021-01-01 08:42:33 +00:00
|
|
|
func (um *User) AvatarSize() int {
|
2020-10-05 03:45:34 +00:00
|
|
|
return 96
|
|
|
|
}
|
|
|
|
|
2021-01-01 08:42:33 +00:00
|
|
|
func (um *User) AvatarText() string {
|
2021-01-06 02:40:01 +00:00
|
|
|
return um.DisplayName()
|
2020-10-05 03:45:34 +00:00
|
|
|
}
|
|
|
|
|
2021-01-01 08:42:33 +00:00
|
|
|
func (um *User) Avatar() (url string) {
|
2021-01-06 02:40:01 +00:00
|
|
|
return urlutils.AvatarURL(um.user.AvatarURL())
|
2020-10-05 03:45:34 +00:00
|
|
|
}
|
|
|
|
|
2021-01-01 08:42:33 +00:00
|
|
|
func (um *User) MentionInfo() text.Rich {
|
2020-10-05 03:45:34 +00:00
|
|
|
var content bytes.Buffer
|
|
|
|
var segment text.Rich
|
|
|
|
|
2021-01-06 02:40:01 +00:00
|
|
|
content.WriteString("Username: ")
|
|
|
|
content.WriteString(um.user.Username)
|
|
|
|
content.WriteByte('#')
|
|
|
|
content.WriteString(um.user.Discriminator)
|
|
|
|
content.WriteString("\n\n")
|
2020-10-05 03:45:34 +00:00
|
|
|
|
|
|
|
// Write extra information if any, but only if we have the guild state.
|
2021-01-06 02:40:01 +00:00
|
|
|
if um.guildID.IsValid() {
|
|
|
|
guild := um.getGuild()
|
|
|
|
member := um.getMember()
|
|
|
|
|
|
|
|
if guild != nil && member != nil {
|
|
|
|
// 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")
|
|
|
|
|
|
|
|
for _, id := range member.RoleIDs {
|
|
|
|
rl, ok := findRole(guild.Roles, id)
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prepend a new line before each item.
|
|
|
|
content.WriteByte('\n')
|
|
|
|
// Write exactly the role name, then grab the segment and color
|
|
|
|
// it.
|
|
|
|
start, end := segutil.WriteStringBuf(&content, "@"+rl.Name)
|
|
|
|
// But we only add the color if the role has one.
|
|
|
|
if rgb := rl.Color.Uint32(); rgb > 0 {
|
|
|
|
segutil.Add(&segment, colored.NewSegment(start, end, rgb))
|
|
|
|
}
|
2020-10-05 03:45:34 +00:00
|
|
|
}
|
|
|
|
|
2021-01-06 02:40:01 +00:00
|
|
|
// End section.
|
|
|
|
content.WriteString("\n\n")
|
2020-10-05 03:45:34 +00:00
|
|
|
}
|
2021-01-06 02:40:01 +00:00
|
|
|
}
|
2020-10-05 03:45:34 +00:00
|
|
|
|
2021-01-06 02:40:01 +00:00
|
|
|
// Does the user have rich presence? If so, write.
|
|
|
|
if p := um.getPresence(); p != nil {
|
|
|
|
for _, ac := range p.Activities {
|
|
|
|
formatActivity(&segment, &content, ac)
|
|
|
|
content.WriteString("\n\n")
|
|
|
|
}
|
2020-10-05 03:45:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// These information can only be obtained from the state. As such, we check
|
|
|
|
// if the state is given.
|
2020-12-20 05:44:26 +00:00
|
|
|
if um.ningen != nil {
|
2020-10-05 03:45:34 +00:00
|
|
|
// Write the user's note if any.
|
2021-01-06 02:40:01 +00:00
|
|
|
formatSectionf(&segment, &content, "Note")
|
|
|
|
content.WriteRune('\n')
|
2020-10-05 03:45:34 +00:00
|
|
|
|
2021-01-06 02:40:01 +00:00
|
|
|
if note := um.ningen.NoteState.Note(um.user.ID); note != "" {
|
2020-10-05 03:45:34 +00:00
|
|
|
start, end := segutil.WriteStringBuf(&content, note)
|
|
|
|
segutil.Add(&segment, inline.NewSegment(start, end, text.AttributeMonospace))
|
2021-01-06 02:40:01 +00:00
|
|
|
} else {
|
|
|
|
start, end := segutil.WriteStringBuf(&content, "empty")
|
|
|
|
segutil.Add(&segment, inline.NewSegment(start, end, text.AttributeDimmed))
|
2020-10-05 03:45:34 +00:00
|
|
|
}
|
2021-01-06 02:40:01 +00:00
|
|
|
|
|
|
|
content.WriteString("\n\n")
|
2020-10-05 03:45:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Assign the written content into the text segment and return it after
|
|
|
|
// trimming the trailing new line.
|
|
|
|
segment.Content = strings.TrimSuffix(content.String(), "\n")
|
|
|
|
return segment
|
|
|
|
}
|
2021-01-06 02:40:01 +00:00
|
|
|
|
|
|
|
func (um *User) getGuild() *discord.Guild {
|
|
|
|
if um.guild != nil {
|
|
|
|
return um.guild
|
|
|
|
}
|
|
|
|
|
|
|
|
g, err := um.store.Guild(um.guildID)
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
um.guild = g
|
|
|
|
return g
|
|
|
|
}
|
|
|
|
|
|
|
|
func (um *User) getMember() *discord.Member {
|
|
|
|
if !um.guildID.IsValid() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if um.member != nil {
|
|
|
|
return um.member
|
|
|
|
}
|
|
|
|
|
|
|
|
m, err := um.store.Member(um.guildID, um.user.ID)
|
|
|
|
if err != nil {
|
|
|
|
if um.ningen != nil {
|
|
|
|
um.ningen.MemberState.RequestMember(um.guildID, um.user.ID)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
um.member = m
|
|
|
|
return m
|
|
|
|
}
|
|
|
|
|
|
|
|
func (um *User) getPresence() *gateway.Presence {
|
|
|
|
if um.presence != nil {
|
|
|
|
return um.presence
|
|
|
|
}
|
|
|
|
|
|
|
|
p, err := um.store.Presence(um.guildID, um.user.ID)
|
|
|
|
if err != nil {
|
|
|
|
if um.guildID.IsValid() && um.ningen != nil {
|
|
|
|
um.ningen.MemberState.RequestMember(um.guildID, um.user.ID)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
um.presence = p
|
|
|
|
return p
|
|
|
|
}
|