package main import ( "bytes" "context" "fmt" "github.com/boxes-ltd/imaging" "github.com/crazy3lf/colorconv" "github.com/diamondburned/gotk4/pkg/gdk/v4" "github.com/diamondburned/gotk4/pkg/gdkpixbuf/v2" "github.com/diamondburned/gotk4/pkg/glib/v2" "github.com/diamondburned/gotk4/pkg/gtk/v4" "github.com/diamondburned/gotk4/pkg/pango" "github.com/rrivera/identicon" "gosrc.io/xmpp/stanza" "image" "image/png" xmpp_color "mellium.im/xmpp/color" "strconv" ) 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, name string) 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.name = name newTab.msgs.Append(gtk.NewButtonWithLabel(loadedLocale["getPastMessages"])) 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) typingStatus.SetText("") 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, 10) i := 0 rangeOrdered(&mm, (func(k, v any) bool { i++ userbox := gtk.NewBox(gtk.OrientationHorizontal, 2) u := v.(stanza.Presence) var mu MucUser var ocu OccupantID u.Get(&mu) u.Get(&ocu) if mu.MucUserItem.Role == "moderator" { gen.Prepend(userbox) } else { gen.Append(userbox) } //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\n%s", u.From, mu.MucUserItem.Role, mu.MucUserItem.Affiliation, loadedLocale["clickForMoreInfo"])) userbox.Append(nick_label) var hats Hats ok := u.Get(&hats) if ok { for _, hat := range hats.Hats { var val float64 if hat.Hue != "" { tval, _ := strconv.Atoi(hat.Hue) val = float64(tval) } else { xc := xmpp_color.String(hat.URI, 255, loadedConfig.CVD) r, g, b, _ := xc.RGBA() val, _, _ = colorconv.RGBToHSV(uint8(r), uint8(g), uint8(b)) } tB := tagBytes img, _, _ := image.Decode(bytes.NewReader(tB)) i_rgba := imaging.AdjustHue(img, val) var buf bytes.Buffer png.Encode(&buf, i_rgba) tB = buf.Bytes() loader := gdkpixbuf.NewPixbufLoader() loader.Write(tB) loader.Close() tag := gtk.NewPictureForPaintable(gdk.NewTextureForPixbuf(loader.Pixbuf())) tag.SetTooltipText(hat.Title) userbox.Prepend(tag) } } status := gtk.NewImageFromPaintable(clientAssets["status_"+string(u.Show)]) status.SetTooltipText(string(u.Show)) status.SetHAlign(gtk.AlignEnd) // medal.SetHExpand(true) userbox.Prepend(status) 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(1) mod_gesture := gtk.NewGestureClick() mod_gesture.SetButton(3) popover := gtk.NewPopover() popover.SetHasArrow(false) popover.SetParent(userbox) rc_box := gtk.NewBox(gtk.OrientationVertical, 0) bb := gtk.NewButtonWithLabel(loadedLocale["ban"]) kb := gtk.NewButtonWithLabel(loadedLocale["kick"]) ab := gtk.NewButtonWithLabel(loadedLocale["setAffil"]) rb := gtk.NewButtonWithLabel(loadedLocale["setRole"]) kb.ConnectClicked(func() { client.SendRaw(fmt.Sprintf(` `, clientroot.Session.BindJid, jid, JidMustParse(u.From).Resource)) }) bb.ConnectClicked(func() { var mu MucUser ok = u.Get(&mu) if ok { if mu.MucUserItem.JID != "" { client.SendRaw(fmt.Sprintf(` `, clientroot.Session.BindJid, jid, JidMustParse(mu.MucUserItem.JID).Bare())) } } }) ab.ConnectClicked(func() { var mu MucUser ok = u.Get(&mu) if ok { if mu.MucUserItem.JID != "" { win := gtk.NewWindow() win.SetDefaultSize(400, 1) win.SetResizable(false) box := gtk.NewBox(gtk.OrientationVertical, 0) box.Append(gtk.NewLabel(loadedLocale["setAffilDescPartOne"] + JidMustParse(u.From).Resource + loadedLocale["setAffilDescPartTwo"])) the_entry := gtk.NewEntry() the_entry.SetText(mu.MucUserItem.Affiliation) submit := gtk.NewButtonWithLabel(loadedLocale["submit"]) submit.ConnectClicked(func() { client.SendRaw(fmt.Sprintf(` `, clientroot.Session.BindJid, jid, the_entry.Text(), JidMustParse(mu.MucUserItem.JID).Bare())) win.SetVisible(false) }) box.Append(the_entry) box.Append(submit) win.SetChild(box) win.SetVisible(true) } } }) rb.ConnectClicked(func() { var mu MucUser ok = u.Get(&mu) if ok { if mu.MucUserItem.JID != "" { win := gtk.NewWindow() win.SetDefaultSize(400, 1) win.SetResizable(false) box := gtk.NewBox(gtk.OrientationVertical, 0) box.Append(gtk.NewLabel(loadedLocale["setRoleDescPartOne"] + JidMustParse(u.From).Resource + loadedLocale["setRoleDescPartTwo"])) box.Append(gtk.NewLabel(loadedLocale["setRoleWarning"])) the_entry := gtk.NewEntry() the_entry.SetText(mu.MucUserItem.Role) submit := gtk.NewButtonWithLabel(loadedLocale["submit"]) submit.ConnectClicked(func() { client.SendRaw(fmt.Sprintf(` `, clientroot.Session.BindJid, jid, JidMustParse(u.From).Resource, the_entry.Text())) }) box.Append(the_entry) box.Append(submit) win.SetChild(box) win.SetVisible(true) } } }) rc_box.Append(bb) rc_box.Append(kb) rc_box.Append(ab) rc_box.Append(rb) popover.SetChild(rc_box) mod_gesture.Connect("pressed", func(n_press, x, y int) { rect := gdk.NewRectangle(x, y, 1, 1) popover.SetPointingTo(&rect) popover.Popup() }) 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) ver_text := gtk.NewLabel(loadedLocale["gettingVersion"]) ver_text.AddCSSClass("visitor") win.SetTitle(JidMustParse(u.From).Resource) nick.AddCSSClass("author") nick.SetSelectable(true) profile_box.Append(nick) profile_box.Append(ver_text) fr := gtk.NewLabel(u.From) fr.AddCSSClass("jid") fr.SetSelectable(true) profile_box.Append(fr) profile_box.Append(ver_text) 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 != "" { ji := (gtk.NewLabel(mu.MucUserItem.JID)) ji.AddCSSClass("jid") ji.SetSelectable(true) profile_box.Append(ji) } profile_box.Append(gtk.NewLabel(loadedLocale["connectedWithRole"] + mu.MucUserItem.Role)) profile_box.Append(gtk.NewLabel(loadedLocale["affiliatedAs"] + 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("%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)) */ sw := gtk.NewScrolledWindow() s := "" for _, feature := range res.Features { s = s + feature.Var + "\n" } sw.SetChild(gtk.NewLabel(s)) profile_box.Append(sw) } } }() 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 vr := fmt.Sprintf("%s %s %s", name, version, os) if name == "" && version == "" && os == "" { ver_text.SetText(loadedLocale["versionQueryEmpty"]) } else { ver_text.SetText(vr) ver_text.RemoveCSSClass("visitor") } } else if result.Error != nil && result.Error.Type != "" { ver_text.SetText(loadedLocale["versionQueryError"]) ver_text.SetTooltipText(result.Error.Reason + ": " + result.Error.Text) ver_text.RemoveCSSClass("visitor") ver_text.AddCSSClass("error") } } }() 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.Prepend(im) } else { im := createIdenticon(u.From) im.SetPixelSize(80) im.AddCSSClass("author_img") profile_box.Prepend(im) } } else { im := createIdenticon(u.From) im.SetPixelSize(80) im.AddCSSClass("author_img") profile_box.Prepend(im) } }() win.SetChild(profile_box) win.SetTransientFor(win) win.Present() }) userbox.AddController(gesture) userbox.AddController(mod_gesture) return true })) headerBox := gtk.NewBox(gtk.OrientationHorizontal, 0) if i >= 500 { headerBox.Append(gtk.NewImageFromPaintable(clientAssets["world"])) } else if i >= 50 { headerBox.Append(gtk.NewImageFromPaintable(clientAssets["large_group"])) } else { headerBox.Append(gtk.NewImageFromPaintable(clientAssets["group"])) } headerBox.Append(gtk.NewLabel(fmt.Sprintf("%d %s", i, loadedLocale["participants"]))) gen.Prepend(headerBox) muci := getAvatar(jid, jid) muci.SetPixelSize(80) gen.Prepend(muci) muc_name := gtk.NewLabel(typed_tab.name) muc_name.AddCSSClass("author") gen.Prepend(muc_name) memberList.SetChild(gen) } else { memberList.SetChild(gtk.NewLabel(jid)) } } func showErrorDialog(err error) { err_win := gtk.NewWindow() err_win.SetTitle(loadedLocale["error"]) err_win.SetDefaultSize(400, 200) err_win.SetResizable(false) box := gtk.NewBox(gtk.OrientationVertical, 0) err_label := gtk.NewLabel(err.Error()) err_label.SetSelectable(true) box.Append(err_label) close_btn := gtk.NewButtonWithLabel(loadedLocale["close"]) close_btn.ConnectClicked(func() { err_win.SetVisible(false) }) box.Append(close_btn) err_win.SetChild(box) err_win.Present() } func createIdenticon(word string) *gtk.Image { // This function generates an identicon if !loadedConfig.Identicons { i := gtk.NewImageFromPaintable(clientAssets["DefaultAvatar"]) i.AddCSSClass(loadedConfig.CVD.String() + "_CVD") return i } gen, _ := identicon.New("github", 5, 3) ii, _ := gen.Draw(word) im := ii.Image(25) buf := new(bytes.Buffer) err := png.Encode(buf, im) if err != nil { panic(err) } loader := gdkpixbuf.NewPixbufLoader() loader.Write(buf.Bytes()) loader.Close() i := gtk.NewImageFromPaintable(gdk.NewTextureForPixbuf(loader.Pixbuf())) i.AddCSSClass(loadedConfig.CVD.String() + "_CVD") return i }