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"
|
|
|
|
"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
|
|
|
)
|
|
|
|
|
|
|
|
// NameSegment represents a clickable member name; it does not implement colors.
|
|
|
|
type NameSegment struct {
|
|
|
|
empty.TextSegment
|
|
|
|
start int
|
|
|
|
end int
|
|
|
|
um User
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ text.Segment = (*NameSegment)(nil)
|
|
|
|
|
|
|
|
func UserSegment(start, end int, u discord.User) NameSegment {
|
|
|
|
return NameSegment{
|
|
|
|
start: start,
|
|
|
|
end: end,
|
|
|
|
um: User{
|
2020-12-20 05:44:26 +00:00
|
|
|
store: store.NoopCabinet,
|
2020-10-07 01:53:15 +00:00
|
|
|
Member: discord.Member{User: u},
|
2020-10-05 03:45:34 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func MemberSegment(start, end int, guild discord.Guild, m discord.Member) NameSegment {
|
|
|
|
return NameSegment{
|
|
|
|
start: start,
|
|
|
|
end: end,
|
|
|
|
um: User{
|
2020-12-20 05:44:26 +00:00
|
|
|
store: store.NoopCabinet,
|
2020-10-07 01:53:15 +00:00
|
|
|
Guild: guild,
|
|
|
|
Member: m,
|
2020-10-05 03:45:34 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithState assigns a ningen state into the given name segment. This allows the
|
|
|
|
// popovers to have additional information such as user notes.
|
|
|
|
func (m *NameSegment) WithState(state *ningen.State) {
|
2020-12-20 05:44:26 +00:00
|
|
|
m.um.WithState(state)
|
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 {
|
2020-12-20 05:44:26 +00:00
|
|
|
ningen *ningen.State
|
|
|
|
store store.Cabinet
|
|
|
|
|
2020-10-07 01:53:15 +00:00
|
|
|
Guild discord.Guild
|
|
|
|
Member discord.Member
|
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.
|
2020-12-20 05:44:26 +00:00
|
|
|
func NewUser(store store.Cabinet, guild discord.GuildID, guser discord.GuildUser) *User {
|
2020-10-05 03:45:34 +00:00
|
|
|
if guser.Member == nil {
|
2020-12-20 05:44:26 +00:00
|
|
|
m, err := store.Member(guild, guser.ID)
|
2020-10-05 03:45:34 +00:00
|
|
|
if err != nil {
|
|
|
|
guser.Member = &discord.Member{}
|
|
|
|
} else {
|
|
|
|
guser.Member = m
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
guser.Member.User = guser.User
|
|
|
|
|
|
|
|
// Get the guild for the role slice. If not, then too bad.
|
2020-12-20 05:44:26 +00:00
|
|
|
g, err := store.Guild(guild)
|
2020-10-05 03:45:34 +00:00
|
|
|
if err != nil {
|
|
|
|
g = &discord.Guild{}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &User{
|
2020-12-20 05:44:26 +00:00
|
|
|
store: store,
|
2020-10-07 01:53:15 +00:00
|
|
|
Guild: *g,
|
|
|
|
Member: *guser.Member,
|
2020-10-05 03:45:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
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.
|
|
|
|
if !um.Guild.ID.IsValid() || !um.Member.User.ID.IsValid() {
|
2021-01-01 08:42:33 +00:00
|
|
|
um.fetchedColor = true
|
2020-10-14 20:22:11 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-12-20 05:44:26 +00:00
|
|
|
g, err := um.store.Guild(um.Guild.ID)
|
2020-10-05 03:45:34 +00:00
|
|
|
if err != nil {
|
2021-01-01 08:42:33 +00:00
|
|
|
um.fetchedColor = true
|
2020-10-14 20:22:11 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-01-01 08:42:33 +00:00
|
|
|
um.fetchedColor = true
|
|
|
|
um.color, um.hasColor = MemberColor(*g, um.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 {
|
2020-10-07 01:53:15 +00:00
|
|
|
if um.Member.Nick != "" {
|
|
|
|
return um.Member.Nick
|
2020-10-05 03:45:34 +00:00
|
|
|
}
|
2020-10-07 01:53:15 +00:00
|
|
|
return um.Member.User.Username
|
2020-10-05 03:45:34 +00:00
|
|
|
}
|
|
|
|
|
2021-01-01 08:42:33 +00:00
|
|
|
func (um *User) Avatar() (url string) {
|
2020-12-31 02:58:36 +00:00
|
|
|
return urlutils.AvatarURL(um.Member.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
|
|
|
|
|
|
|
|
// Write the username if the user has a nickname.
|
2020-10-07 01:53:15 +00:00
|
|
|
if um.Member.Nick != "" {
|
2020-10-05 03:45:34 +00:00
|
|
|
content.WriteString("Username: ")
|
2020-10-07 01:53:15 +00:00
|
|
|
content.WriteString(um.Member.User.Username)
|
2020-10-05 03:45:34 +00:00
|
|
|
content.WriteByte('#')
|
2020-10-07 01:53:15 +00:00
|
|
|
content.WriteString(um.Member.User.Discriminator)
|
2020-10-05 03:45:34 +00:00
|
|
|
content.WriteString("\n\n")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write extra information if any, but only if we have the guild state.
|
2020-10-07 01:53:15 +00:00
|
|
|
if len(um.Member.RoleIDs) > 0 && um.Guild.ID.IsValid() {
|
2020-10-05 03:45:34 +00:00
|
|
|
// 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")
|
|
|
|
|
2020-10-07 01:53:15 +00:00
|
|
|
for _, id := range um.Member.RoleIDs {
|
|
|
|
rl, ok := findRole(um.Guild.Roles, id)
|
2020-10-05 03:45:34 +00:00
|
|
|
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))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// End section.
|
|
|
|
content.WriteString("\n\n")
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
// Does the user have rich presence? If so, write.
|
2020-12-20 05:44:26 +00:00
|
|
|
if p, err := um.store.Presence(um.Guild.ID, um.Member.User.ID); err == nil {
|
2020-10-05 03:45:34 +00:00
|
|
|
for _, ac := range p.Activities {
|
|
|
|
formatActivity(&segment, &content, ac)
|
|
|
|
content.WriteString("\n\n")
|
|
|
|
}
|
2020-10-07 01:53:15 +00:00
|
|
|
} else if um.Guild.ID.IsValid() {
|
2020-10-05 03:45:34 +00:00
|
|
|
// If we're still in a guild, then we can ask Discord for that
|
|
|
|
// member with their presence attached.
|
2020-12-20 05:44:26 +00:00
|
|
|
um.ningen.MemberState.RequestMember(um.Guild.ID, um.Member.User.ID)
|
2020-10-05 03:45:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Write the user's note if any.
|
2020-12-20 05:44:26 +00:00
|
|
|
if note := um.ningen.NoteState.Note(um.Member.User.ID); note != "" {
|
2020-10-05 03:45:34 +00:00
|
|
|
formatSectionf(&segment, &content, "Note")
|
|
|
|
content.WriteRune('\n')
|
|
|
|
|
|
|
|
start, end := segutil.WriteStringBuf(&content, note)
|
|
|
|
segutil.Add(&segment, inline.NewSegment(start, end, text.AttributeMonospace))
|
|
|
|
|
|
|
|
content.WriteString("\n\n")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|