Path: blob/main/internal/messages/composer/autocomplete_members.go
366 views
package composer12import (3"context"4"fmt"5"html"6"time"78"github.com/diamondburned/arikawa/v3/discord"9"github.com/diamondburned/chatkit/components/autocomplete"10"github.com/diamondburned/gotk4/pkg/gtk/v4"11"github.com/diamondburned/gotk4/pkg/pango"12"github.com/diamondburned/gotkit/components/onlineimage"13"github.com/diamondburned/gotkit/gtkutil/imgutil"14"github.com/sahilm/fuzzy"15"libdb.so/dissent/internal/gtkcord"16)1718const memberCacheExpiry = 2 * time.Second1920type members []discord.Member2122func (m members) String(i int) string { return m[i].Nick + m[i].User.DisplayName + m[i].User.Tag() }23func (m members) Len() int { return len(m) }2425type memberCompleter struct {26members members27matched []autocomplete.Data28updated time.Time29guildID discord.GuildID30chID discord.ChannelID31}3233// NewMemberCompleter creates a new autocomplete searcher that searches for34// members.35func NewMemberCompleter(chID discord.ChannelID) autocomplete.Searcher {36return &memberCompleter{37chID: chID,38guildID: discord.NullGuildID,39matched: make([]autocomplete.Data, 0, maxAutocompletion),40}41}4243func (c *memberCompleter) Rune() rune { return '@' }4445func (c *memberCompleter) Search(ctx context.Context, str string) []autocomplete.Data {46if len(str) < 1 {47return nil48}4950state := gtkcord.FromContext(ctx)51if len(str) > 2 {52state.MemberState.SearchMember(c.guildID, str)53}5455now := time.Now()5657if c.members != nil && c.updated.Add(memberCacheExpiry).After(now) {58return c.search(str)59}6061c.updated = now6263if c.guildID.IsNull() {64ch, _ := state.Cabinet.Channel(c.chID)65if ch != nil {66// Set to 0 (not null) so we don't have to refetch.67c.guildID = 068if ch.GuildID.IsValid() {69c.guildID = ch.GuildID70}71} else {72return nil73}74}7576if !c.guildID.IsValid() {77ch, _ := state.Cabinet.Channel(c.chID)78if ch == nil || len(ch.DMRecipients) == 0 {79return nil80}8182c.members = make([]discord.Member, len(ch.DMRecipients))83for i, recipient := range ch.DMRecipients {84// This hack works. Whatever.85c.members[i] = discord.Member{User: recipient}86}87} else {88mems, _ := state.Cabinet.Members(c.guildID)89c.members = members(mems)90}9192if data := c.search(str); len(data) > 0 {93return data94}9596return nil97}9899func (c *memberCompleter) search(str string) []autocomplete.Data {100res := fuzzy.FindFrom(str, c.members)101if len(res) > maxAutocompletion {102res = res[:maxAutocompletion]103}104105data := c.matched[:0]106for _, r := range res {107data = append(data, MemberData(c.members[r.Index]))108}109110return data111}112113// MemberData is the Data structure for each member.114type MemberData discord.Member115116func (d MemberData) Row(ctx context.Context) *gtk.ListBoxRow {117i := onlineimage.NewAvatar(ctx, imgutil.HTTPProvider, emojiSize)118i.AddCSSClass("autocompleter-customemoji")119i.SetFromURL(gtkcord.InjectAvatarSize(d.User.AvatarURLWithType(discord.PNGImage)))120121l := gtk.NewLabel("")122l.SetMaxWidthChars(45)123l.SetWrap(false)124l.SetEllipsize(pango.EllipsizeEnd)125l.SetXAlign(0)126l.SetJustify(gtk.JustifyLeft)127128if d.Nick != "" {129l.SetLines(2)130l.SetMarkup(fmt.Sprintf(131`%s`+"\n"+`<span size="smaller" fgalpha="75%%" rise="-1200">%s</span>`,132html.EscapeString(d.Nick),133html.EscapeString(d.User.Tag()),134))135} else {136l.SetLines(1)137l.SetText(d.User.Tag())138}139140b := gtk.NewBox(gtk.OrientationHorizontal, 4)141b.Append(i)142b.Append(l)143144r := gtk.NewListBoxRow()145r.AddCSSClass("autocomplete-member")146r.SetChild(b)147148return r149}150151152