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/gdk/v4" "github.com/diamondburned/gotk4/pkg/glib/v2" "github.com/diamondburned/gotk4/pkg/gtk/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" "strings" ) func generatePresenceWidget(p stanza.Packet) gtk.Widgetter { b := gtk.NewBox(gtk.OrientationHorizontal, 0) presence, ok := p.(stanza.Presence) if !ok { return gtk.NewLabel(loadedLocale["unsupportedMessage"]) } if presence.Type == stanza.PresenceTypeUnavailable { var mu MucUser ok := presence.Get(&mu) if ok { if mu.MucUserItem.Affiliation == "outcast" { b.Append(gtk.NewImageFromPaintable(clientAssets["outcast"])) b.Append(gtk.NewLabel(JidMustParse(presence.From).Resource + loadedLocale["bannedWidget"] + mu.MucUserItem.Actor.Nick + "!")) return b } } b.Append(gtk.NewImageFromPaintable(clientAssets["door_out"])) b.Append(gtk.NewLabel(JidMustParse(presence.From).Resource)) } else { b.Append(gtk.NewImageFromPaintable(clientAssets["door_in"])) b.Append(gtk.NewLabel(JidMustParse(presence.From).Resource)) } b.SetTooltipText(presence.Status) return b } 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 { b := gtk.NewBox(gtk.OrientationHorizontal, 0) b.Append(gtk.NewLabel(fmt.Sprintf("%s%s", JidMustParse(m.From).Resource, loadedLocale["readWidget"]))) return b } composing := stanza.StateComposing{} ok = m.Get(&composing) if ok { b := gtk.NewBox(gtk.OrientationHorizontal, 0) b.Append(gtk.NewLabel(fmt.Sprintf("%s%s", JidMustParse(m.From).Resource, loadedLocale["isTyping"]))) return b } if m.Error.Type != "" { error_box := gtk.NewBox(gtk.OrientationHorizontal, 0) cancel_img := gtk.NewImageFromPaintable(clientAssets["cancel"]) error_label := gtk.NewLabel(m.Error.Text + ": ") error_box.Append(cancel_img) error_box.Append(error_label) error_box.Append(gtk.NewLabel(m.Error.Reason)) return error_box } sid := StanzaID{} m.Get(&sid) mainBox := gtk.NewBox(gtk.OrientationVertical, 10) 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) quote := gtk.NewButtonWithLabel("Quote") quote.ConnectClicked(func() { lines := strings.Split(m.Body, "\n") for i, line := range lines { quoteline := "> " + line lines[i] = quoteline } newstr := strings.Join(lines, "\n") + "\n\n" message_en.SetText(newstr) }) rc_box.Append(quote) 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) ocu := OccupantID{} m.Get(&ocu) //id := ocu.ID // if id == "" { id := JidMustParse(m.From).Resource // } authorBox := gtk.NewBox(gtk.OrientationHorizontal, 10) contentBox := gtk.NewBox(gtk.OrientationHorizontal, 0) // im := newImageFromPath("debug.png") // authorBox.Append(im) n := JidMustParse(m.From).Resource if n == "" { n = JidMustParse(m.From).Resource } al := gtk.NewLabel(n) al.AddCSSClass("author") al.SetSelectable(true) if m.Type == stanza.MessageTypeGroupchat { mo, _ := mucmembers.Load(JidMustParse(m.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) im := createIdenticon(m.From) im.SetPixelSize(40) im.AddCSSClass("author_img") authorBox.Append(im) if vu.Photo != "" { go func() { new_im := getAvatar(m.From, vu.Photo) glib.IdleAdd(func() { new_im.SetPixelSize(40) new_im.AddCSSClass("author_img") authorBox.Remove(im) authorBox.Prepend(new_im) }) }() } } else { im := createIdenticon(m.From) im.SetPixelSize(40) im.AddCSSClass("author_img") authorBox.Append(im) } } else if m.Type == stanza.MessageTypeChat { al.SetText(al.Text() + loadedLocale["whispers"]) } authorBox.Append(al) wxdc := XDCEl{} ok = m.Get(&wxdc) if ok { authorBox.Append(gtk.NewLabel("🎮")) } mlabel := gtk.NewLabel(m.Body) if m.Body == "" { mlabel.SetText(loadedLocale["noBodySet"]) mlabel.AddCSSClass("visitor") } mlabel.SetWrap(true) mlabel.SetSelectable(true) mlabel.SetHAlign(gtk.AlignFill) /* mum := MucUser{} ok = m.Get(&mum) if ok { mlabel.SetText(fmt.Sprintf("%s%s%s", mum.MucUserItem.JID, loadedLocale["affilChange"], mum.MucUserItem.Affiliation)) } */ 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) } if m.Subject != "" { subjectlabel := gtk.NewLabel(m.Subject) subjectlabel.SetWrap(true) subjectlabel.SetSelectable(true) subjectlabel.SetHAlign(gtk.AlignFill) subjectlabel.AddCSSClass("subject") mainBox.Append(subjectlabel) } link_preview := LinkPreview{} ok = m.Get(&link_preview) if ok { lp_box := gtk.NewBox(gtk.OrientationVertical, 10) lp_box.AddCSSClass("link_preview") lp_title := gtk.NewLabel(link_preview.Title) lp_title.SetSelectable(true) lp_title.SetWrap(true) lp_title.SetHAlign(gtk.AlignFill) lp_desc := gtk.NewLabel(link_preview.URL + "\n" + link_preview.Description) lp_desc.SetSelectable(true) lp_desc.SetWrap(true) lp_desc.SetHAlign(gtk.AlignFill) lp_box.Append(lp_title) lp_box.Append(lp_desc) warning := gtk.NewLabel("⚠️") warning.SetTooltipText(loadedLocale["linkPreviewWarning"]) lp_box.Append(warning) mainBox.Append(lp_box) } 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. oghash := hash p, err := ensureCache() if err != nil { return createIdenticon(j) } if hash == "" { fmt.Println("Hash is nil!") return createIdenticon(j) } _, ok := invalidImages[hash] if ok { fmt.Println("Image is invalid") return createIdenticon(j) } hash = filepath.Join(p, sanitizefilename.Sanitize(hash)) _, err = os.ReadFile(hash) if err == nil { i := newImageFromPath(hash) i.AddCSSClass(loadedConfig.CVD.String() + "_CVD") return i } 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 createIdenticon(j) } base64_data := card.Photo.Binval if card.Photo.Binval == "" || ((card.Photo.Type == "image/svg+xml" || card.Photo.Type == "image/webp") && (runtime.GOOS == "windows" || runtime.GOOS == "netbsd")) { fmt.Println("Blocking image") invalidImages[oghash] = true return createIdenticon(j) } data, err := base64.StdEncoding.DecodeString(base64_data) if err != nil { panic(err) } err = os.WriteFile(hash, data, 0644) if err != nil { panic(err) } i := newImageFromPath(hash) i.AddCSSClass(loadedConfig.CVD.String() + "_CVD") return i }