Compare commits
4 Commits
14970d0e05
...
13f87a764b
Author | SHA1 | Date |
---|---|---|
diamondburned | 13f87a764b | |
diamondburned | 2c7b56ab6e | |
diamondburned | 46db98bfe9 | |
diamondburned | f5ac9a2422 |
4
go.mod
4
go.mod
|
@ -3,9 +3,9 @@ module github.com/diamondburned/cchat-discord
|
|||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/diamondburned/arikawa/v2 v2.0.0-20210101083335-169b36126239
|
||||
github.com/diamondburned/arikawa/v2 v2.0.0-20210106050916-771591e5eb65
|
||||
github.com/diamondburned/cchat v0.3.17
|
||||
github.com/diamondburned/ningen/v2 v2.0.0-20210101084041-d9a5058b63b5
|
||||
github.com/diamondburned/ningen/v2 v2.0.0-20210106052055-9da2a0102d49
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/go-test/deep v1.0.7
|
||||
github.com/lithammer/fuzzysearch v1.1.1
|
||||
|
|
8
go.sum
8
go.sum
|
@ -66,6 +66,10 @@ github.com/diamondburned/arikawa/v2 v2.0.0-20201227001310-f3f075b27f44/go.mod h1
|
|||
github.com/diamondburned/arikawa/v2 v2.0.0-20210101074829-c6d8c741e883/go.mod h1:e+lhS20ni2luFEU06Pc8paCxgZL99/RZb77dOC82CF0=
|
||||
github.com/diamondburned/arikawa/v2 v2.0.0-20210101083335-169b36126239 h1:ogL6/TJJecNYkvREJa+nHZ326b+QjHN/eLXMUtiyz/A=
|
||||
github.com/diamondburned/arikawa/v2 v2.0.0-20210101083335-169b36126239/go.mod h1:e+lhS20ni2luFEU06Pc8paCxgZL99/RZb77dOC82CF0=
|
||||
github.com/diamondburned/arikawa/v2 v2.0.0-20210105213913-8a213759164c h1:6n1EqFEPZbtm0pj8vtS7VzZuWvg7v04UL9hAcpK3lNk=
|
||||
github.com/diamondburned/arikawa/v2 v2.0.0-20210105213913-8a213759164c/go.mod h1:e+lhS20ni2luFEU06Pc8paCxgZL99/RZb77dOC82CF0=
|
||||
github.com/diamondburned/arikawa/v2 v2.0.0-20210106050916-771591e5eb65 h1:foJMpT+BAoASVzDj9WDxNp6/OxnWnQ/uUHk2DXARP/Y=
|
||||
github.com/diamondburned/arikawa/v2 v2.0.0-20210106050916-771591e5eb65/go.mod h1:e+lhS20ni2luFEU06Pc8paCxgZL99/RZb77dOC82CF0=
|
||||
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=
|
||||
|
@ -212,6 +216,10 @@ github.com/diamondburned/ningen/v2 v2.0.0-20201227034843-dc1d22fc28e4 h1:ptIpcyB
|
|||
github.com/diamondburned/ningen/v2 v2.0.0-20201227034843-dc1d22fc28e4/go.mod h1:zQkAo1RT4ru4HW6B5T4IRO2pee8ITzTUA2Y7XNpgjqo=
|
||||
github.com/diamondburned/ningen/v2 v2.0.0-20210101084041-d9a5058b63b5 h1:GKqBXunV2AC/owpkiaFng1wPxgxE76sQ8HEPAHGj29o=
|
||||
github.com/diamondburned/ningen/v2 v2.0.0-20210101084041-d9a5058b63b5/go.mod h1:WRQCUX/dTH4OPEy3JANLA5D6fbumzp5zk03uSUAZppA=
|
||||
github.com/diamondburned/ningen/v2 v2.0.0-20210106043942-5e3332344ab6 h1:YTvBovyUXatZbU/+gdLJPmBvisLbJkLQe6pq4BFvcUQ=
|
||||
github.com/diamondburned/ningen/v2 v2.0.0-20210106043942-5e3332344ab6/go.mod h1:WRQCUX/dTH4OPEy3JANLA5D6fbumzp5zk03uSUAZppA=
|
||||
github.com/diamondburned/ningen/v2 v2.0.0-20210106052055-9da2a0102d49 h1:wfj+fvDJLUC+xkRmVA/ZE9nmeSqFy4fbyIi3hBHgn/U=
|
||||
github.com/diamondburned/ningen/v2 v2.0.0-20210106052055-9da2a0102d49/go.mod h1:WRQCUX/dTH4OPEy3JANLA5D6fbumzp5zk03uSUAZppA=
|
||||
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=
|
||||
|
|
|
@ -178,7 +178,7 @@ var World = Commands{
|
|||
return nil, err
|
||||
}
|
||||
|
||||
m, err := ch.State.Member(ch.GuildID, user.ID())
|
||||
m, err := ch.State.Cabinet.Member(ch.GuildID, user.ID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -31,17 +31,11 @@ func (bl Backlogger) Backlog(ctx context.Context, b cchat.ID, c cchat.MessagesCo
|
|||
return errors.Wrap(err, "Failed to get messages")
|
||||
}
|
||||
|
||||
// Create the backlog without any member information.
|
||||
g, err := s.Guild(bl.GuildID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed to get guild")
|
||||
}
|
||||
|
||||
for _, m := range m {
|
||||
// Discord sucks.
|
||||
m.GuildID = bl.GuildID
|
||||
|
||||
c.CreateMessage(message.NewBacklogMessage(m, bl.State, *g))
|
||||
c.CreateMessage(message.NewBacklogMessage(m, bl.State))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -9,83 +9,57 @@ import (
|
|||
"github.com/diamondburned/arikawa/v2/gateway"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/shared"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/colored"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/emoji"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/mention"
|
||||
"github.com/diamondburned/cchat-discord/internal/urlutils"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
)
|
||||
|
||||
type Member struct {
|
||||
channel shared.Channel
|
||||
userID discord.UserID
|
||||
origName string // use if cache is stale
|
||||
mention mention.User
|
||||
presence gateway.Presence
|
||||
}
|
||||
|
||||
// New creates a new list member. it.Member must not be nil.
|
||||
func NewMember(ch shared.Channel, opItem gateway.GuildMemberListOpItem) cchat.ListMember {
|
||||
user := mention.NewUser(opItem.Member.User)
|
||||
user.WithState(ch.State.State)
|
||||
user.WithMember(opItem.Member.Member)
|
||||
user.WithGuildID(ch.GuildID)
|
||||
user.WithPresence(opItem.Member.Presence)
|
||||
user.Prefetch()
|
||||
|
||||
return &Member{
|
||||
channel: ch,
|
||||
userID: opItem.Member.User.ID,
|
||||
origName: opItem.Member.User.Username,
|
||||
presence: opItem.Member.Presence,
|
||||
mention: *user,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Member) ID() cchat.ID {
|
||||
return l.userID.String()
|
||||
return l.mention.UserID().String()
|
||||
}
|
||||
|
||||
func (l *Member) Name() text.Rich {
|
||||
g, err := l.channel.State.Cabinet.Guild(l.channel.GuildID)
|
||||
if err != nil {
|
||||
return text.Plain(l.origName)
|
||||
content := l.mention.DisplayName()
|
||||
|
||||
return text.Rich{
|
||||
Content: content,
|
||||
Segments: []text.Segment{
|
||||
mention.NewSegment(0, len(content), &l.mention),
|
||||
},
|
||||
}
|
||||
|
||||
m, err := l.channel.State.Cabinet.Member(l.channel.GuildID, l.userID)
|
||||
if err != nil {
|
||||
return text.Plain(l.origName)
|
||||
}
|
||||
|
||||
var name = m.User.Username
|
||||
if m.Nick != "" {
|
||||
name = m.Nick
|
||||
}
|
||||
|
||||
mention := mention.MemberSegment(0, len(name), *g, *m)
|
||||
mention.WithState(l.channel.State.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, colored.New(len(name), uint32(c)))
|
||||
}
|
||||
|
||||
return txt
|
||||
}
|
||||
|
||||
func (l *Member) AsIconer() cchat.Iconer { return l }
|
||||
|
||||
func (l *Member) Icon(ctx context.Context, c cchat.IconContainer) (func(), error) {
|
||||
m, err := l.channel.State.Member(l.channel.GuildID, l.userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.SetIcon(urlutils.AvatarURL(m.User.AvatarURL()))
|
||||
|
||||
c.SetIcon(l.mention.Avatar())
|
||||
return func() {}, nil
|
||||
}
|
||||
|
||||
func (l *Member) Status() cchat.Status {
|
||||
p, err := l.channel.State.Cabinet.Presence(l.channel.GuildID, l.userID)
|
||||
if err != nil {
|
||||
return cchat.StatusUnknown
|
||||
}
|
||||
|
||||
switch p.Status {
|
||||
switch l.presence.Status {
|
||||
case gateway.OnlineStatus:
|
||||
return cchat.StatusOnline
|
||||
case gateway.DoNotDisturbStatus:
|
||||
|
@ -100,16 +74,11 @@ func (l *Member) Status() cchat.Status {
|
|||
}
|
||||
|
||||
func (l *Member) Secondary() text.Rich {
|
||||
p, err := l.channel.State.Cabinet.Presence(l.channel.GuildID, l.userID)
|
||||
if err != nil {
|
||||
return text.Plain("")
|
||||
if len(l.presence.Activities) == 0 {
|
||||
return text.Rich{}
|
||||
}
|
||||
|
||||
if len(p.Activities) > 0 {
|
||||
return formatSmallActivity(p.Activities[0])
|
||||
}
|
||||
|
||||
return text.Plain("")
|
||||
return formatSmallActivity(l.presence.Activities[0])
|
||||
}
|
||||
|
||||
func formatSmallActivity(ac discord.Activity) text.Rich {
|
||||
|
|
|
@ -18,7 +18,6 @@ import (
|
|||
"github.com/diamondburned/cchat-discord/internal/discord/message"
|
||||
"github.com/diamondburned/cchat-discord/internal/funcutil"
|
||||
"github.com/diamondburned/cchat/utils/empty"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Messenger struct {
|
||||
|
@ -42,19 +41,7 @@ func (msgr *Messenger) JoinServer(ctx context.Context, ct cchat.MessagesContaine
|
|||
|
||||
var addcancel = funcutil.NewCancels()
|
||||
|
||||
var constructor func(discord.Message) cchat.MessageCreate
|
||||
|
||||
if msgr.GuildID.IsValid() {
|
||||
// Create the backlog without any member information.
|
||||
g, err := state.Guild(msgr.GuildID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Failed to get guild")
|
||||
}
|
||||
|
||||
constructor = func(m discord.Message) cchat.MessageCreate {
|
||||
return message.NewBacklogMessage(m, msgr.State, *g)
|
||||
}
|
||||
|
||||
// Subscribe to typing events.
|
||||
msgr.State.MemberState.Subscribe(msgr.GuildID)
|
||||
|
||||
|
@ -64,33 +51,16 @@ func (msgr *Messenger) JoinServer(ctx context.Context, ct cchat.MessagesContaine
|
|||
return
|
||||
}
|
||||
|
||||
messages, err := msgr.Messages()
|
||||
if err != nil {
|
||||
// TODO: log
|
||||
return
|
||||
}
|
||||
messages, _ := msgr.Messages()
|
||||
|
||||
guild, err := msgr.Guild()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Loop over all messages and replace the author. The latest
|
||||
// messages are in front.
|
||||
for _, msg := range messages {
|
||||
for _, m := range c.Members {
|
||||
if msg.Author.ID != m.User.ID {
|
||||
continue
|
||||
for _, m := range c.Members {
|
||||
for _, msg := range messages {
|
||||
if msg.Author.ID == m.User.ID {
|
||||
ct.UpdateMessage(message.NewAuthorUpdate(msg, m, msgr.State))
|
||||
}
|
||||
|
||||
ct.UpdateMessage(message.NewMessageUpdateAuthor(msg, m, *guild, msgr.State))
|
||||
}
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
constructor = func(m discord.Message) cchat.MessageCreate {
|
||||
return message.NewDirectMessage(m, msgr.State)
|
||||
}
|
||||
}
|
||||
|
||||
// Only do all this if we even have any messages.
|
||||
|
@ -101,7 +71,7 @@ func (msgr *Messenger) JoinServer(ctx context.Context, ct cchat.MessagesContaine
|
|||
|
||||
// Iterate from the earliest messages to the latest messages.
|
||||
for _, m := range m {
|
||||
ct.CreateMessage(constructor(m))
|
||||
ct.CreateMessage(message.NewBacklogMessage(m, msgr.State))
|
||||
}
|
||||
|
||||
// Mark this channel as read.
|
||||
|
@ -119,7 +89,7 @@ func (msgr *Messenger) JoinServer(ctx context.Context, ct cchat.MessagesContaine
|
|||
msgr.State.AddHandler(func(m *gateway.MessageUpdateEvent) {
|
||||
// If the updated content is empty. TODO: add embed support.
|
||||
if m.ChannelID == msgr.ID {
|
||||
ct.UpdateMessage(message.NewMessageUpdateContent(m.Message, msgr.State))
|
||||
ct.UpdateMessage(message.NewContentUpdate(m.Message, msgr.State))
|
||||
}
|
||||
}),
|
||||
msgr.State.AddHandler(func(m *gateway.MessageDeleteEvent) {
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/message"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/mention"
|
||||
"github.com/diamondburned/cchat-discord/internal/urlutils"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
)
|
||||
|
@ -40,23 +41,17 @@ func GuildMessageMentions(
|
|||
|
||||
authors[msg.Author.ID] = struct{}{}
|
||||
|
||||
var rich text.Rich
|
||||
user := mention.NewUser(msg.Author)
|
||||
user.WithGuildID(msg.GuildID)
|
||||
|
||||
if guild != nil && state != nil {
|
||||
m, err := state.Cabinet.Member(guild.ID, msg.Author.ID)
|
||||
if err == nil {
|
||||
rich = message.RenderMemberName(*m, *guild, state)
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to searching the author if member fails.
|
||||
if rich.IsEmpty() {
|
||||
rich = text.Plain(msg.Author.Username)
|
||||
user.WithState(state.State)
|
||||
user.WithGuild(*guild)
|
||||
}
|
||||
|
||||
entries = append(entries, cchat.CompletionEntry{
|
||||
Raw: msg.Author.Mention(),
|
||||
Text: rich,
|
||||
Text: message.RenderAuthorName(user),
|
||||
Secondary: text.Plain(msg.Author.Username + "#" + msg.Author.Discriminator),
|
||||
IconURL: msg.Author.AvatarURL(),
|
||||
})
|
||||
|
@ -101,7 +96,7 @@ func AllUsers(s *state.Instance, word string) []cchat.CompletionEntry {
|
|||
raw := r.User.Mention()
|
||||
|
||||
var status = gateway.UnknownStatus
|
||||
if p, _ := s.PresenceState.Presence(0, r.UserID); p != nil {
|
||||
if p, _ := s.PresenceStore.Presence(0, r.UserID); p != nil {
|
||||
status = p.Status
|
||||
}
|
||||
|
||||
|
@ -123,7 +118,7 @@ func AllUsers(s *state.Instance, word string) []cchat.CompletionEntry {
|
|||
}
|
||||
|
||||
// Search for presences.
|
||||
s.PresenceState.Each(0, func(p *gateway.Presence) bool {
|
||||
s.PresenceStore.Each(0, func(p *gateway.Presence) bool {
|
||||
// Avoid duplicates.
|
||||
if _, ok := friends[p.User.ID]; ok {
|
||||
return false
|
||||
|
@ -237,45 +232,35 @@ func (ch ChannelCompleter) CompleteMentions(word string) []cchat.CompletionEntry
|
|||
return entries
|
||||
}
|
||||
|
||||
// If we're in a guild, then we should search for (all) members.
|
||||
m, merr := ch.State.Cabinet.Members(ch.GuildID)
|
||||
g, gerr := ch.State.Cabinet.Guild(ch.GuildID)
|
||||
|
||||
if merr != nil || gerr != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If we couldn't find any members, then we can request Discord to
|
||||
// search for them.
|
||||
if len(m) == 0 {
|
||||
ch.State.MemberState.SearchMember(ch.GuildID, word)
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, mem := range m {
|
||||
rank := memberMatchString(word, &mem)
|
||||
// Prioritize searching the guild's presences because we don't need to copy
|
||||
// slices.
|
||||
ch.State.MemberStore.Each(ch.GuildID, func(m *discord.Member) (stop bool) {
|
||||
rank := memberMatchString(word, m)
|
||||
if rank == -1 {
|
||||
continue
|
||||
return false
|
||||
}
|
||||
|
||||
ensureEntriesMade(&entries)
|
||||
ensureDistancesMade(&distances)
|
||||
|
||||
raw := mem.User.Mention()
|
||||
user := mention.NewUser(m.User)
|
||||
user.WithGuildID(ch.GuildID)
|
||||
user.WithMember(*m)
|
||||
user.WithState(ch.State.State)
|
||||
|
||||
raw := m.User.Mention()
|
||||
|
||||
entries = append(entries, cchat.CompletionEntry{
|
||||
Raw: raw,
|
||||
Text: message.RenderMemberName(mem, *g, ch.State),
|
||||
Secondary: text.Plain(mem.User.Username + "#" + mem.User.Discriminator),
|
||||
IconURL: urlutils.AvatarURL(mem.User.AvatarURL()),
|
||||
Text: message.RenderAuthorName(user),
|
||||
Secondary: text.Plain(m.User.Username + "#" + m.User.Discriminator),
|
||||
IconURL: user.Avatar(),
|
||||
})
|
||||
|
||||
distances[raw] = rank
|
||||
|
||||
if len(entries) >= MaxCompletion {
|
||||
break
|
||||
}
|
||||
}
|
||||
return len(entries) >= MaxCompletion
|
||||
})
|
||||
|
||||
sortDistances(entries, distances)
|
||||
return entries
|
||||
|
|
|
@ -72,12 +72,7 @@ func (s Sender) Send(msg cchat.SendableMessage) error {
|
|||
|
||||
// CanAttach returns true if the channel can attach files.
|
||||
func (s Sender) CanAttach() bool {
|
||||
p, err := s.State.StateOnly().Permissions(s.ID, s.State.UserID)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return p.Has(discord.PermissionAttachFiles)
|
||||
return s.HasPermission(discord.PermissionAttachFiles)
|
||||
}
|
||||
|
||||
func (s Sender) AsCompleter() cchat.Completer {
|
||||
|
|
|
@ -67,7 +67,7 @@ func (ch Channel) HasPermission(perms ...discord.Permissions) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
p, err := ch.State.StateOnly().Permissions(ch.ID, ch.State.UserID)
|
||||
p, err := ch.State.Permissions(ch.ID, ch.State.UserID)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package typer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
|
@ -9,6 +8,8 @@ import (
|
|||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/message"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/mention"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Typer struct {
|
||||
|
@ -18,48 +19,47 @@ type Typer struct {
|
|||
|
||||
var _ cchat.Typer = (*Typer)(nil)
|
||||
|
||||
func NewFromAuthor(author message.Author, ev *gateway.TypingStartEvent) Typer {
|
||||
return Typer{
|
||||
Author: author,
|
||||
time: ev.Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
// New creates a new Typer that satisfies cchat.Typer.
|
||||
func New(s *state.Instance, ev *gateway.TypingStartEvent) (*Typer, error) {
|
||||
var user *mention.User
|
||||
|
||||
if ev.GuildID.IsValid() {
|
||||
g, err := s.Cabinet.Guild(ev.GuildID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ev.Member == nil {
|
||||
ev.Member, err = s.Cabinet.Member(ev.GuildID, ev.UserID)
|
||||
m, err := s.Cabinet.Member(ev.GuildID, ev.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// There's no other way we could get the user (other than to
|
||||
// check for presences), so we bail.
|
||||
return nil, errors.Wrap(err, "failed to get member")
|
||||
}
|
||||
ev.Member = m
|
||||
}
|
||||
|
||||
return &Typer{
|
||||
Author: message.NewGuildMember(*ev.Member, *g, s),
|
||||
time: ev.Timestamp,
|
||||
}, nil
|
||||
}
|
||||
user = mention.NewUser(ev.Member.User)
|
||||
user.WithMember(*ev.Member)
|
||||
} else {
|
||||
c, err := s.Cabinet.Channel(ev.ChannelID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get channel")
|
||||
}
|
||||
|
||||
c, err := s.Cabinet.Channel(ev.ChannelID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, recipient := range c.DMRecipients {
|
||||
if recipient.ID != ev.UserID {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, user := range c.DMRecipients {
|
||||
if user.ID == ev.UserID {
|
||||
return &Typer{
|
||||
Author: message.NewUser(user, s),
|
||||
time: ev.Timestamp,
|
||||
}, nil
|
||||
user = mention.NewUser(recipient)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("typer not found in state")
|
||||
user.WithGuildID(ev.GuildID)
|
||||
user.WithState(s.State)
|
||||
user.Prefetch()
|
||||
|
||||
return &Typer{
|
||||
Author: message.NewAuthor(user),
|
||||
time: ev.Timestamp,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t Typer) Time() time.Time {
|
||||
|
|
|
@ -67,7 +67,7 @@ func (gf *GuildFolder) Servers(container cchat.ServersContainer) error {
|
|||
var servers = make([]cchat.Server, 0, len(gf.GuildIDs))
|
||||
|
||||
for _, id := range gf.GuildIDs {
|
||||
g, err := gf.state.Guild(id)
|
||||
g, err := gf.state.Cabinet.Guild(id)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ func New(s *state.Instance, g *discord.Guild) cchat.Server {
|
|||
}
|
||||
|
||||
func NewFromID(s *state.Instance, gID discord.GuildID) (cchat.Server, error) {
|
||||
g, err := s.Guild(gID)
|
||||
g, err := s.Cabinet.Guild(gID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -38,11 +38,7 @@ func NewFromID(s *state.Instance, gID discord.GuildID) (cchat.Server, error) {
|
|||
return New(s, g), nil
|
||||
}
|
||||
|
||||
func (g *Guild) self(ctx context.Context) (*discord.Guild, error) {
|
||||
return g.state.WithContext(ctx).Guild(g.id)
|
||||
}
|
||||
|
||||
func (g *Guild) selfState() (*discord.Guild, error) {
|
||||
func (g *Guild) self() (*discord.Guild, error) {
|
||||
return g.state.Cabinet.Guild(g.id)
|
||||
}
|
||||
|
||||
|
@ -51,7 +47,7 @@ func (g *Guild) ID() cchat.ID {
|
|||
}
|
||||
|
||||
func (g *Guild) Name() text.Rich {
|
||||
s, err := g.selfState()
|
||||
s, err := g.self()
|
||||
if err != nil {
|
||||
// This shouldn't happen.
|
||||
return text.Rich{Content: g.id.String()}
|
||||
|
@ -63,7 +59,7 @@ func (g *Guild) Name() text.Rich {
|
|||
func (g *Guild) AsIconer() cchat.Iconer { return g }
|
||||
|
||||
func (g *Guild) Icon(ctx context.Context, iconer cchat.IconContainer) (func(), error) {
|
||||
s, err := g.self(ctx)
|
||||
s, err := g.self()
|
||||
if err != nil {
|
||||
// This shouldn't happen.
|
||||
return nil, errors.Wrap(err, "Failed to get guild")
|
||||
|
|
|
@ -3,98 +3,56 @@ package message
|
|||
import (
|
||||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/shared"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/colored"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/mention"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/reference"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/segutil"
|
||||
"github.com/diamondburned/cchat-discord/internal/urlutils"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
)
|
||||
|
||||
type Author struct {
|
||||
id discord.UserID
|
||||
name text.Rich
|
||||
avatar string
|
||||
name text.Rich
|
||||
user *mention.User // same pointer as in name
|
||||
}
|
||||
|
||||
var _ cchat.Author = (*Author)(nil)
|
||||
|
||||
func NewUser(u discord.User, s *state.Instance) Author {
|
||||
var rich text.Rich
|
||||
richUser(&rich, u, s)
|
||||
|
||||
// NewAuthor creates a new message author.
|
||||
func NewAuthor(user *mention.User) Author {
|
||||
return Author{
|
||||
id: u.ID,
|
||||
name: rich,
|
||||
avatar: urlutils.AvatarURL(u.AvatarURL()),
|
||||
name: RenderAuthorName(user),
|
||||
user: user,
|
||||
}
|
||||
}
|
||||
|
||||
func NewGuildMember(m discord.Member, g discord.Guild, s *state.Instance) Author {
|
||||
return Author{
|
||||
id: m.User.ID,
|
||||
name: RenderMemberName(m, g, s),
|
||||
avatar: urlutils.AvatarURL(m.User.AvatarURL()),
|
||||
}
|
||||
}
|
||||
|
||||
func RenderMemberName(m discord.Member, g discord.Guild, s *state.Instance) text.Rich {
|
||||
// RenderAuthorName renders the given user mention into a text segment.
|
||||
func RenderAuthorName(user *mention.User) text.Rich {
|
||||
var rich text.Rich
|
||||
richMember(&rich, m, g, s)
|
||||
richUser(&rich, user)
|
||||
return rich
|
||||
}
|
||||
|
||||
// richMember appends the member name directly into rich.
|
||||
func richMember(
|
||||
rich *text.Rich, m discord.Member, g discord.Guild, s *state.Instance) (start, end int) {
|
||||
|
||||
var displayName = m.User.Username
|
||||
if m.Nick != "" {
|
||||
displayName = m.Nick
|
||||
}
|
||||
|
||||
start, end = segutil.Write(rich, displayName)
|
||||
func richUser(rich *text.Rich, user *mention.User) (start, end int) {
|
||||
start, end = segutil.Write(rich, user.DisplayName())
|
||||
|
||||
// Append the bot prefix if the user is a bot.
|
||||
if m.User.Bot {
|
||||
if user.User().Bot {
|
||||
rich.Content += " "
|
||||
rich.Segments = append(rich.Segments,
|
||||
colored.NewBlurple(segutil.Write(rich, "[BOT]")),
|
||||
)
|
||||
}
|
||||
|
||||
// Append a clickable user popup.
|
||||
useg := mention.MemberSegment(start, end, g, m)
|
||||
useg.WithState(s.State)
|
||||
rich.Segments = append(rich.Segments, useg)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func richUser(
|
||||
rich *text.Rich, u discord.User, s *state.Instance) (start, end int) {
|
||||
|
||||
start, end = segutil.Write(rich, u.Username)
|
||||
|
||||
// Append the bot prefix if the user is a bot.
|
||||
if u.Bot {
|
||||
rich.Content += " "
|
||||
rich.Segments = append(rich.Segments,
|
||||
colored.NewBlurple(segutil.Write(rich, "[BOT]")),
|
||||
)
|
||||
}
|
||||
|
||||
// Append a clickable user popup.
|
||||
useg := mention.UserSegment(start, end, u)
|
||||
useg.WithState(s.State)
|
||||
rich.Segments = append(rich.Segments, useg)
|
||||
rich.Segments = append(rich.Segments, mention.NewSegment(start, end, user))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (a Author) ID() cchat.ID {
|
||||
return a.id.String()
|
||||
return a.user.UserID().String()
|
||||
}
|
||||
|
||||
func (a Author) Name() text.Rich {
|
||||
|
@ -102,60 +60,54 @@ func (a Author) Name() text.Rich {
|
|||
}
|
||||
|
||||
func (a Author) Avatar() string {
|
||||
return a.avatar
|
||||
return a.user.Avatar()
|
||||
}
|
||||
|
||||
const authorReplyingTo = " replying to "
|
||||
|
||||
// AddUserReply modifies Author to make it appear like it's a message reply.
|
||||
// Specifically, this function is used for direct messages.
|
||||
// Specifically, this function is used for direct messages in virtual channels.
|
||||
func (a *Author) AddUserReply(user discord.User, s *state.Instance) {
|
||||
a.name.Content += authorReplyingTo
|
||||
richUser(&a.name, user, s)
|
||||
|
||||
userMention := mention.NewUser(user)
|
||||
userMention.WithState(s.State)
|
||||
userMention.Prefetch()
|
||||
|
||||
richUser(&a.name, userMention)
|
||||
}
|
||||
|
||||
func (a *Author) AddReply(name string) {
|
||||
a.name.Content += authorReplyingTo + name
|
||||
}
|
||||
// AddChannelReply adds a reply pointing to a channel. If the given channel is a
|
||||
// direct message channel, then the first recipient will be used instead, and
|
||||
// the function will operate similar to AddUserReply.
|
||||
func (a *Author) AddChannelReply(ch discord.Channel, s *state.Instance) {
|
||||
if ch.Type == discord.DirectMessage && len(ch.DMRecipients) > 0 {
|
||||
a.AddUserReply(ch.DMRecipients[0], s)
|
||||
return
|
||||
}
|
||||
|
||||
// // AddMemberReply modifies Author to make it appear like it's a message reply.
|
||||
// // Specifically, this function is used for guild messages.
|
||||
// func (a *Author) AddMemberReply(m discord.Member, g discord.Guild, s *state.Instance) {
|
||||
// a.name.Content += authorReplyingTo
|
||||
// richMember(&a.name, m, g, s)
|
||||
// }
|
||||
|
||||
func (a *Author) addAuthorReference(msgref discord.Message, s *state.Instance) {
|
||||
a.name.Content += authorReplyingTo
|
||||
start, end := richUser(&a.name, msgref.Author, s)
|
||||
start, end := segutil.Write(&a.name, shared.ChannelName(ch))
|
||||
|
||||
a.name.Segments = append(a.name.Segments,
|
||||
reference.NewMessageSegment(start, end, msgref.ID),
|
||||
mention.Segment{
|
||||
Start: start,
|
||||
End: end,
|
||||
Channel: mention.NewChannel(ch),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// AddMessageReference adds a message reference to the author.
|
||||
func (a *Author) AddMessageReference(ref discord.Message, s *state.Instance) {
|
||||
if !ref.GuildID.IsValid() {
|
||||
a.addAuthorReference(ref, s)
|
||||
return
|
||||
}
|
||||
|
||||
g, err := s.Cabinet.Guild(ref.GuildID)
|
||||
if err != nil {
|
||||
a.addAuthorReference(ref, s)
|
||||
return
|
||||
}
|
||||
|
||||
m, err := s.Cabinet.Member(g.ID, ref.Author.ID)
|
||||
if err != nil {
|
||||
a.addAuthorReference(ref, s)
|
||||
s.MemberState.RequestMember(g.ID, ref.Author.ID)
|
||||
return
|
||||
}
|
||||
|
||||
a.name.Content += authorReplyingTo
|
||||
start, end := richMember(&a.name, *m, *g, s)
|
||||
|
||||
userMention := mention.NewUser(ref.Author)
|
||||
userMention.WithGuildID(ref.GuildID)
|
||||
userMention.WithState(s.State)
|
||||
userMention.Prefetch()
|
||||
|
||||
start, end := richUser(&a.name, userMention)
|
||||
|
||||
a.name.Segments = append(a.name.Segments,
|
||||
reference.NewMessageSegment(start, end, ref.ID),
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package message
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -9,8 +10,10 @@ import (
|
|||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/inline"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/mention"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/reference"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/segutil"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
)
|
||||
|
||||
|
@ -79,37 +82,6 @@ var (
|
|||
_ cchat.Noncer = (*Message)(nil)
|
||||
)
|
||||
|
||||
func NewMessageUpdateContent(msg discord.Message, s *state.Instance) Message {
|
||||
// Check if content is empty.
|
||||
if msg.Content == "" {
|
||||
// Then grab the content from the state.
|
||||
m, err := s.Cabinet.Message(msg.ChannelID, msg.ID)
|
||||
if err == nil {
|
||||
msg.Content = m.Content
|
||||
}
|
||||
}
|
||||
|
||||
var content = segments.ParseMessage(&msg, s.Cabinet)
|
||||
return Message{
|
||||
messageHeader: newHeader(msg),
|
||||
content: content,
|
||||
}
|
||||
}
|
||||
|
||||
func NewMessageUpdateAuthor(
|
||||
msg discord.Message, member discord.Member, g discord.Guild, s *state.Instance) Message {
|
||||
|
||||
author := NewGuildMember(member, g, s)
|
||||
if ref := ReferencedMessage(msg, s, true); ref != nil {
|
||||
author.AddMessageReference(*ref, s)
|
||||
}
|
||||
|
||||
return Message{
|
||||
messageHeader: newHeader(msg),
|
||||
author: NewGuildMember(member, g, s),
|
||||
}
|
||||
}
|
||||
|
||||
// NewGuildMessageCreate uses the session to create a message. It does not do
|
||||
// API calls. Member is optional. This is the only call that populates the Nonce
|
||||
// in the header.
|
||||
|
@ -118,96 +90,221 @@ func NewGuildMessageCreate(c *gateway.MessageCreateEvent, s *state.Instance) Mes
|
|||
message := c.Message
|
||||
message.Nonce = s.Nonces.Load(c.Nonce)
|
||||
|
||||
// This should not error.
|
||||
g, err := s.Cabinet.Guild(c.GuildID)
|
||||
if err != nil {
|
||||
return NewMessage(message, s, NewUser(c.Author, s))
|
||||
user := mention.NewUser(c.Author)
|
||||
user.WithState(s.State)
|
||||
user.WithGuildID(c.GuildID)
|
||||
|
||||
if c.Member != nil {
|
||||
user.WithMember(*c.Member)
|
||||
}
|
||||
|
||||
if c.Member == nil {
|
||||
c.Member, _ = s.Cabinet.Member(c.GuildID, c.Author.ID)
|
||||
}
|
||||
if c.Member == nil {
|
||||
s.MemberState.RequestMember(c.GuildID, c.Author.ID)
|
||||
return NewMessage(message, s, NewUser(c.Author, s))
|
||||
}
|
||||
user.Prefetch()
|
||||
|
||||
return NewMessage(message, s, NewGuildMember(*c.Member, *g, s))
|
||||
return NewMessage(message, s, NewAuthor(user))
|
||||
}
|
||||
|
||||
// NewBacklogMessage uses the session to create a message fetched from the
|
||||
// backlog. It takes in an existing guild and tries to fetch a new member, if
|
||||
// it's nil.
|
||||
func NewBacklogMessage(m discord.Message, s *state.Instance, g discord.Guild) Message {
|
||||
func NewBacklogMessage(m discord.Message, s *state.Instance) Message {
|
||||
// If the message doesn't have a guild, then we don't need all the
|
||||
// complicated member fetching process.
|
||||
if !m.GuildID.IsValid() {
|
||||
return NewMessage(m, s, NewUser(m.Author, s))
|
||||
return NewDirectMessage(m, s)
|
||||
}
|
||||
|
||||
mem, err := s.Cabinet.Member(m.GuildID, m.Author.ID)
|
||||
if err != nil {
|
||||
s.MemberState.RequestMember(m.GuildID, m.Author.ID)
|
||||
return NewMessage(m, s, NewUser(m.Author, s))
|
||||
}
|
||||
user := mention.NewUser(m.Author)
|
||||
user.WithGuildID(m.GuildID)
|
||||
user.WithState(s.State)
|
||||
user.Prefetch()
|
||||
|
||||
return NewMessage(m, s, NewGuildMember(*mem, g, s))
|
||||
return NewMessage(m, s, NewAuthor(user))
|
||||
}
|
||||
|
||||
// NewDirectMessage creates a new direct message.
|
||||
func NewDirectMessage(m discord.Message, s *state.Instance) Message {
|
||||
return NewMessage(m, s, NewUser(m.Author, s))
|
||||
user := mention.NewUser(m.Author)
|
||||
user.WithState(s.State)
|
||||
user.Prefetch()
|
||||
|
||||
return NewMessage(m, s, NewAuthor(user))
|
||||
}
|
||||
|
||||
func NewMessage(m discord.Message, s *state.Instance, author Author) Message {
|
||||
var content text.Rich
|
||||
|
||||
if ref := ReferencedMessage(m, s, true); ref != nil {
|
||||
// TODO: markup support
|
||||
var refmsg = "> " + ref.Content
|
||||
if len(refmsg) > 120 {
|
||||
refmsg = refmsg[:120] + "..."
|
||||
}
|
||||
|
||||
content.Content = strings.ReplaceAll(refmsg, "\n", " ") + "\n"
|
||||
content.Segments = []text.Segment{
|
||||
reference.NewMessageSegment(0, len(content.Content), ref.ID),
|
||||
}
|
||||
// NewAuthorUpdate creates a new message that contains a new author.
|
||||
func NewAuthorUpdate(msg discord.Message, m discord.Member, s *state.Instance) Message {
|
||||
user := mention.NewUser(msg.Author)
|
||||
user.WithState(s.State)
|
||||
user.WithGuildID(msg.GuildID)
|
||||
user.WithMember(m)
|
||||
|
||||
author := NewAuthor(user)
|
||||
if ref := ReferencedMessage(msg, s, true); ref != nil {
|
||||
author.AddMessageReference(*ref, s)
|
||||
}
|
||||
|
||||
// Render the message content.
|
||||
segments.ParseMessageRich(&content, &m, s.Cabinet)
|
||||
return Message{
|
||||
messageHeader: newHeader(msg),
|
||||
author: author,
|
||||
}
|
||||
}
|
||||
|
||||
// Request members in mentions if we're in a guild.
|
||||
if m.GuildID.IsValid() {
|
||||
for _, segment := range content.Segments {
|
||||
mention, ok := segment.(*mention.Segment)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// If this is not a user mention, then skip. 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 mention.User == nil || mention.User.Member.Joined.IsValid() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Request the member.
|
||||
s.MemberState.RequestMember(m.GuildID, mention.User.Member.User.ID)
|
||||
// NewContentUpdate creates a new message that does not have an author. It
|
||||
// should be used for UpdateMessage only.
|
||||
func NewContentUpdate(msg discord.Message, s *state.Instance) Message {
|
||||
// Check if content is empty.
|
||||
if msg.Content == "" {
|
||||
// Then grab the content from the state.
|
||||
m, err := s.Cabinet.Message(msg.ChannelID, msg.ID)
|
||||
if err == nil {
|
||||
msg = *m
|
||||
}
|
||||
}
|
||||
|
||||
return newMessageContent(&msg, s)
|
||||
}
|
||||
|
||||
// NewMessage creates a new message from the given author. It may modify author
|
||||
// to add a message reference.
|
||||
func NewMessage(m discord.Message, s *state.Instance, author Author) Message {
|
||||
message := newMessageContent(&m, s)
|
||||
message.author = author
|
||||
|
||||
if m.ReferencedMessage != nil {
|
||||
message.author.AddMessageReference(*m.ReferencedMessage, s)
|
||||
}
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
// newMessageContent creates a new message with a content only. The given
|
||||
// message will have its ReferencedMessage field validated and filled if
|
||||
// available.
|
||||
func newMessageContent(m *discord.Message, s *state.Instance) Message {
|
||||
// Ensure the validity of ReferencedMessage.
|
||||
m.ReferencedMessage = ReferencedMessage(*m, s, true)
|
||||
|
||||
var content text.Rich
|
||||
|
||||
switch m.Type {
|
||||
case discord.ChannelPinnedMessage:
|
||||
writeSegmented(&content, "Pinned ", "a message", " to this channel.",
|
||||
func(i, j int) text.Segment {
|
||||
if m.ReferencedMessage == nil {
|
||||
return nil
|
||||
}
|
||||
return reference.NewMessageSegment(i, j, m.ReferencedMessage.ID)
|
||||
},
|
||||
)
|
||||
|
||||
case discord.GuildMemberJoinMessage:
|
||||
content.Content = "Joined the server."
|
||||
|
||||
case discord.CallMessage:
|
||||
content.Content = "Calling you."
|
||||
|
||||
case discord.ChannelIconChangeMessage:
|
||||
content.Content = "Changed the channel icon."
|
||||
|
||||
case discord.ChannelNameChangeMessage:
|
||||
writeSegmented(&content, "Changed the channel name to ", m.Content, ".",
|
||||
func(i, j int) text.Segment {
|
||||
return mention.Segment{
|
||||
Start: i,
|
||||
End: j,
|
||||
Channel: mention.NewChannelFromID(s.State, m.ChannelID),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
case discord.RecipientAddMessage:
|
||||
if len(m.Mentions) == 0 {
|
||||
content.Content = "Added recipient to the group."
|
||||
break
|
||||
}
|
||||
|
||||
writeSegmented(&content,
|
||||
"Added ", m.Mentions[0].Username, " to the group.",
|
||||
segmentFuncFromMention(*m, s),
|
||||
)
|
||||
|
||||
case discord.RecipientRemoveMessage:
|
||||
if len(m.Mentions) == 0 {
|
||||
content.Content = "Removed recipient from the group."
|
||||
break
|
||||
}
|
||||
|
||||
writeSegmented(&content,
|
||||
"Removed ", m.Mentions[0].Username, " from the group.",
|
||||
segmentFuncFromMention(*m, s),
|
||||
)
|
||||
|
||||
case discord.NitroBoostMessage:
|
||||
content.Content = "Boosted the server."
|
||||
case discord.NitroTier1Message:
|
||||
content.Content = "The server is now Nitro Boosted to Tier 1."
|
||||
case discord.NitroTier2Message:
|
||||
content.Content = "The server is now Nitro Boosted to Tier 2."
|
||||
case discord.NitroTier3Message:
|
||||
content.Content = "The server is now Nitro Boosted to Tier 3."
|
||||
|
||||
case discord.ChannelFollowAddMessage:
|
||||
log.Printf("[Discord] Unknown message type: %#v\n", m)
|
||||
content.Content = "Type = discord.ChannelFollowAddMessage"
|
||||
|
||||
case discord.GuildDiscoveryDisqualifiedMessage:
|
||||
log.Printf("[Discord] Unknown message type: %#v\n", m)
|
||||
content.Content = "Type = discord.GuildDiscoveryDisqualifiedMessage"
|
||||
|
||||
case discord.GuildDiscoveryRequalifiedMessage:
|
||||
log.Printf("[Discord] Unknown message type: %#v\n", m)
|
||||
content.Content = "Type = discord.GuildDiscoveryRequalifiedMessage"
|
||||
|
||||
case discord.ApplicationCommandMessage:
|
||||
fallthrough
|
||||
case discord.InlinedReplyMessage:
|
||||
fallthrough
|
||||
case discord.DefaultMessage:
|
||||
fallthrough
|
||||
default:
|
||||
return newRegularContent(*m, s)
|
||||
}
|
||||
|
||||
segutil.Add(&content, inline.NewSegment(
|
||||
0, len(content.Content),
|
||||
text.AttributeDimmed|text.AttributeItalics,
|
||||
))
|
||||
|
||||
return Message{
|
||||
messageHeader: newHeaderNonce(*m, m.Nonce),
|
||||
content: content,
|
||||
}
|
||||
}
|
||||
|
||||
func newRegularContent(m discord.Message, s *state.Instance) Message {
|
||||
var content text.Rich
|
||||
|
||||
if m.ReferencedMessage != nil {
|
||||
refContent := []byte(m.ReferencedMessage.Content)
|
||||
segments.ParseWithMessageRich(&content, refContent, &m, s.Cabinet)
|
||||
|
||||
content = segments.Ellipsize(content, 100)
|
||||
content.Content += "\n"
|
||||
|
||||
segutil.Add(&content,
|
||||
reference.NewMessageSegment(0, len(content.Content)-1, m.ReferencedMessage.ID),
|
||||
)
|
||||
}
|
||||
|
||||
segments.ParseMessageRich(&content, &m, s.Cabinet)
|
||||
|
||||
return Message{
|
||||
messageHeader: newHeaderNonce(m, m.Nonce),
|
||||
author: author,
|
||||
content: content,
|
||||
}
|
||||
}
|
||||
|
||||
func (m Message) Author() cchat.Author {
|
||||
if !m.author.id.IsValid() {
|
||||
if m.author.user == nil {
|
||||
return nil
|
||||
}
|
||||
return m.author
|
||||
|
@ -255,3 +352,42 @@ func ReferencedMessage(m discord.Message, s *state.Instance, wait bool) (reply *
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
// segmentFuncFromMention returns a function that gets the message's first
|
||||
// mention and returns a segment created from it. It returns nil if the message
|
||||
// does not have any mentions.
|
||||
func segmentFuncFromMention(m discord.Message, s *state.Instance) func(i, j int) text.Segment {
|
||||
return func(i, j int) text.Segment {
|
||||
if len(m.Mentions) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
firstMention := m.Mentions[0]
|
||||
|
||||
user := mention.NewUser(firstMention.User)
|
||||
user.WithGuildID(m.GuildID)
|
||||
user.WithState(s.State)
|
||||
|
||||
if firstMention.Member != nil {
|
||||
user.WithMember(*firstMention.Member)
|
||||
}
|
||||
|
||||
user.Prefetch()
|
||||
|
||||
return mention.NewSegment(i, j, user)
|
||||
}
|
||||
}
|
||||
|
||||
func writeSegmented(rich *text.Rich, start, mid, end string, f func(i, j int) text.Segment) {
|
||||
var builder strings.Builder
|
||||
|
||||
builder.WriteString(start)
|
||||
i, j := segutil.WriteStringBuilder(&builder, start)
|
||||
builder.WriteString(end)
|
||||
|
||||
rich.Content = builder.String()
|
||||
|
||||
if seg := f(i, j); seg != nil {
|
||||
rich.Segments = append(rich.Segments, f(i, j))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,11 +8,11 @@ import (
|
|||
"github.com/diamondburned/arikawa/v2/gateway"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/message/send/complete"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/shared"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/message"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state/nonce"
|
||||
"github.com/diamondburned/cchat-discord/internal/funcutil"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/mention"
|
||||
"github.com/diamondburned/cchat/utils/empty"
|
||||
)
|
||||
|
||||
|
@ -167,16 +167,14 @@ func (msgs *Messages) JoinServer(ctx context.Context, ct cchat.MessagesContainer
|
|||
isReply = true
|
||||
}
|
||||
|
||||
var author = message.NewUser(msg.Author, msgs.state)
|
||||
user := mention.NewUser(msg.Author)
|
||||
user.WithState(msgs.state.State)
|
||||
|
||||
var author = message.NewAuthor(user)
|
||||
if isReply {
|
||||
c, err := msgs.state.Channel(msg.ChannelID)
|
||||
if err == nil {
|
||||
switch c.Type {
|
||||
case discord.DirectMessage:
|
||||
author.AddUserReply(c.DMRecipients[0], msgs.state)
|
||||
case discord.GroupDM:
|
||||
author.AddReply(shared.ChannelName(*c))
|
||||
}
|
||||
author.AddChannelReply(*c, msgs.state)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -188,7 +186,7 @@ func (msgs *Messages) JoinServer(ctx context.Context, ct cchat.MessagesContainer
|
|||
return
|
||||
}
|
||||
|
||||
ct.UpdateMessage(message.NewMessageUpdateContent(update.Message, msgs.state))
|
||||
ct.UpdateMessage(message.NewContentUpdate(update.Message, msgs.state))
|
||||
}),
|
||||
msgs.state.AddHandler(func(del *gateway.MessageDeleteEvent) {
|
||||
if del.GuildID.IsValid() || msgs.acList.isActive(del.ChannelID) {
|
||||
|
|
|
@ -88,6 +88,13 @@ func New(s *state.State) (*Instance, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
// Permissions queries for the permission without hitting the REST API.
|
||||
func (s *Instance) Permissions(
|
||||
chID discord.ChannelID, uID discord.UserID) (discord.Permissions, error) {
|
||||
|
||||
return s.StateOnly().Permissions(chID, uID)
|
||||
}
|
||||
|
||||
// StateOnly returns a shallow copy of *State with an already-expired context.
|
||||
func (s *Instance) StateOnly() *state.State {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
|
|
@ -62,6 +62,18 @@ func DimSuffix(prefix, suffix string) text.Rich {
|
|||
}
|
||||
}
|
||||
|
||||
func Write(rich *text.Rich, content string, attr text.Attribute) {
|
||||
start := len(rich.Content)
|
||||
rich.Content += content
|
||||
end := len(rich.Content)
|
||||
|
||||
rich.Segments = append(rich.Segments, Segment{
|
||||
start: start,
|
||||
end: end,
|
||||
attributes: Attribute(attr),
|
||||
})
|
||||
}
|
||||
|
||||
type Segment struct {
|
||||
empty.TextSegment
|
||||
start, end int
|
||||
|
|
|
@ -25,9 +25,13 @@ func ParseMessage(m *discord.Message, s store.Cabinet) text.Rich {
|
|||
|
||||
func ParseMessageRich(rich *text.Rich, m *discord.Message, s store.Cabinet) {
|
||||
var content = []byte(m.Content)
|
||||
if len(content) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var node = md.ParseWithMessage(content, s, m, true)
|
||||
|
||||
r := renderer.New(content, node)
|
||||
r := renderer.New(content)
|
||||
r.Buffer.Grow(len(rich.Content))
|
||||
r.Buffer.WriteString(rich.Content)
|
||||
|
||||
|
@ -43,12 +47,80 @@ func ParseMessageRich(rich *text.Rich, m *discord.Message, s store.Cabinet) {
|
|||
rich.Segments = append(rich.Segments, r.Segments...)
|
||||
}
|
||||
|
||||
func ParseWithMessage(b []byte, m *discord.Message, s store.Cabinet, msg bool) text.Rich {
|
||||
node := md.ParseWithMessage(b, s, m, msg)
|
||||
return renderer.RenderNode(b, node)
|
||||
func ParseWithMessage(b []byte, m *discord.Message, s store.Cabinet) text.Rich {
|
||||
var rich text.Rich
|
||||
ParseWithMessageRich(&rich, b, m, s)
|
||||
return rich
|
||||
}
|
||||
|
||||
func ParseWithMessageRich(b []byte, m *discord.Message, s store.Cabinet, msg bool) text.Rich {
|
||||
node := md.ParseWithMessage(b, s, m, msg)
|
||||
return renderer.RenderNode(b, node)
|
||||
func ParseWithMessageRich(rich *text.Rich, b []byte, m *discord.Message, s store.Cabinet) {
|
||||
if len(b) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
node := md.ParseWithMessage(b, s, m, true)
|
||||
|
||||
r := renderer.New(b)
|
||||
r.Buffer.Grow(len(rich.Content))
|
||||
r.Buffer.WriteString(rich.Content)
|
||||
|
||||
r.WithState(m, s)
|
||||
r.Walk(node)
|
||||
|
||||
rich.Content = r.String()
|
||||
rich.Segments = append(rich.Segments, r.Segments...)
|
||||
}
|
||||
|
||||
// Ellipsize caps the length of the rendered text segment to be not longer than
|
||||
// the given length. The ellipsize will be appended if it is.
|
||||
func Ellipsize(rich text.Rich, maxLen int) text.Rich {
|
||||
if maxLen > len(rich.Content) {
|
||||
maxLen = len(rich.Content) - 1
|
||||
if maxLen <= 0 {
|
||||
return text.Rich{}
|
||||
}
|
||||
|
||||
rich.Content += "…"
|
||||
}
|
||||
|
||||
return Substring(rich, 0, maxLen)
|
||||
}
|
||||
|
||||
// Substring slices the given rich text.
|
||||
func Substring(rich text.Rich, start, end int) text.Rich {
|
||||
substring := text.Rich{
|
||||
Content: rich.Content[start:end],
|
||||
Segments: make([]text.Segment, 0, len(rich.Segments)),
|
||||
}
|
||||
|
||||
for _, seg := range rich.Segments {
|
||||
i, j := seg.Bounds()
|
||||
|
||||
// Bound-check: check if the starting point is within the range.
|
||||
if start <= i && i <= end {
|
||||
// If the current segment is cleanly within the bound, then we can
|
||||
// directly insert it.
|
||||
if j <= end {
|
||||
substring.Segments = append(substring.Segments, seg)
|
||||
continue
|
||||
}
|
||||
|
||||
substring.Segments = append(substring.Segments, trimmedSegment{
|
||||
Segment: seg,
|
||||
start: i, // preserve the segment's starting point
|
||||
end: end,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return substring
|
||||
}
|
||||
|
||||
type trimmedSegment struct {
|
||||
text.Segment
|
||||
start, end int
|
||||
}
|
||||
|
||||
func (seg trimmedSegment) Bounds() (int, int) {
|
||||
return seg.start, seg.end
|
||||
}
|
||||
|
|
|
@ -4,12 +4,27 @@ import (
|
|||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/renderer"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
"github.com/diamondburned/ningen/v2"
|
||||
"github.com/diamondburned/ningen/v2/md"
|
||||
)
|
||||
|
||||
type Channel struct {
|
||||
discord.Channel
|
||||
}
|
||||
|
||||
func NewChannelFromID(s *ningen.State, chID discord.ChannelID) *Channel {
|
||||
ch, err := s.Channel(chID)
|
||||
if err != nil {
|
||||
return &Channel{
|
||||
Channel: discord.Channel{ID: chID, Name: "unknown channel"},
|
||||
}
|
||||
}
|
||||
|
||||
return &Channel{
|
||||
Channel: *ch,
|
||||
}
|
||||
}
|
||||
|
||||
func NewChannel(ch discord.Channel) *Channel {
|
||||
return &Channel{
|
||||
Channel: ch,
|
||||
|
@ -26,5 +41,13 @@ func (ch *Channel) MentionInfo() text.Rich {
|
|||
return text.Rich{}
|
||||
}
|
||||
|
||||
return renderer.Parse([]byte(topic))
|
||||
bytes := []byte(topic)
|
||||
|
||||
r := renderer.New(bytes)
|
||||
r.Walk(md.Parse(bytes))
|
||||
|
||||
return text.Rich{
|
||||
Content: r.String(),
|
||||
Segments: r.Segments,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,12 +23,21 @@ func mention(r *renderer.Text, node ast.Node, enter bool) ast.WalkStatus {
|
|||
case n.Channel != nil:
|
||||
seg.Start, seg.End = r.WriteString("#" + n.Channel.Name)
|
||||
seg.Channel = NewChannel(*n.Channel)
|
||||
|
||||
case n.GuildUser != nil:
|
||||
seg.Start, seg.End = r.WriteString("@" + n.GuildUser.Username)
|
||||
seg.User = NewUser(r.Store, r.Message.GuildID, *n.GuildUser)
|
||||
seg.User = NewUser(n.GuildUser.User)
|
||||
seg.User.store = r.Store
|
||||
seg.User.WithGuildID(r.Message.GuildID)
|
||||
if n.GuildUser.Member != nil {
|
||||
seg.User.WithMember(*n.GuildUser.Member)
|
||||
}
|
||||
seg.User.Prefetch()
|
||||
|
||||
case n.GuildRole != nil:
|
||||
seg.Start, seg.End = r.WriteString("@" + n.GuildRole.Name)
|
||||
seg.Role = NewRole(*n.GuildRole)
|
||||
|
||||
default:
|
||||
// Unexpected error; skip.
|
||||
return ast.WalkSkipChildren
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/arikawa/v2/gateway"
|
||||
"github.com/diamondburned/arikawa/v2/state/store"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/colored"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/inline"
|
||||
|
@ -15,7 +16,7 @@ import (
|
|||
"github.com/diamondburned/ningen/v2"
|
||||
)
|
||||
|
||||
// NameSegment represents a clickable member name; it does not implement colors.
|
||||
// NameSegment represents a clickable member name.
|
||||
type NameSegment struct {
|
||||
empty.TextSegment
|
||||
start int
|
||||
|
@ -25,35 +26,14 @@ type NameSegment struct {
|
|||
|
||||
var _ text.Segment = (*NameSegment)(nil)
|
||||
|
||||
func UserSegment(start, end int, u discord.User) NameSegment {
|
||||
func NewSegment(start, end int, user *User) NameSegment {
|
||||
return NameSegment{
|
||||
start: start,
|
||||
end: end,
|
||||
um: User{
|
||||
store: store.NoopCabinet,
|
||||
Member: discord.Member{User: u},
|
||||
},
|
||||
um: *user,
|
||||
}
|
||||
}
|
||||
|
||||
func MemberSegment(start, end int, guild discord.Guild, m discord.Member) NameSegment {
|
||||
return NameSegment{
|
||||
start: start,
|
||||
end: end,
|
||||
um: User{
|
||||
store: store.NoopCabinet,
|
||||
Guild: guild,
|
||||
Member: m,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
m.um.WithState(state)
|
||||
}
|
||||
|
||||
func (m NameSegment) Bounds() (start, end int) {
|
||||
return m.start, m.end
|
||||
}
|
||||
|
@ -70,11 +50,17 @@ func (m NameSegment) AsColorer() text.Colorer {
|
|||
}
|
||||
|
||||
type User struct {
|
||||
ningen *ningen.State
|
||||
store store.Cabinet
|
||||
user discord.User
|
||||
guildID discord.GuildID
|
||||
|
||||
Guild discord.Guild
|
||||
Member discord.Member
|
||||
store store.Cabinet
|
||||
ningen *ningen.State
|
||||
|
||||
// optional prefetching
|
||||
|
||||
guild *discord.Guild
|
||||
member *discord.Member
|
||||
presence *gateway.Presence
|
||||
|
||||
color uint32
|
||||
hasColor bool
|
||||
|
@ -88,36 +74,69 @@ var (
|
|||
)
|
||||
|
||||
// NewUser creates a new user mention.
|
||||
func NewUser(store store.Cabinet, guild discord.GuildID, guser discord.GuildUser) *User {
|
||||
if guser.Member == nil {
|
||||
m, err := store.Member(guild, guser.ID)
|
||||
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.
|
||||
g, err := store.Guild(guild)
|
||||
if err != nil {
|
||||
g = &discord.Guild{}
|
||||
}
|
||||
|
||||
func NewUser(u discord.User) *User {
|
||||
return &User{
|
||||
store: store,
|
||||
Guild: *g,
|
||||
Member: *guser.Member,
|
||||
user: u,
|
||||
store: store.NoopCabinet,
|
||||
}
|
||||
}
|
||||
|
||||
// User returns the internal user.
|
||||
func (um *User) User() discord.User {
|
||||
return um.user
|
||||
}
|
||||
|
||||
// UserID returns the user ID.
|
||||
func (um *User) UserID() discord.UserID {
|
||||
return um.user.ID
|
||||
}
|
||||
|
||||
// SetGuildID sets the user's guild ID.
|
||||
func (um *User) WithGuildID(guildID discord.GuildID) {
|
||||
um.guildID = guildID
|
||||
}
|
||||
|
||||
// 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
|
||||
// be nil.
|
||||
func (um *User) WithMember(m discord.Member) {
|
||||
um.member = &m
|
||||
}
|
||||
|
||||
// WithPresence sets the internal presence to reduce roundtrips or cache hits.
|
||||
func (um *User) WithPresence(p gateway.Presence) {
|
||||
um.presence = &p
|
||||
}
|
||||
|
||||
// WithState sets the internal state for usage.
|
||||
func (um *User) WithState(state *ningen.State) {
|
||||
um.ningen = state
|
||||
um.store = state.Cabinet
|
||||
}
|
||||
|
||||
// Prefetch prefetches everything in User.
|
||||
func (um *User) Prefetch() {
|
||||
um.HasColor()
|
||||
um.getPresence()
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// HasColor returns true if the current user has a color.
|
||||
func (um *User) HasColor() bool {
|
||||
if um.fetchedColor {
|
||||
|
@ -125,19 +144,22 @@ func (um *User) HasColor() bool {
|
|||
}
|
||||
|
||||
// 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() {
|
||||
if !um.guildID.IsValid() || !um.user.ID.IsValid() {
|
||||
um.fetchedColor = true
|
||||
return false
|
||||
}
|
||||
|
||||
g, err := um.store.Guild(um.Guild.ID)
|
||||
if err != nil {
|
||||
um.fetchedColor = true
|
||||
// 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 {
|
||||
return false
|
||||
}
|
||||
|
||||
um.fetchedColor = true
|
||||
um.color, um.hasColor = MemberColor(*g, um.Member)
|
||||
um.color, um.hasColor = MemberColor(*guild, *member)
|
||||
|
||||
return um.hasColor
|
||||
}
|
||||
|
@ -155,80 +177,79 @@ func (um *User) AvatarSize() int {
|
|||
}
|
||||
|
||||
func (um *User) AvatarText() string {
|
||||
if um.Member.Nick != "" {
|
||||
return um.Member.Nick
|
||||
}
|
||||
return um.Member.User.Username
|
||||
return um.DisplayName()
|
||||
}
|
||||
|
||||
func (um *User) Avatar() (url string) {
|
||||
return urlutils.AvatarURL(um.Member.User.AvatarURL())
|
||||
return urlutils.AvatarURL(um.user.AvatarURL())
|
||||
}
|
||||
|
||||
func (um *User) MentionInfo() text.Rich {
|
||||
var content bytes.Buffer
|
||||
var segment text.Rich
|
||||
|
||||
// Write the username if the user has a nickname.
|
||||
if um.Member.Nick != "" {
|
||||
content.WriteString("Username: ")
|
||||
content.WriteString(um.Member.User.Username)
|
||||
content.WriteByte('#')
|
||||
content.WriteString(um.Member.User.Discriminator)
|
||||
content.WriteString("\n\n")
|
||||
}
|
||||
content.WriteString("Username: ")
|
||||
content.WriteString(um.user.Username)
|
||||
content.WriteByte('#')
|
||||
content.WriteString(um.user.Discriminator)
|
||||
content.WriteString("\n\n")
|
||||
|
||||
// Write extra information if any, but only if we have the guild state.
|
||||
if len(um.Member.RoleIDs) > 0 && um.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")
|
||||
if um.guildID.IsValid() {
|
||||
guild := um.getGuild()
|
||||
member := um.getMember()
|
||||
|
||||
for _, id := range um.Member.RoleIDs {
|
||||
rl, ok := findRole(um.Guild.Roles, id)
|
||||
if !ok {
|
||||
continue
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
|
||||
// End section.
|
||||
content.WriteString("\n\n")
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
|
||||
// These information can only be obtained from the state. As such, we check
|
||||
// if the state is given.
|
||||
if um.ningen != nil {
|
||||
// Does the user have rich presence? If so, write.
|
||||
if p, err := um.store.Presence(um.Guild.ID, um.Member.User.ID); err == nil {
|
||||
for _, ac := range p.Activities {
|
||||
formatActivity(&segment, &content, ac)
|
||||
content.WriteString("\n\n")
|
||||
}
|
||||
} else if um.Guild.ID.IsValid() {
|
||||
// If we're still in a guild, then we can ask Discord for that
|
||||
// member with their presence attached.
|
||||
um.ningen.MemberState.RequestMember(um.Guild.ID, um.Member.User.ID)
|
||||
}
|
||||
|
||||
// Write the user's note if any.
|
||||
if note := um.ningen.NoteState.Note(um.Member.User.ID); note != "" {
|
||||
formatSectionf(&segment, &content, "Note")
|
||||
content.WriteRune('\n')
|
||||
formatSectionf(&segment, &content, "Note")
|
||||
content.WriteRune('\n')
|
||||
|
||||
if note := um.ningen.NoteState.Note(um.user.ID); note != "" {
|
||||
start, end := segutil.WriteStringBuf(&content, note)
|
||||
segutil.Add(&segment, inline.NewSegment(start, end, text.AttributeMonospace))
|
||||
|
||||
content.WriteString("\n\n")
|
||||
} else {
|
||||
start, end := segutil.WriteStringBuf(&content, "empty")
|
||||
segutil.Add(&segment, inline.NewSegment(start, end, text.AttributeDimmed))
|
||||
}
|
||||
|
||||
content.WriteString("\n\n")
|
||||
}
|
||||
|
||||
// Assign the written content into the text segment and return it after
|
||||
|
@ -236,3 +257,57 @@ func (um *User) MentionInfo() text.Rich {
|
|||
segment.Content = strings.TrimSuffix(content.String(), "\n")
|
||||
return segment
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package reference
|
|||
import (
|
||||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/segutil"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
"github.com/diamondburned/cchat/utils/empty"
|
||||
)
|
||||
|
@ -23,6 +24,13 @@ type MessageSegment struct {
|
|||
|
||||
var _ text.Segment = (*MessageSegment)(nil)
|
||||
|
||||
// Write appends to the given rich text the reference to the message ID with the
|
||||
// given text.
|
||||
func Write(rich *text.Rich, msgID discord.MessageID, text string) {
|
||||
start, end := segutil.Write(rich, text)
|
||||
segutil.Add(rich, NewMessageSegment(start, end, msgID))
|
||||
}
|
||||
|
||||
func NewMessageSegment(start, end int, msgID discord.MessageID) MessageSegment {
|
||||
return MessageSegment{
|
||||
start: start,
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"github.com/diamondburned/arikawa/v2/state/store"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/segutil"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
"github.com/diamondburned/ningen/v2/md"
|
||||
"github.com/yuin/goldmark/ast"
|
||||
)
|
||||
|
||||
|
@ -23,24 +22,6 @@ func Register(kind ast.NodeKind, r Renderer) {
|
|||
|
||||
var smallRenderers = map[ast.NodeKind]Renderer{}
|
||||
|
||||
// Parse parses the raw Markdown bytes into a rich text.
|
||||
func Parse(b []byte) text.Rich {
|
||||
node := md.Parse(b)
|
||||
return RenderNode(b, node)
|
||||
}
|
||||
|
||||
// RenderNode renders the given raw Markdown bytes with the parsed AST node into
|
||||
// a rich text.
|
||||
func RenderNode(source []byte, n ast.Node) text.Rich {
|
||||
r := New(source, n)
|
||||
r.Walk(n)
|
||||
|
||||
return text.Rich{
|
||||
Content: r.String(),
|
||||
Segments: r.Segments,
|
||||
}
|
||||
}
|
||||
|
||||
type Text struct {
|
||||
Buffer *bytes.Buffer
|
||||
Source []byte
|
||||
|
@ -53,14 +34,13 @@ type Text struct {
|
|||
Store store.Cabinet
|
||||
}
|
||||
|
||||
func New(src []byte, node ast.Node) *Text {
|
||||
func New(src []byte) *Text {
|
||||
buf := &bytes.Buffer{}
|
||||
buf.Grow(len(src))
|
||||
|
||||
return &Text{
|
||||
Source: src,
|
||||
Buffer: buf,
|
||||
Segments: make([]text.Segment, 0, node.ChildCount()),
|
||||
Source: src,
|
||||
Buffer: buf,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -178,6 +158,10 @@ func (r *Text) Join(renderer *Text) {
|
|||
|
||||
// Walk walks on the given node with the RenderNode as the walker function.
|
||||
func (r *Text) Walk(n ast.Node) {
|
||||
if r.Segments == nil {
|
||||
r.Segments = make([]text.Segment, 0, n.ChildCount())
|
||||
}
|
||||
|
||||
ast.Walk(n, r.RenderNode)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package segutil
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
|
||||
"github.com/diamondburned/cchat/text"
|
||||
)
|
||||
|
@ -29,6 +30,13 @@ func WriteStringBuf(w *bytes.Buffer, b string) (start, end int) {
|
|||
return start, end
|
||||
}
|
||||
|
||||
func WriteStringBuilder(w *strings.Builder, b string) (start, end int) {
|
||||
start = w.Len()
|
||||
w.WriteString(b)
|
||||
end = w.Len()
|
||||
return start, end
|
||||
}
|
||||
|
||||
func Add(r *text.Rich, seg ...text.Segment) {
|
||||
r.Segments = append(r.Segments, seg...)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue