Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
diamondburned
GitHub Repository: diamondburned/gtkcord4
Path: blob/main/internal/messages/composer/autocomplete_members.go
366 views
1
package composer
2
3
import (
4
"context"
5
"fmt"
6
"html"
7
"time"
8
9
"github.com/diamondburned/arikawa/v3/discord"
10
"github.com/diamondburned/chatkit/components/autocomplete"
11
"github.com/diamondburned/gotk4/pkg/gtk/v4"
12
"github.com/diamondburned/gotk4/pkg/pango"
13
"github.com/diamondburned/gotkit/components/onlineimage"
14
"github.com/diamondburned/gotkit/gtkutil/imgutil"
15
"github.com/sahilm/fuzzy"
16
"libdb.so/dissent/internal/gtkcord"
17
)
18
19
const memberCacheExpiry = 2 * time.Second
20
21
type members []discord.Member
22
23
func (m members) String(i int) string { return m[i].Nick + m[i].User.DisplayName + m[i].User.Tag() }
24
func (m members) Len() int { return len(m) }
25
26
type memberCompleter struct {
27
members members
28
matched []autocomplete.Data
29
updated time.Time
30
guildID discord.GuildID
31
chID discord.ChannelID
32
}
33
34
// NewMemberCompleter creates a new autocomplete searcher that searches for
35
// members.
36
func NewMemberCompleter(chID discord.ChannelID) autocomplete.Searcher {
37
return &memberCompleter{
38
chID: chID,
39
guildID: discord.NullGuildID,
40
matched: make([]autocomplete.Data, 0, maxAutocompletion),
41
}
42
}
43
44
func (c *memberCompleter) Rune() rune { return '@' }
45
46
func (c *memberCompleter) Search(ctx context.Context, str string) []autocomplete.Data {
47
if len(str) < 1 {
48
return nil
49
}
50
51
state := gtkcord.FromContext(ctx)
52
if len(str) > 2 {
53
state.MemberState.SearchMember(c.guildID, str)
54
}
55
56
now := time.Now()
57
58
if c.members != nil && c.updated.Add(memberCacheExpiry).After(now) {
59
return c.search(str)
60
}
61
62
c.updated = now
63
64
if c.guildID.IsNull() {
65
ch, _ := state.Cabinet.Channel(c.chID)
66
if ch != nil {
67
// Set to 0 (not null) so we don't have to refetch.
68
c.guildID = 0
69
if ch.GuildID.IsValid() {
70
c.guildID = ch.GuildID
71
}
72
} else {
73
return nil
74
}
75
}
76
77
if !c.guildID.IsValid() {
78
ch, _ := state.Cabinet.Channel(c.chID)
79
if ch == nil || len(ch.DMRecipients) == 0 {
80
return nil
81
}
82
83
c.members = make([]discord.Member, len(ch.DMRecipients))
84
for i, recipient := range ch.DMRecipients {
85
// This hack works. Whatever.
86
c.members[i] = discord.Member{User: recipient}
87
}
88
} else {
89
mems, _ := state.Cabinet.Members(c.guildID)
90
c.members = members(mems)
91
}
92
93
if data := c.search(str); len(data) > 0 {
94
return data
95
}
96
97
return nil
98
}
99
100
func (c *memberCompleter) search(str string) []autocomplete.Data {
101
res := fuzzy.FindFrom(str, c.members)
102
if len(res) > maxAutocompletion {
103
res = res[:maxAutocompletion]
104
}
105
106
data := c.matched[:0]
107
for _, r := range res {
108
data = append(data, MemberData(c.members[r.Index]))
109
}
110
111
return data
112
}
113
114
// MemberData is the Data structure for each member.
115
type MemberData discord.Member
116
117
func (d MemberData) Row(ctx context.Context) *gtk.ListBoxRow {
118
i := onlineimage.NewAvatar(ctx, imgutil.HTTPProvider, emojiSize)
119
i.AddCSSClass("autocompleter-customemoji")
120
i.SetFromURL(gtkcord.InjectAvatarSize(d.User.AvatarURLWithType(discord.PNGImage)))
121
122
l := gtk.NewLabel("")
123
l.SetMaxWidthChars(45)
124
l.SetWrap(false)
125
l.SetEllipsize(pango.EllipsizeEnd)
126
l.SetXAlign(0)
127
l.SetJustify(gtk.JustifyLeft)
128
129
if d.Nick != "" {
130
l.SetLines(2)
131
l.SetMarkup(fmt.Sprintf(
132
`%s`+"\n"+`<span size="smaller" fgalpha="75%%" rise="-1200">%s</span>`,
133
html.EscapeString(d.Nick),
134
html.EscapeString(d.User.Tag()),
135
))
136
} else {
137
l.SetLines(1)
138
l.SetText(d.User.Tag())
139
}
140
141
b := gtk.NewBox(gtk.OrientationHorizontal, 4)
142
b.Append(i)
143
b.Append(l)
144
145
r := gtk.NewListBoxRow()
146
r.AddCSSClass("autocomplete-member")
147
r.SetChild(b)
148
149
return r
150
}
151
152