package main import ( "context" "crypto/sha1" "encoding/hex" "fmt" "github.com/diamondburned/gotk4/pkg/glib/v2" "github.com/diamondburned/gotk4/pkg/gtk/v4" "github.com/diamondburned/gotk4/pkg/pango" "gosrc.io/xmpp/stanza" ) func scrollToBottomAfterUpdate(scrolledWindow *gtk.ScrolledWindow) { glib.IdleAdd(func() bool { vAdj := scrolledWindow.VAdjustment() vAdj.SetValue(vAdj.Upper() - vAdj.PageSize()) return false // Return false to run only once }) } func createTab(jid string, isMuc bool) bool { fmt.Println("Creating tab", jid, "isMuc:", isMuc) _, ok := tabs.Load(jid) _, uok := userdevices.Load(jid) _, mok := mucmembers.Load(jid) if !ok && !uok && !mok { newTab := new(chatTab) newTab.isMuc = isMuc newTab.msgs = gtk.NewListBox() newTab.msgs.SetVExpand(true) newTab.msgs.SetShowSeparators(true) newTab.msgs.Append(gtk.NewButtonWithLabel("Get past messages...")) tabs.Store(jid, newTab) return true } return false } func switchToTab(jid string, w *gtk.Window) { current = jid tab, ok := tabs.Load(current) if !ok { return } typed_tab := tab.(*chatTab) scroller.SetChild(typed_tab.msgs) if typed_tab.isMuc { m, ok := mucmembers.Load(jid) if !ok { return } ma, ok := m.(mucUnit) if !ok { return } mm := ma.Members gen := gtk.NewBox(gtk.OrientationVertical, 0) i := 0 mm.Range(func(k, v any) bool { i++ userbox := gtk.NewBox(gtk.OrientationHorizontal, 0) u := v.(stanza.Presence) var mu MucUser var ocu OccupantID u.Get(&mu) u.Get(&ocu) id := ocu.ID if id == "" { id = JidMustParse(u.From).Resource } nick_label := gtk.NewLabel(JidMustParse(u.From).Resource) nick_label.SetEllipsize(pango.EllipsizeEnd) nick_label.AddCSSClass(mu.MucUserItem.Role) if mu.MucUserItem.Role == "visitor" { nick_label.SetOpacity(0.5) } userbox.SetTooltipText(fmt.Sprintf("%s\n%s\n%s\nRight-click for more information", u.From, mu.MucUserItem.Role, mu.MucUserItem.Affiliation)) userbox.Append(nick_label) var hats Hats ok := u.Get(&hats) if ok { for _, hat := range hats.Hats { tag := gtk.NewImageFromPaintable(clientAssets["tag"]) tag.SetTooltipText(hat.Title) userbox.Prepend(tag) } } medal := gtk.NewImageFromPaintable(clientAssets[mu.MucUserItem.Affiliation]) medal.SetTooltipText(mu.MucUserItem.Affiliation) medal.SetHAlign(gtk.AlignEnd) medal.SetHExpand(true) userbox.Append(medal) gesture := gtk.NewGestureClick() gesture.SetButton(3) // Right click gesture.Connect("pressed", func(n_press, x, y int) { win := gtk.NewWindow() win.SetDefaultSize(400, 400) profile_box := gtk.NewBox(gtk.OrientationVertical, 0) nick := gtk.NewLabel(JidMustParse(u.From).Resource) win.SetTitle(JidMustParse(u.From).Resource) nick.AddCSSClass("author") profile_box.Append(nick) profile_box.Append(gtk.NewLabel(u.From)) iqResp, err := stanza.NewIQ(stanza.Attrs{ Type: "get", From: clientroot.Session.BindJid, To: u.From, Id: "vc2", Lang: "en", }) if err != nil { panic(err) } iqResp.Payload = &stanza.Version{} loading_version_text := gtk.NewLabel("...") var hats Hats ok := u.Get(&hats) if ok { for _, hat := range hats.Hats { l := gtk.NewLabel(hat.Title) l.AddCSSClass("hat") profile_box.Append(l) } } var mu MucUser ok = u.Get(&mu) if ok { if mu.MucUserItem.JID != "" { profile_box.Append(gtk.NewLabel(mu.MucUserItem.JID)) } profile_box.Append(gtk.NewLabel("Connected with role " + mu.MucUserItem.Role)) profile_box.Append(gtk.NewLabel("Affiliated as " + mu.MucUserItem.Affiliation)) } go func() { fmt.Println("Attempting to get Disco info") myIQ, err := stanza.NewIQ(stanza.Attrs{ Type: "get", From: clientroot.Session.BindJid, To: u.From, Id: "dicks", Lang: "en", }) if err != nil { panic(err) } myIQ.Payload = &stanza.DiscoInfo{} ctx := context.TODO() mychan, err := client.SendIQ(ctx, myIQ) if err == nil { result := <-mychan res, ok := result.Payload.(*stanza.DiscoInfo) if ok { idents := res.Identity for i, ident := range idents { profile_box.Append(gtk.NewLabel(fmt.Sprintf("Identity %d: Name: %s, Category: %s, Type: %s", i+1, ident.Name, ident.Category, ident.Type))) } s := fmt.Sprintf("%v", res.Features) h := sha1.New() h.Write([]byte(s)) sha1_hash := hex.EncodeToString(h.Sum(nil)) profile_box.Append(gtk.NewLabel(fmt.Sprintf("The hash of this user's Disco features is:\n%s\nUse the disco feature to view them", sha1_hash))) } } }() go func() { ctx := context.TODO() mychan, err := client.SendIQ(ctx, iqResp) if err == nil { result := <-mychan ver, ok := result.Payload.(*stanza.Version) if ok { loading_version_text.SetVisible(false) name := ver.Name version := ver.Version os := ver.OS profile_box.Append(gtk.NewLabel(fmt.Sprintf("%s %s %s", name, version, os))) } } }() go func() { mo, _ := mucmembers.Load(JidMustParse(u.From).Bare()) mm := mo.(mucUnit) mmm := mm.Members mmmm, ok := mmm.Load(id) if ok { pres := mmmm.(stanza.Presence) var vu VCardUpdate pres.Get(&vu) if vu.Photo != "" { im := getAvatar(u.From, vu.Photo) im.SetPixelSize(80) im.AddCSSClass("author_img") profile_box.Append(im) } else { im := newImageFromPath("debug.png") im.SetPixelSize(80) im.AddCSSClass("author_img") profile_box.Append(im) } } else { im := newImageFromPath("debug.png") im.SetPixelSize(80) im.AddCSSClass("author_img") profile_box.Append(im) } }() win.SetChild(profile_box) win.SetTransientFor(win) win.Present() }) userbox.AddController(gesture) if mu.MucUserItem.Role == "moderator" { gen.Prepend(userbox) } else { gen.Append(userbox) } return true }) headerBox := gtk.NewBox(gtk.OrientationHorizontal, 0) headerBox.Append(gtk.NewImageFromPaintable(clientAssets["group"])) headerBox.Append(gtk.NewLabel(fmt.Sprintf("%d participant(s)", i))) gen.Prepend(headerBox) memberList.SetChild(gen) } else { memberList.SetChild(gtk.NewLabel(jid)) } } func showErrorDialog(err error) { fmt.Println(err.Error()) }