Files
lambda/gtk-message.go
T

448 lines
11 KiB
Go
Raw Normal View History

2026-01-29 21:35:36 +00:00
package main
// This file contains the function that generates message widgets depending on message stanza
import (
2026-01-30 13:29:53 +00:00
"context"
2026-01-30 15:56:58 +00:00
"encoding/base64"
2026-01-29 21:35:36 +00:00
"fmt"
2026-02-01 18:16:55 +00:00
"github.com/diamondburned/gotk4/pkg/gdk/v4"
2026-04-30 14:55:17 +01:00
"github.com/diamondburned/gotk4/pkg/glib/v2"
"github.com/diamondburned/gotk4/pkg/gtk/v4"
2026-01-30 15:56:58 +00:00
"github.com/google/uuid"
"github.com/jasonlovesdoggo/gopen"
2026-01-29 21:35:36 +00:00
"gosrc.io/xmpp/stanza"
2026-05-19 14:07:21 +01:00
xmpp_color "mellium.im/xmpp/color"
2026-01-29 21:35:36 +00:00
"mellium.im/xmpp/jid"
2026-01-30 15:56:58 +00:00
"os"
"path/filepath"
2026-01-31 10:25:09 +00:00
"runtime"
2026-04-06 11:04:53 +01:00
"strings"
2026-01-29 21:35:36 +00:00
)
2026-01-31 10:02:04 +00:00
func generatePresenceWidget(p stanza.Packet) gtk.Widgetter {
b := gtk.NewBox(gtk.OrientationHorizontal, 0)
2026-01-31 10:02:04 +00:00
presence, ok := p.(stanza.Presence)
if !ok {
2026-04-28 12:58:00 +01:00
return gtk.NewLabel(loadedLocale["unsupportedMessage"])
2026-01-31 10:02:04 +00:00
}
2026-06-03 06:38:31 +01:00
nick := JidMustParse(presence.From).Resource
2026-01-31 10:02:04 +00:00
if presence.Type == stanza.PresenceTypeUnavailable {
2026-01-31 15:08:54 +00:00
var mu MucUser
ok := presence.Get(&mu)
if ok {
if mu.MucUserItem.Affiliation == "outcast" {
2026-06-03 06:38:31 +01:00
if loadedConfig.CompactMode {
l := gtk.NewLabel("")
if mu.MucUserItem.Actor.Nick != "" {
l.SetMarkup(fmt.Sprintf("<span background='black' foreground='white'>%s%s%s!</span>", nick, loadedLocale["bannedWidget"], mu.MucUserItem.Actor.Nick))
} else {
l.SetMarkup(fmt.Sprintf("<span background='black' foreground='white'>%s%s%s!</span>", nick, loadedLocale["bannedWidgetNoActor"]))
}
b.Append(l)
return b
}
2026-05-23 06:45:18 +01:00
b.Append(gtk.NewImageFromPaintable(clientAssetsLoad("outcast")))
2026-06-03 06:38:31 +01:00
b.Append(gtk.NewLabel(nick + loadedLocale["bannedWidget"] + mu.MucUserItem.Actor.Nick + "!"))
return b
2026-01-31 15:08:54 +00:00
}
}
2026-06-03 06:38:31 +01:00
if loadedConfig.CompactMode {
l := gtk.NewLabel("")
l.SetMarkup(fmt.Sprintf("<span foreground='%s'>- %s</span>", "red", nick))
b.Append(l)
} else {
b.Append(gtk.NewImageFromPaintable(clientAssetsLoad("door_out")))
b.Append(gtk.NewLabel(nick))
}
2026-01-31 10:02:04 +00:00
} else {
2026-06-03 06:38:31 +01:00
if loadedConfig.CompactMode {
l := gtk.NewLabel("")
l.SetMarkup(fmt.Sprintf("<span foreground='%s'>+ %s</span>", "green", nick))
b.Append(l)
} else {
b.Append(gtk.NewImageFromPaintable(clientAssetsLoad("door_in")))
b.Append(gtk.NewLabel(JidMustParse(presence.From).Resource))
}
2026-01-31 10:02:04 +00:00
}
b.SetTooltipText(presence.Status)
return b
2026-01-31 10:02:04 +00:00
}
2026-01-29 21:35:36 +00:00
func generateMessageWidget(p stanza.Packet) gtk.Widgetter {
m, ok := p.(stanza.Message)
if !ok {
return gtk.NewLabel("Unsupported message.")
}
readmarker := Marker{}
ok = m.Get(&readmarker)
if ok {
2026-02-03 10:07:33 +00:00
b := gtk.NewBox(gtk.OrientationHorizontal, 0)
2026-04-28 12:58:00 +01:00
b.Append(gtk.NewLabel(fmt.Sprintf("%s%s", JidMustParse(m.From).Resource, loadedLocale["readWidget"])))
2026-02-03 10:07:33 +00:00
return b
}
composing := stanza.StateComposing{}
ok = m.Get(&composing)
if ok {
2026-02-03 10:07:33 +00:00
b := gtk.NewBox(gtk.OrientationHorizontal, 0)
2026-04-28 12:58:00 +01:00
b.Append(gtk.NewLabel(fmt.Sprintf("%s%s", JidMustParse(m.From).Resource, loadedLocale["isTyping"])))
return b
2026-02-03 10:07:33 +00:00
}
if m.Error.Type != "" {
error_box := gtk.NewBox(gtk.OrientationHorizontal, 0)
2026-05-23 06:45:18 +01:00
cancel_img := gtk.NewImageFromPaintable(clientAssetsLoad("cancel"))
error_label := gtk.NewLabel(m.Error.Text + ": ")
2026-02-03 10:07:33 +00:00
error_box.Append(cancel_img)
error_box.Append(error_label)
error_box.Append(gtk.NewLabel(m.Error.Reason))
2026-02-03 10:07:33 +00:00
return error_box
}
2026-01-29 21:35:36 +00:00
sid := StanzaID{}
m.Get(&sid)
2026-05-09 09:00:44 +01:00
padding := 10
orientation := gtk.OrientationVertical
if loadedConfig.CompactMode {
padding = 5
orientation = gtk.OrientationHorizontal
}
mainBox := gtk.NewBox(orientation, padding)
2026-01-29 21:35:36 +00:00
gesture := gtk.NewGestureClick()
gesture.SetButton(3) // Right click
2026-02-01 18:16:55 +00:00
popover := gtk.NewPopover()
popover.SetParent(mainBox)
popover.SetHasArrow(false)
2026-05-07 09:16:38 +01:00
gesture.Connect("pressed", func(n_press, x, y int) {
2026-05-09 07:33:47 +01:00
rc_box := gtk.NewBox(gtk.OrientationVertical, 0)
2026-02-01 18:16:55 +00:00
2026-05-09 07:33:47 +01:00
reactions := gtk.NewBox(gtk.OrientationHorizontal, 0)
reaction := []string{"👍", "👎", "♥️", "🤣", "💀"}
for _, v := range reaction {
2026-05-18 06:27:55 +01:00
like := gtk.NewButtonWithLabel(v)
2026-05-09 07:33:47 +01:00
like.SetHExpand(true)
like.ConnectClicked(func() {
client.SendRaw(fmt.Sprintf(`
2026-01-29 21:35:36 +00:00
<message from='%s' to='%s' id='%s' type='%s'>
<reactions id='%s' xmlns='urn:xmpp:reactions:0'>
<reaction>%s</reaction>
</reactions>
</message>
`, m.To, jid.MustParse(m.From).Bare().String(), uuid.New().String(), m.Type, sid.ID, v))
2026-05-09 07:33:47 +01:00
})
reactions.Append(like)
2026-04-06 11:04:53 +01:00
}
2026-05-09 07:33:47 +01:00
rc_box.Append(reactions)
2026-05-18 06:27:55 +01:00
custom := gtk.NewEntry()
custom.SetPlaceholderText("...")
enter_custom := gtk.NewButtonWithLabel("React")
enter_custom.SetLabel("React")
enter_custom.SetHExpand(true)
enter_custom.ConnectClicked(func() {
2026-05-19 14:07:21 +01:00
client.SendRaw(fmt.Sprintf(`
2026-05-18 06:27:55 +01:00
<message from='%s' to='%s' id='%s' type='%s'>
<reactions id='%s' xmlns='urn:xmpp:reactions:0'>
<reaction>%s</reaction>
</reactions>
</message>
`, m.To, jid.MustParse(m.From).Bare().String(), uuid.New().String(), m.Type, sid.ID, custom.Text()))
})
rc_box.Append(custom)
rc_box.Append(enter_custom)
2026-04-06 11:04:53 +01:00
2026-05-09 07:33:47 +01:00
quote := gtk.NewButtonWithLabel("Quote")
quote.ConnectClicked(func() {
lines := strings.Split(m.Body, "\n")
for i, line := range lines {
quoteline := "> " + line
lines[i] = quoteline
}
2026-02-01 18:16:55 +00:00
2026-05-09 07:33:47 +01:00
newstr := strings.Join(lines, "\n") + "\n\n"
message_en.SetText(newstr)
})
rc_box.Append(quote)
popover.SetChild(rc_box)
2026-02-01 18:16:55 +00:00
rect := gdk.NewRectangle(x, y, 1, 1)
popover.SetPointingTo(&rect)
popover.Popup()
2026-01-29 21:35:36 +00:00
})
mainBox.AddController(gesture)
2026-01-30 13:29:53 +00:00
ocu := OccupantID{}
2026-01-30 19:08:18 +00:00
2026-01-30 13:29:53 +00:00
m.Get(&ocu)
id := JidMustParse(m.From).Resource
2026-05-18 06:27:55 +01:00
name := id
2026-05-10 14:46:19 +01:00
custom_nick, ok := loadedConfig.CustomNicks[ocu.ID]
if ok {
2026-05-18 06:27:55 +01:00
name = custom_nick
2026-05-10 14:46:19 +01:00
}
2026-05-18 06:27:55 +01:00
if name == "" {
name = JidMustParse(m.From).Bare()
2026-05-10 14:46:19 +01:00
}
2026-01-30 13:29:53 +00:00
2026-05-09 09:00:44 +01:00
if loadedConfig.CompactMode {
al := gtk.NewLabel(id)
ycbcr := xmpp_color.String(id, 255, loadedConfig.CVD)
r, g, b, _ := ycbcr.RGBA()
hexcolor := fmt.Sprintf("#%02x%02x%02x", r, g, b)
2026-05-09 09:00:44 +01:00
al.SetMarkup(fmt.Sprintf("<span foreground='%s'>%s</span>", hexcolor, id))
al.SetSelectable(true)
al.SetVAlign(gtk.AlignStart)
mainBox.Append(al)
2026-01-29 21:35:36 +00:00
2026-05-09 09:00:44 +01:00
mlabel := gtk.NewLabel(m.Body)
2026-01-30 19:08:18 +00:00
2026-05-09 09:00:44 +01:00
if m.Body == "" {
mlabel.SetText(loadedLocale["noBodySet"])
mlabel.AddCSSClass("visitor")
}
mlabel.SetWrap(true)
mlabel.SetSelectable(true)
// mlabel.SetHAlign(gtk.AlignFill)
mainBox.Append(mlabel)
} else {
authorBox := gtk.NewBox(gtk.OrientationHorizontal, 10)
contentBox := gtk.NewBox(gtk.OrientationHorizontal, 0)
2026-05-10 14:46:19 +01:00
2026-05-18 06:27:55 +01:00
al := gtk.NewLabel(name)
2026-05-09 09:00:44 +01:00
al.AddCSSClass("author")
al.SetSelectable(true)
2026-02-01 18:16:55 +00:00
2026-05-09 09:00:44 +01:00
if m.Type == stanza.MessageTypeGroupchat {
2026-05-18 06:27:55 +01:00
can_generate := true
mo, ok := mucmembers.Load(JidMustParse(m.From).Bare())
if !ok {
can_generate = false
}
mm, ok := mo.(mucUnit)
if !ok {
can_generate = false
}
2026-05-09 09:00:44 +01:00
mmm := mm.Members
mmmm, ok := mmm.Load(id)
2026-05-18 06:27:55 +01:00
if !ok {
can_generate = false
}
if can_generate {
2026-05-09 09:00:44 +01:00
pres := mmmm.(stanza.Presence)
var vu VCardUpdate
pres.Get(&vu)
im := createIdenticon(m.From, false)
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, false)
im.SetPixelSize(40)
im.AddCSSClass("author_img")
authorBox.Append(im)
}
2026-05-09 09:00:44 +01:00
} else if m.Type == stanza.MessageTypeChat {
al.SetText(al.Text() + loadedLocale["whispers"])
}
2026-04-30 14:55:17 +01:00
2026-05-09 09:00:44 +01:00
authorBox.Append(al)
if m.Thread != "" {
im := createIdenticon(m.Thread, true)
im.SetPixelSize(10)
authorBox.Append(im)
2026-01-30 19:08:18 +00:00
}
2026-05-09 09:00:44 +01:00
wxdc := XDCEl{}
ok = m.Get(&wxdc)
2026-05-01 21:05:14 +01:00
if ok {
2026-05-09 09:00:44 +01:00
authorBox.Append(gtk.NewLabel("🎮"))
2026-05-01 21:05:14 +01:00
}
2026-05-09 09:00:44 +01:00
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)
2026-01-29 21:35:36 +00:00
2026-05-09 09:00:44 +01:00
/*
mum := MucUser{}
ok = m.Get(&mum)
if ok {
mlabel.SetText(fmt.Sprintf("%s%s%s", mum.MucUserItem.JID, loadedLocale["affilChange"], mum.MucUserItem.Affiliation))
}
*/
2026-01-29 21:35:36 +00:00
2026-05-09 09:00:44 +01:00
contentBox.Append(mlabel)
2026-01-29 21:35:36 +00:00
2026-05-09 09:00:44 +01:00
mainBox.Append(authorBox)
mainBox.Append(contentBox)
2026-05-09 09:00:44 +01:00
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)
}
2026-04-26 10:40:13 +01:00
2026-05-09 09:00:44 +01:00
if m.Subject != "" {
subjectlabel := gtk.NewLabel(m.Subject)
subjectlabel.SetWrap(true)
subjectlabel.SetSelectable(true)
subjectlabel.SetHAlign(gtk.AlignFill)
subjectlabel.AddCSSClass("subject")
mainBox.Append(subjectlabel)
}
2026-04-26 10:40:13 +01:00
2026-05-09 09:00:44 +01:00
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)
2026-04-26 10:40:13 +01:00
2026-05-09 09:00:44 +01:00
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)
}
2026-04-26 10:40:13 +01:00
}
2026-01-29 21:35:36 +00:00
return mainBox
}
func getVAdjustment(scrolledWindow *gtk.ScrolledWindow) *gtk.Adjustment {
val := scrolledWindow.ObjectProperty("vadjustment").(*gtk.Adjustment)
return val
}
2026-01-30 13:29:53 +00:00
2026-01-30 15:56:58 +00:00
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
2026-01-30 15:56:58 +00:00
p, err := ensureCache()
if err != nil {
2026-05-08 06:56:51 +01:00
return createIdenticon(j, false)
2026-01-30 15:56:58 +00:00
}
2026-01-30 13:29:53 +00:00
2026-01-30 19:08:18 +00:00
if hash == "" {
2026-05-08 06:56:51 +01:00
return createIdenticon(j, false)
2026-01-30 19:08:18 +00:00
}
2026-05-08 06:56:51 +01:00
_, ok := invalidImages.Load(hash)
if ok {
2026-05-08 06:56:51 +01:00
return createIdenticon(j, false)
}
2026-05-02 06:56:08 +01:00
hash = filepath.Join(p, hash)
2026-01-30 15:56:58 +00:00
_, err = os.ReadFile(hash)
2026-01-30 13:29:53 +00:00
if err == nil {
2026-05-02 06:56:08 +01:00
i, err := newImageFromPath(hash)
if err != nil {
2026-05-08 06:56:51 +01:00
invalidImages.Store(oghash, true)
return createIdenticon(j, false)
2026-05-02 06:56:08 +01:00
}
2026-05-09 07:33:47 +01:00
// i.AddCSSClass(loadedConfig.CVD.String() + "_CVD")
2026-04-06 11:04:53 +01:00
return i
2026-01-30 13:29:53 +00:00
}
iqResp, err := stanza.NewIQ(stanza.Attrs{
Type: "get",
From: clientroot.Session.BindJid,
2026-01-30 15:56:58 +00:00
To: j,
2026-05-02 06:56:08 +01:00
Id: uuid.New().String(),
2026-01-30 15:56:58 +00:00
Lang: "en",
2026-01-30 13:29:53 +00:00
})
if err != nil {
panic(err)
}
iqResp.Payload = &VCard{}
ctx := context.TODO()
mychan, err := client.SendIQ(ctx, iqResp)
if err != nil {
panic(err)
}
2026-01-30 15:56:58 +00:00
result := <-mychan
2026-01-30 13:29:53 +00:00
card, ok := result.Payload.(*VCard)
if !ok {
2026-05-08 06:56:51 +01:00
return createIdenticon(j, false)
2026-01-30 13:29:53 +00:00
}
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")) {
2026-05-08 06:56:51 +01:00
invalidImages.Store(oghash, true)
return createIdenticon(j, false)
2026-01-30 13:29:53 +00:00
}
data, err := base64.StdEncoding.DecodeString(base64_data)
if err != nil {
panic(err)
}
err = os.WriteFile(hash, data, 0644)
if err != nil {
panic(err)
}
2026-05-02 06:56:08 +01:00
i, err := newImageFromPath(hash)
if err != nil {
2026-05-08 06:56:51 +01:00
invalidImages.Store(oghash, true)
return createIdenticon(j, false)
2026-05-02 06:56:08 +01:00
}
i.AddCSSClass(loadedConfig.CVD.String() + "_CVD")
2026-04-06 11:04:53 +01:00
return i
2026-01-30 15:56:58 +00:00
}