package main // This file contains the function that generates message widgets depending on message stanza import ( "context" "encoding/base64" "fmt" "github.com/diamondburned/gotk4/pkg/gtk/v4" "github.com/diamondburned/gotk4/pkg/gdk/v4" "github.com/google/uuid" "github.com/jacoblockett/sanitizefilename" "github.com/jasonlovesdoggo/gopen" "gosrc.io/xmpp/stanza" "mellium.im/xmpp/jid" "os" "path/filepath" "runtime" ) func generatePresenceWidget(p stanza.Packet) gtk.Widgetter { presence, ok := p.(stanza.Presence) if !ok { return gtk.NewLabel("Unsupported message.") } if presence.Type == stanza.PresenceTypeUnavailable { var mu MucUser ok := presence.Get(&mu) if ok { if mu.MucUserItem.Affiliation == "outcast" { return gtk.NewLabel(jid.MustParse(presence.From).Resourcepart() + " has been banned!") } } return gtk.NewLabel(jid.MustParse(presence.From).Resourcepart() + " left the MUC") } else { return gtk.NewLabel(jid.MustParse(presence.From).Resourcepart() + " joined the MUC") } } func generateMessageWidget(p stanza.Packet) gtk.Widgetter { m, ok := p.(stanza.Message) if !ok { return gtk.NewLabel("Unsupported message.") } fmt.Println(m.Body) readmarker := Marker{} ok = m.Get(&readmarker) if ok { return gtk.NewLabel(fmt.Sprintf("%s has read to this point", JidMustParse(m.From).Resource)) } sid := StanzaID{} m.Get(&sid) mainBox := gtk.NewBox(gtk.OrientationVertical, 0) gesture := gtk.NewGestureClick() gesture.SetButton(3) // Right click popover := gtk.NewPopover() popover.SetParent(mainBox) popover.SetHasArrow(false) rc_box := gtk.NewBox(gtk.OrientationVertical, 0) reactions := gtk.NewBox(gtk.OrientationHorizontal, 0) reaction := []string{"👍", "👎", "♥️", "🤣", "😭"} for _, v := range reaction { like := gtk.NewButton() like.SetLabel(v) like.SetHExpand(true) like.ConnectClicked(func() { fmt.Println("licked") // TODO: Implement proper support for reactions via extension client.SendRaw(fmt.Sprintf(` %s `, m.To, jid.MustParse(m.From).Bare().String(), uuid.New().String(), m.Type, sid.ID, v)) }) reactions.Append(like) } rc_box.Append(reactions) if m.Type == stanza.MessageTypeGroupchat { moderate := gtk.NewButtonWithLabel("Moderate") // TODO: Implement proper support for moderations via extension moderate.ConnectClicked(func() { client.SendRaw(fmt.Sprintf(` Retracted `, jid.MustParse(m.From).Bare().String(), uuid.New().String(), sid.ID)) }) rc_box.Append(moderate) } popover.SetChild(rc_box) gesture.Connect("pressed", func(n_press, x, y int) { rect := gdk.NewRectangle(x, y, 1, 1) popover.SetPointingTo(&rect) popover.Popup() }) mainBox.AddController(gesture) reply := Reply{} ok = m.Get(&reply) if ok { replyBox := gtk.NewBox(gtk.OrientationHorizontal, 0) replyBox.Append(gtk.NewLabel("↱ " + jid.MustParse(reply.To).Resourcepart())) mainBox.Append(replyBox) } ocu := OccupantID{} m.Get(&ocu) authorBox := gtk.NewBox(gtk.OrientationHorizontal, 10) contentBox := gtk.NewBox(gtk.OrientationHorizontal, 0) // im := newImageFromPath("debug.png") // authorBox.Append(im) al := gtk.NewLabel(jid.MustParse(m.From).Resourcepart()) al.AddCSSClass("author") if m.Type == stanza.MessageTypeGroupchat { mo, _ := mucmembers.Load(jid.MustParse(m.From).Bare().String()) mm := mo.(mucUnit) mmm := mm.Members mmmm, ok := mmm.Load(ocu.ID) if ok { pres := mmmm.(stanza.Presence) var vu VCardUpdate pres.Get(&vu) if vu.Photo != "" { im := getAvatar(m.From, vu.Photo) im.SetPixelSize(40) im.AddCSSClass("author_img") authorBox.Append(im) } else { im := gtk.NewImageFromPaintable(clientAssets["DefaultAvatar"]) im.SetPixelSize(40) im.AddCSSClass("author_img") authorBox.Append(im) } } else { im := gtk.NewImageFromPaintable(clientAssets["DefaultAvatar"]) im.SetPixelSize(40) im.AddCSSClass("author_img") authorBox.Append(im) } } else if m.Type == stanza.MessageTypeChat { al.SetText(al.Text() + " whispers") } authorBox.Append(al) mlabel := gtk.NewLabel(m.Body) mlabel.SetWrap(true) mlabel.SetSelectable(true) mlabel.SetHAlign(gtk.AlignFill) contentBox.Append(mlabel) mainBox.Append(authorBox) mainBox.Append(contentBox) mainBox.Append(reactions) oob := stanza.OOB{} ok = m.Get(&oob) if ok { // media := newPictureFromWeb(oob.URL) // media.SetCanShrink(false) // mainBox.Append(media) // media.AddCSSClass("chat_image") mbtn := gtk.NewButtonWithLabel("🖼️") // mbtn.SetChild(newImageFromWeb(oob.URL)) mbtn.ConnectClicked(func() { gopen.Open(oob.URL) }) authorBox.Append(mbtn) } return mainBox } func getVAdjustment(scrolledWindow *gtk.ScrolledWindow) *gtk.Adjustment { val := scrolledWindow.ObjectProperty("vadjustment").(*gtk.Adjustment) return val } func getAvatar(j, hash string) *gtk.Image { // TODO: This function probably shouldn't be here, and should probably be in xmpp-helpers or somewhere similar. p, err := ensureCache() if err != nil { return gtk.NewImageFromPaintable(clientAssets["DefaultAvatar"]) } if hash == "" { fmt.Println("Hash is nil!") return gtk.NewImageFromPaintable(clientAssets["DefaultAvatar"]) } hash = filepath.Join(p, sanitizefilename.Sanitize(hash)) _, err = os.ReadFile(hash) if err == nil { return newImageFromPath(hash) } iqResp, err := stanza.NewIQ(stanza.Attrs{ Type: "get", From: clientroot.Session.BindJid, To: j, Id: "vc2", Lang: "en", }) if err != nil { panic(err) } iqResp.Payload = &VCard{} ctx := context.TODO() mychan, err := client.SendIQ(ctx, iqResp) if err != nil { panic(err) } result := <-mychan card, ok := result.Payload.(*VCard) if !ok { return gtk.NewImageFromPaintable(clientAssets["DefaultAvatar"]) } base64_data := card.Photo.Binval if card.Photo.Binval == "" || (card.Photo.Type == "image/svg+xml" && runtime.GOOS == "windows") { return gtk.NewImageFromPaintable(clientAssets["DefaultAvatar"]) } data, err := base64.StdEncoding.DecodeString(base64_data) if err != nil { panic(err) } err = os.WriteFile(hash, data, 0644) if err != nil { panic(err) } return newImageFromPath(hash) }