diff --git a/assets.go b/assets.go index 918bee5..d48009d 100644 --- a/assets.go +++ b/assets.go @@ -153,7 +153,9 @@ var updateBytes []byte //go:embed assets/solar.png var solarBytes []byte -// var clientAssets map[string]gdk.Paintabler = make(map[string]gdk.Paintabler) +//go:embed assets/timeline_marker.png +var timelineMarkerBytes []byte + var clientAssets sync.Map func loadAsset(key string, data []byte) { @@ -227,6 +229,7 @@ func init() { "please_wait": pleaseWaitBytes, "update": updateBytes, "solar": solarBytes, + "timeline_marker": timelineMarkerBytes, } { loadAsset(key, data) } diff --git a/assets/banned.jpg b/assets/banned.jpg new file mode 100644 index 0000000..cc27f15 Binary files /dev/null and b/assets/banned.jpg differ diff --git a/assets/timeline_marker.png b/assets/timeline_marker.png new file mode 100644 index 0000000..a3fbddf Binary files /dev/null and b/assets/timeline_marker.png differ diff --git a/go.mod b/go.mod index db82868..4b40381 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/BurntSushi/toml v1.6.0 github.com/boxes-ltd/imaging v1.7.5 github.com/crazy3lf/colorconv v1.2.0 - github.com/diamondburned/gotk4/pkg v0.3.1 + github.com/diamondburned/gotk4/pkg v0.4.1 github.com/gen2brain/beeep v0.11.2 github.com/go-analyze/charts v0.6.0 github.com/google/uuid v1.6.0 @@ -16,32 +16,30 @@ require ( github.com/rrivera/identicon v0.0.0-20240116195454-d5ba35832c0d github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef - golang.org/x/net v0.57.0 gosrc.io/xmpp v0.5.1 mellium.im/xmpp v0.23.0 ) require ( git.sr.ht/~jackmordaunt/go-toast v1.1.2 // indirect - github.com/KarpelesLab/weak v0.1.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/esiqveland/notify v0.14.0 // indirect - github.com/go-analyze/bulk v0.1.4 // indirect + github.com/go-analyze/bulk v0.1.5 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/godbus/dbus/v5 v5.2.2 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/jackmordaunt/icns/v3 v3.0.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect - github.com/rogpeppe/go-internal v1.15.0 // indirect + github.com/rogpeppe/go-internal v1.16.0 // indirect github.com/sergeymakinen/go-bmp v1.0.0 // indirect github.com/sergeymakinen/go-ico v1.0.0 // indirect github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect - go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 // indirect - golang.org/x/image v0.44.0 // indirect + golang.org/x/image v0.45.0 // indirect + golang.org/x/net v0.57.0 // indirect golang.org/x/sync v0.22.0 // indirect golang.org/x/sys v0.47.0 // indirect - golang.org/x/text v0.40.0 // indirect + golang.org/x/text v0.41.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect mellium.im/reader v0.1.0 // indirect mellium.im/xmlstream v0.15.4 // indirect diff --git a/go.sum b/go.sum index 21d08e6..bf21154 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,6 @@ git.sr.ht/~jackmordaunt/go-toast v1.1.2 h1:/yrfI55LRt1M7H1vkaw+NaH1+L1CDxrqDltwm git.sr.ht/~jackmordaunt/go-toast v1.1.2/go.mod h1:jA4OqHKTQ4AFBdwrSnwnskUIIS3HYzlJSgdzCKqfavo= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/KarpelesLab/weak v0.1.1 h1:fNnlPo3aypS9tBzoEQluY13XyUfd/eWaSE/vMvo9s4g= -github.com/KarpelesLab/weak v0.1.1/go.mod h1:pzXsWs5f2bf+fpgHayTlBE1qJpO3MpJKo5sRaLu1XNw= github.com/boxes-ltd/imaging v1.7.5 h1:k4kYxJEhysoGhEEN1IEeKoSbnG8/8snjj7M48Ok0fnk= github.com/boxes-ltd/imaging v1.7.5/go.mod h1:+8H+oRvis3InOFtTpcoCCB1RDXqo6p9tQBtjZfWnrC8= github.com/crazy3lf/colorconv v1.2.0 h1:UM7kSZWnwFMGiC+PpYrjxQSOd6sEyWb+dRKKTd3KslA= @@ -12,16 +10,16 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/diamondburned/gotk4/pkg v0.3.1 h1:uhkXSUPUsCyz3yujdvl7DSN8jiLS2BgNTQE95hk6ygg= -github.com/diamondburned/gotk4/pkg v0.3.1/go.mod h1:DqeOW+MxSZFg9OO+esk4JgQk0TiUJJUBfMltKhG+ub4= +github.com/diamondburned/gotk4/pkg v0.4.1 h1:w4BwVUUBkKyNLz5HKHN2Oz3kZCTLHfpfKgB/su1p6uo= +github.com/diamondburned/gotk4/pkg v0.4.1/go.mod h1:BAEEDXtj5srQ1S2++5HX2K1s9c6CtuWSa60dAywrkRM= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/esiqveland/notify v0.14.0 h1:LgIT/vYnziiw3d4ykaeyytc7jBUhD+Nvv3U7GIuPFj4= github.com/esiqveland/notify v0.14.0/go.mod h1:hesw/IRYTO0x99u1JPweAl4+5mwXJibQVUcP0Iu5ORE= github.com/gen2brain/beeep v0.11.2 h1:+KfiKQBbQCuhfJFPANZuJ+oxsSKAYNe88hIpJuyKWDA= github.com/gen2brain/beeep v0.11.2/go.mod h1:jQVvuwnLuwOcdctHn/uyh8horSBNJ8uGb9Cn2W4tvoc= -github.com/go-analyze/bulk v0.1.4 h1:PIlVl+KA73nW2KKr8eGiF3fhwfI1yAkTsur/3hzBM10= -github.com/go-analyze/bulk v0.1.4/go.mod h1:afon/KtFJYnekIyN20H/+XUvcLFjE8sKR1CfpqfClgM= +github.com/go-analyze/bulk v0.1.5 h1:Zj8w3gEOhEnp8aRZ7DHMDqaG3/Bp7CvQ7pu6Mw9YhFc= +github.com/go-analyze/bulk v0.1.5/go.mod h1:afon/KtFJYnekIyN20H/+XUvcLFjE8sKR1CfpqfClgM= github.com/go-analyze/charts v0.6.0 h1:aBE2cbdr58faYGwPk3RUzGQMyg5fGsSAxepwYTYmvhg= github.com/go-analyze/charts v0.6.0/go.mod h1:m8hqGNSpYYdMMc0Im/RCowY9CIA7705jiltuOVQZWuI= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= @@ -51,8 +49,8 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.15.0 h1:D0RCU5rMAp+SpgkiNdrjfJ+LX4J1M32V2NeCY7EJ6hc= -github.com/rogpeppe/go-internal v1.15.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs= +github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g= +github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs= github.com/rrivera/identicon v0.0.0-20240116195454-d5ba35832c0d h1:l3+2LWCbVxn5itfvXAfH9n4YL9jh8l1g5zcncbIc1cs= github.com/rrivera/identicon v0.0.0-20240116195454-d5ba35832c0d/go.mod h1:TbpErkob6SY7cyozRVSGoB3OlO2qOAgVN8O3KAJ4fMI= github.com/sergeymakinen/go-bmp v1.0.0 h1:SdGTzp9WvCV0A1V0mBeaS7kQAwNLdVJbmHlqNWq0R+M= @@ -73,14 +71,12 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af h1:6yITBqGTE2lEeTPG04SN9W+iWHCRyHqlVYILiSXziwk= github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af/go.mod h1:4F09kP5F+am0jAwlQLddpoMDM+iewkxxt6nxUQ5nq5o= -go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 h1:lGdhQUN/cnWdSH3291CUuxSEqc+AsGTiDxPP3r2J0l4= -go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= -golang.org/x/image v0.44.0 h1:+tDekMZED9+LrtB3G5xzRggpVh9CARjZqROla3R3R+I= -golang.org/x/image v0.44.0/go.mod h1:V8K3KE9KKKE+pLpQDOeN18w9oacNSvy1tDOirTu4xtY= -golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= -golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= +golang.org/x/image v0.45.0 h1:FMb1nTbH5H9vF55SriQHgFw5GnNL9Jg6L25BwXKzhB0= +golang.org/x/image v0.45.0/go.mod h1:n62x/7RqlwXDvGsSU4u6IUTUf6KghUZ9Bt7cG/T9Fx4= +golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= +golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= @@ -88,10 +84,10 @@ golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= -golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= -golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= -golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= +golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/gtk-helpers.go b/gtk-helpers.go index 28056b7..c51d438 100644 --- a/gtk-helpers.go +++ b/gtk-helpers.go @@ -257,14 +257,14 @@ func createTab(jid string, isMuc bool, name string) bool { if !ok && !uok && !mok { newTab := new(chatTab) newTab.isMuc = isMuc - newTab.msgs = gtk.NewListBox() glib.IdleAdd(func() { + newTab.msgs = gtk.NewListBox() newTab.msgs.SetVExpand(true) newTab.msgs.SetShowSeparators(true) // newTab.msgs.Append(gtk.NewButtonWithLabel(loadedLocale["getPastMessages"])) }) newTab.name = name - + newTab.msg_refsID = make(map[string]*abMessage) tabs.Store(jid, newTab) return true } @@ -646,15 +646,14 @@ func switchToTab(jid string, w *gtk.Window) { muc_presence := typed_tab.muc_presence muci := gtk.NewImage() + muci.SetPixelSize(80) + gen.Prepend(muci) - createIdenticon(jid, true, muci) if muc_presence != nil { vc := &VCardUpdate{} ok = muc_presence.Get(vc) if ok { - getAvatar(jid, vc.Photo, muci) - muci.SetPixelSize(80) - gen.Prepend(muci) + go getAvatar(jid, vc.Photo, muci) } } @@ -723,8 +722,10 @@ func createIdenticon(word string, always_create bool, i *gtk.Image) { // This fu return i */ - i.SetFromPaintable(gt) - i.AddCSSClass(loadedConfig.CVD.String() + "_CVD") + glib.IdleAdd(func() { + i.SetFromPaintable(gt) + i.AddCSSClass(loadedConfig.CVD.String() + "_CVD") + }) } func jidBuilder(en *gtk.Entry) { // This function spawns a window that allows the user to interactively build a JID diff --git a/gtk-message.go b/gtk-message.go index a8adb72..7191201 100644 --- a/gtk-message.go +++ b/gtk-message.go @@ -74,7 +74,7 @@ func generatePresenceWidget(p stanza.Packet) gtk.Widgetter { return b } -func generateMessageWidget(p stanza.Packet, blocked bool) (gtk.Widgetter, bool) { +func generateMessageWidget(p stanza.Packet, blocked bool, target string) (gtk.Widgetter, bool) { m, ok := p.(stanza.Message) if !ok { return gtk.NewLabel("Unsupported message."), true @@ -107,6 +107,13 @@ func generateMessageWidget(p stanza.Packet, blocked bool) (gtk.Widgetter, bool) return error_box, true } + store_label := false + tab, ok := tabs.Load(target) + if ok { + store_label = true + } + typed_tab := tab.(*chatTab) + sid := StanzaID{} m.Get(&sid) @@ -257,7 +264,7 @@ func generateMessageWidget(p stanza.Packet, blocked bool) (gtk.Widgetter, bool) mainBox.Append(al) body := m.Body - body = XEPToPango(body) + body = XEPToPango(body, false) mlabel := gtk.NewLabel("") mlabel.SetMarkup(body) @@ -266,6 +273,11 @@ func generateMessageWidget(p stanza.Packet, blocked bool) (gtk.Widgetter, bool) mlabel.SetSelectable(true) // mlabel.SetHAlign(gtk.AlignFill) mainBox.Append(mlabel) + + if store_label { + typed_tab.msg_refsID[m.Id].label_ref = mlabel + tabs.Store(target, tab) + } } else { authorBox := gtk.NewBox(gtk.OrientationHorizontal, 10) contentBox := gtk.NewBox(gtk.OrientationHorizontal, 0) @@ -332,7 +344,7 @@ func generateMessageWidget(p stanza.Packet, blocked bool) (gtk.Widgetter, bool) } body := m.Body - body = XEPToPango(body) + body = XEPToPango(body, false) mlabel := gtk.NewLabel("") if m.Body == "" { @@ -348,14 +360,17 @@ func generateMessageWidget(p stanza.Packet, blocked bool) (gtk.Widgetter, bool) 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)) + if store_label { + msg, ok := typed_tab.msg_refsID[m.Id] + if !ok { + msg = new(abMessage) + typed_tab.msg_refsID[m.Id] = msg } - */ + msg.label_ref = mlabel + msg.ocu_id = ocu.ID + tabs.Store(target, typed_tab) + } contentBox.Append(mlabel) mainBox.Append(authorBox) diff --git a/gtk-xmpp-markup.go b/gtk-xmpp-markup.go index 0f8acaf..9708a14 100644 --- a/gtk-xmpp-markup.go +++ b/gtk-xmpp-markup.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "html" "regexp" "strings" @@ -14,7 +15,7 @@ var ( codeRE = regexp.MustCompile("`([^`]+)`") ) -func XEPToPango(text string) string { +func XEPToPango(text string, correction bool) string { formatInline := func(s string) string { s = html.EscapeString(s) s = boldRE.ReplaceAllString(s, "$1") @@ -24,13 +25,12 @@ func XEPToPango(text string) string { return s } - format := func(s string) string { lines := strings.Split(s, "\n") for i, line := range lines { - lines[i] = formatInline(line) + lines[i] = formatInline(line) } return strings.Join(lines, "\n") @@ -56,5 +56,9 @@ func XEPToPango(text string) string { b.WriteString(format(text[last:])) - return b.String() + str := b.String() + if correction { + str = fmt.Sprintf("%s %s", str, loadedLocale["correction"]) + } + return str } diff --git a/l18n.go b/l18n.go index 63d82f7..a004ca4 100644 --- a/l18n.go +++ b/l18n.go @@ -29,6 +29,7 @@ var enGB = map[string]string{ // British English "joinMUCJIDEntry": "MUC JID:", "joinMUCNickEntry": "Nick:", "joinMUCDiscoCheck": "Check MUC features before joining", + "joinMUCBookmarkCheck": "Add to bookmarks", "joinMUCDiscoCheckTooltip": "If you are creating a MUC through this window then turn this off", "joinPreviewTitle": "Joining ", "joinPasswordRequired": "Password required", @@ -91,7 +92,7 @@ var enGB = map[string]string{ // British English "affiliatedAs": "Affiliated as ", "unaffiliated": "Unaffiliated", "participants": "participant(s)", - "versionQueryEmpty": "Client responded with empty version", + "versionQueryEmpty": "You do not have permission to view this client's version", "versionQueryError": "Got error trying to get version", "away": "Away", @@ -118,6 +119,9 @@ var enGB = map[string]string{ // British English "updateSave": "Lambda %s check the upstream server for updates. You can change this at any time via preferences.", "updateWill": "will", "updateWont": "won't", + + // gtk-xmpp-markup.go + "correction": "[corrected] ", } var kaGE = map[string]string{ // Georgian (Georgia) diff --git a/main.go b/main.go index 19c8186..4483577 100644 --- a/main.go +++ b/main.go @@ -18,7 +18,6 @@ import ( "github.com/gen2brain/beeep" "github.com/go-analyze/charts" "github.com/google/uuid" - "golang.org/x/net/html/charset" "time" @@ -28,7 +27,6 @@ import ( _ "embed" "encoding/xml" - "io" "runtime" "github.com/kr/pretty" @@ -44,6 +42,9 @@ var connectionIcon *gtk.Image var mStatus *gtk.Label var mIcon *gtk.Image +var gStatus *gtk.Label +var gIcon *gtk.Image + /* var sStatus *gtk.Label var sIcon *gtk.Image @@ -141,10 +142,7 @@ func main() { config := xmpp.Config{ TransportConfiguration: xmpp.TransportConfiguration{ - Address: loadedConfig.Server, - CharsetReader: func(c string, input io.Reader) (io.Reader, error) { - return charset.NewReaderLabel(c, input) - }, + Address: loadedConfig.Server, ConnectTimeout: 300, TLSConfig: &tls.Config{InsecureSkipVerify: loadedConfig.Insecure}, }, @@ -170,15 +168,14 @@ func main() { // All requests from either ourselves or our server will always be accepted regardless of user settings. - if !(loadedConfig.DiscoPrivacy == 0) && !((JidMustParse(iq.From).Bare() == JidMustParse(clientroot.Session.BindJid).Bare()) || (JidMustParse(iq.From).Domain == JidMustParse(clientroot.Session.BindJid).Domain)) { if loadedConfig.DiscoPrivacy == None || (loadedConfig.DiscoPrivacy == OnlyRoster && !Roster[JidMustParse(iq.From).Bare()]) { - fmt.Println("blocking", loadedConfig.DiscoPrivacy) if loadedConfig.IgnoreInsteadOfReturnErrorUponPrivacyViolation { return } iqResp.Type = "error" + iqResp.From = clientroot.Session.BindJid stanza_err := new(stanza.Err) stanza_err.Type = stanza.ErrorTypeCancel @@ -190,7 +187,6 @@ func main() { } } - identity := stanza.Identity{ Name: "Lambda", Category: "client", // TODO: Allow spoofing on user request @@ -235,12 +231,12 @@ func main() { if !(loadedConfig.VersionPrivacy == 0) && !((JidMustParse(iq.From).Bare() == JidMustParse(clientroot.Session.BindJid).Bare()) || (JidMustParse(iq.From).Domain == JidMustParse(clientroot.Session.BindJid).Domain)) { if loadedConfig.VersionPrivacy == None || (loadedConfig.VersionPrivacy == OnlyRoster && !Roster[JidMustParse(iq.From).Bare()]) { - fmt.Println("blocking", loadedConfig.DiscoPrivacy) if loadedConfig.IgnoreInsteadOfReturnErrorUponPrivacyViolation { return } iqResp.Type = "error" + iqResp.From = clientroot.Session.BindJid stanza_err := new(stanza.Err) stanza_err.Type = stanza.ErrorTypeCancel @@ -283,6 +279,7 @@ func main() { } originator := JidMustParse(m.From).Bare() + glib.IdleAdd(func() { mStatus.SetText(originator) }) @@ -345,6 +342,28 @@ func main() { return } + // Handle corrections BEFORE attempting to generate a message + correction := new(Correction) + ok = m.Get(correction) + if ok { + tab, ok := tabs.Load(originator) + if ok { + typed_tab := tab.(*chatTab) + if ok { + msg, ok := typed_tab.msg_refsID[correction.ID] + if ok { + if msg.ocu_id == oi.ID && msg.ocu_id != "" { + lab := msg.label_ref + glib.IdleAdd(func() { + lab.SetMarkup(XEPToPango(m.Body, true)) + }) + } + return + } + } + } + } + glib.IdleAdd(func() { b := gtk.NewBox(gtk.OrientationVertical, 0) @@ -363,7 +382,7 @@ func main() { fmt.Println("Got message when the tab does not exist!") } - untyped_box, valid := generateMessageWidget(p, blocked) + untyped_box, valid := generateMessageWidget(p, blocked, originator) ba, converted_successfully := untyped_box.(*gtk.Box) if valid && converted_successfully { @@ -505,11 +524,18 @@ func main() { // client = c // clientroot = c - // Ping go func() { for { time.Sleep(5 * time.Second) go func() { + // Goroutine counter + goroutines := runtime.NumGoroutine() + glib.IdleAdd(func() { + gStatus.SetText(fmt.Sprintf("%d goroutines", goroutines)) + }) + }() + go func() { + // Ping pingStatus.AddCSSClass("pending") before := time.Now() iq := new(stanza.IQ) @@ -971,10 +997,12 @@ func activate(app *gtk.Application) { jid_box := gtk.NewBox(gtk.OrientationHorizontal, 10) nick_box := gtk.NewBox(gtk.OrientationHorizontal, 10) disco_box := gtk.NewBox(gtk.OrientationHorizontal, 10) + bookmark_box := gtk.NewBox(gtk.OrientationHorizontal, 10) jid_entry := gtk.NewEntry() nick_entry := gtk.NewEntry() disco_check := gtk.NewCheckButton() + bookmark_check := gtk.NewCheckButton() jid_entry.SetHAlign(gtk.AlignEnd) jid_entry.SetHExpand(true) @@ -1004,9 +1032,14 @@ func activate(app *gtk.Application) { disco_box.Append(disco_check) disco_box.SetTooltipText(loadedLocale["joinMUCDiscoCheckTooltip"]) + bookmark_check.SetActive(true) + bookmark_box.Append(gtk.NewLabel(loadedLocale["joinMUCBookmarkCheck"])) + bookmark_box.Append(bookmark_check) + box.Append(jid_box) box.Append(nick_box) box.Append(disco_box) + box.Append(bookmark_box) btn := gtk.NewButtonWithLabel(loadedLocale["submit"]) btn.SetVAlign(gtk.AlignBaseline) @@ -1035,15 +1068,7 @@ func activate(app *gtk.Application) { im.SetPixelSize(40) go getAvatar(t, t, im) - /* - go func() { - new_im := getAvatar(t, t) - glib.IdleAdd(func() { - new_im.SetPixelSize(40) - box.Prepend(new_im) - }) - }() - */ + b := gtk.NewLabel(n) box.Append(im) @@ -1065,6 +1090,13 @@ func activate(app *gtk.Application) { box.AddController(gesture2) menu.Append(box) menu.Append(gtk.NewSeparator(gtk.OrientationHorizontal)) + + if bookmark_check.Active() { + _, err := addPEPBookmark(client, t, n, nick_entry.Text(), true, pw) + if err != nil { + panic(err) + } + } } if !ok { @@ -1092,111 +1124,116 @@ func activate(app *gtk.Application) { myIQ.Payload = &stanza.DiscoInfo{} - ctx := context.TODO() + ctx := context.Background() mychan, err := client.SendIQ(ctx, myIQ) if err == nil { result := <-mychan res, ok = result.Payload.(*stanza.DiscoInfo) if ok { - features := res.Features - allowed = false - password_protected := false - password := "" - warning_win := gtk.NewWindow() - warning_win.SetTitle(fmt.Sprintf("%s%s", loadedLocale["joinPreviewTitle"], res.Identity[0].Name)) - warning_win.SetDefaultSize(400, 1) - warning_win.SetResizable(false) + if len(res.Identity) >= 1 { + features := res.Features + allowed = false + password_protected := false + password := "" + warning_win := gtk.NewWindow() + warning_win.SetTitle(fmt.Sprintf("%s%s", loadedLocale["joinPreviewTitle"], res.Identity[0].Name)) + warning_win.SetDefaultSize(400, 1) + warning_win.SetResizable(false) - buttons := gtk.NewBox(gtk.OrientationHorizontal, 10) + buttons := gtk.NewBox(gtk.OrientationHorizontal, 10) - join_button := gtk.NewButtonWithLabel("Join") - join_button.ConnectClicked(func() { - warning_win.SetVisible(false) - if password_protected { - allowed = false + join_button := gtk.NewButtonWithLabel("Join") + join_button.ConnectClicked(func() { + warning_win.SetVisible(false) + if password_protected { + allowed = false - password_win := gtk.NewWindow() - password_win.SetTitle(loadedLocale["joinPasswordRequired"]) - password_win.SetDefaultSize(400, 1) - password_win.SetResizable(false) - box := gtk.NewBox(gtk.OrientationVertical, 10) - en := gtk.NewPasswordEntry() - submit := gtk.NewButtonWithLabel(loadedLocale["submit"]) - submit.ConnectClicked(func() { - password = en.Text() - jm(res.Identity[0].Name, password) - password_win.SetVisible(false) - }) - box.Append(en) - box.Append(submit) - password_win.SetChild(box) - password_win.SetVisible(true) + password_win := gtk.NewWindow() + password_win.SetTitle(loadedLocale["joinPasswordRequired"]) + password_win.SetDefaultSize(400, 1) + password_win.SetResizable(false) + box := gtk.NewBox(gtk.OrientationVertical, 10) + en := gtk.NewPasswordEntry() + submit := gtk.NewButtonWithLabel(loadedLocale["submit"]) + submit.ConnectClicked(func() { + password = en.Text() + jm(res.Identity[0].Name, password) + password_win.SetVisible(false) + }) + box.Append(en) + box.Append(submit) + password_win.SetChild(box) + password_win.SetVisible(true) + } + + jm(res.Identity[0].Name, password) + }) + + cancel_button := gtk.NewButtonWithLabel(loadedLocale["cancel"]) + cancel_button.ConnectClicked(func() { + warning_win.SetVisible(false) + }) + + join_button.SetHExpand(true) + cancel_button.SetHExpand(true) + + buttons.Append(join_button) + buttons.Append(cancel_button) + warning_box := gtk.NewBox(gtk.OrientationVertical, 10) + header := gtk.NewLabel(res.Identity[0].Name) + warning_box.Append(header) + addFeature := func(icon string, description string) { + box := gtk.NewBox(gtk.OrientationHorizontal, 10) + box.Append(gtk.NewImageFromPaintable(clientAssetsLoad(icon))) + box.Append(gtk.NewLabel(description)) + warning_box.Append(box) } - jm(res.Identity[0].Name, password) - }) - - cancel_button := gtk.NewButtonWithLabel(loadedLocale["cancel"]) - cancel_button.ConnectClicked(func() { - warning_win.SetVisible(false) - }) - - join_button.SetHExpand(true) - cancel_button.SetHExpand(true) - - buttons.Append(join_button) - buttons.Append(cancel_button) - warning_box := gtk.NewBox(gtk.OrientationVertical, 10) - header := gtk.NewLabel(res.Identity[0].Name) - warning_box.Append(header) - addFeature := func(icon string, description string) { - box := gtk.NewBox(gtk.OrientationHorizontal, 10) - box.Append(gtk.NewImageFromPaintable(clientAssetsLoad(icon))) - box.Append(gtk.NewLabel(description)) - warning_box.Append(box) - } - - for _, feature := range features { - switch feature.Var { - case "muc_passwordprotected": - password_protected = true - addFeature("muc_passwordprotected", loadedLocale["muc_passwordprotected_description"]) - case "muc_unsecured": - addFeature("muc_unsecured", loadedLocale["muc_unsecured_description"]) - case "muc_membersonly": - addFeature("muc_membersonly", loadedLocale["muc_membersonly_description"]) - case "muc_open": - addFeature("muc_open", loadedLocale["muc_open_description"]) - case "muc_moderated": - addFeature("muc_moderated", loadedLocale["muc_moderated_description"]) - case "muc_unmoderated": - addFeature("muc_unmoderated", loadedLocale["muc_unmoderated_description"]) - case "muc_nonanonymous": - addFeature("muc_nonanonymous", loadedLocale["muc_nonanonymous_description"]) - case "muc_semianonymous": - addFeature("muc_semianonymous", loadedLocale["muc_semianonymous_description"]) - case "muc_persistent": - addFeature("muc_persistent", loadedLocale["muc_persistent_description"]) - case "muc_temporary": - addFeature("muc_temporary", loadedLocale["muc_temporary_description"]) - case "muc_public": - addFeature("muc_public", loadedLocale["muc_public_description"]) - case "muc_hidden": - addFeature("muc_hidden", loadedLocale["muc_hidden_description"]) - case "urn:xmpp:mam:0": - addFeature("ok", loadedLocale["urn:xmpp:mam_description"]) - case "urn:xmpp:message-moderate:0": - addFeature("moderate", loadedLocale["urn:xmpp:message-moderate_description"]) - /* - default: - addFeature("comment", feature.Var) - */ + for _, feature := range features { + switch feature.Var { + case "muc_passwordprotected": + password_protected = true + addFeature("muc_passwordprotected", loadedLocale["muc_passwordprotected_description"]) + case "muc_unsecured": + addFeature("muc_unsecured", loadedLocale["muc_unsecured_description"]) + case "muc_membersonly": + addFeature("muc_membersonly", loadedLocale["muc_membersonly_description"]) + case "muc_open": + addFeature("muc_open", loadedLocale["muc_open_description"]) + case "muc_moderated": + addFeature("muc_moderated", loadedLocale["muc_moderated_description"]) + case "muc_unmoderated": + addFeature("muc_unmoderated", loadedLocale["muc_unmoderated_description"]) + case "muc_nonanonymous": + addFeature("muc_nonanonymous", loadedLocale["muc_nonanonymous_description"]) + case "muc_semianonymous": + addFeature("muc_semianonymous", loadedLocale["muc_semianonymous_description"]) + case "muc_persistent": + addFeature("muc_persistent", loadedLocale["muc_persistent_description"]) + case "muc_temporary": + addFeature("muc_temporary", loadedLocale["muc_temporary_description"]) + case "muc_public": + addFeature("muc_public", loadedLocale["muc_public_description"]) + case "muc_hidden": + addFeature("muc_hidden", loadedLocale["muc_hidden_description"]) + case "urn:xmpp:mam:0": + addFeature("ok", loadedLocale["urn:xmpp:mam_description"]) + case "urn:xmpp:message-moderate:0": + addFeature("moderate", loadedLocale["urn:xmpp:message-moderate_description"]) + /* + default: + addFeature("comment", feature.Var) + */ + } } - } - warning_box.Append(buttons) - warning_win.SetChild(warning_box) - warning_win.Present() + warning_box.Append(buttons) + warning_win.SetChild(warning_box) + warning_win.Present() + } else { + allowed = false + showErrorDialog(fmt.Errorf(loadedLocale["discoFail"]), win) + } } else { allowed = false if result.Error != nil { @@ -1274,9 +1311,16 @@ func activate(app *gtk.Application) { mStatus = gtk.NewLabel("-") mStatus.AddController(gesture1) + gIcon = gtk.NewImageFromPaintable((clientAssetsLoad("timeline_marker"))) + gIcon.AddCSSClass("icon") + gStatus = gtk.NewLabel("...") + cBox.Append(mIcon) cBox.Append(mStatus) + cBox.Append(gIcon) + cBox.Append(gStatus) + statBar.Append(mBox) pBox := gtk.NewBox(gtk.OrientationHorizontal, 0) diff --git a/style.css b/style.css index b8ae471..4886abf 100644 --- a/style.css +++ b/style.css @@ -112,3 +112,17 @@ text-decoration: underline; color: green; } + +.rainbow { + background: linear-gradient(to right, #ef5350, #f48fb1, #7e57c2, #2196f3, #26c6da, #43a047, #eeff41, #f9a825, #ff5722); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +.correction { + font-style: italic; + background-color: white; + border-radius: 5px; + color: black; + +} diff --git a/types.go b/types.go index 505ae1b..21d4b1f 100644 --- a/types.go +++ b/types.go @@ -7,9 +7,15 @@ import ( "sync" ) +type abMessage struct { + label_ref *gtk.Label + ocu_id string +} + type chatTab struct { isMuc bool msgs *gtk.ListBox + msg_refsID map[string]*abMessage name string current_nick string muc_presence *stanza.Presence @@ -18,17 +24,17 @@ type chatTab struct { type BlockingMode int const ( - Drop BlockingMode = iota // Do not even process if the entity is blocked - Hide // Hide the message but allow the user to expand it if they wish (discord style) - Highlight // Just highlight the message in orange + Drop BlockingMode = iota // Do not even process if the entity is blocked + Hide // Hide the message but allow the user to expand it if they wish (discord style) + Highlight // Just highlight the message in orange ) type DiscoveryMode int const ( - Everyone DiscoveryMode = iota // All entities can query - OnlyRoster // Only people on your roster can query - None // Nobody can query + Everyone DiscoveryMode = iota // All entities can query + OnlyRoster // Only people on your roster can query + None // Nobody can query ) type lambdaConfig struct { @@ -60,7 +66,7 @@ type lambdaConfig struct { RandomizeResource bool // If this is set to true, then the resource will be completely randomized by the server on every single connect // Privacy settings - DiscoPrivacy DiscoveryMode // Whether or not Lambda will report its service discovery features and Identity. Reccomended mode is Everyone, Default is Everyone + DiscoPrivacy DiscoveryMode // Whether or not Lambda will report its service discovery features and Identity. Reccomended mode is Everyone, Default is Everyone VersionPrivacy DiscoveryMode // Whether or not Lambda will report its version information. Reccomended mode is Roster, Default is Everyone IgnoreInsteadOfReturnErrorUponPrivacyViolation bool // If set to true, Lambda will simply ignore Disco and Query Version requests from entities that are untrusted as per the user's discovery mode. If set to false, a service-unavailable error will be reported. diff --git a/version.go b/version.go index feb7fed..cf1c369 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package main -var lambda_version string = "7" +var lambda_version string = "8" diff --git a/xmpp-bookmarks.go b/xmpp-bookmarks.go index 1f86565..0997c0f 100644 --- a/xmpp-bookmarks.go +++ b/xmpp-bookmarks.go @@ -2,6 +2,7 @@ package main import ( "encoding/xml" + "fmt" "gosrc.io/xmpp/stanza" ) @@ -17,6 +18,38 @@ type Bookmark struct { Extensions []any `xml:"extensions,omitempty"` } +func (b *Bookmark) ToNode() *stanza.Node { + node := &stanza.Node{ + XMLName: xml.Name{Space: "urn:xmpp:bookmarks:1", Local: "conference"}, + Attrs: []xml.Attr{ + {Name: xml.Name{Local: "name"}, Value: b.Name}, + {Name: xml.Name{Local: "autojoin"}, Value: fmt.Sprintf("%t", b.Autojoin)}, + }, + } + + if b.Nick != "" { + node.Nodes = append(node.Nodes, stanza.Node{ + XMLName: xml.Name{Local: "nick"}, + Content: b.Nick, + }) + } + + if b.Password != "" { + node.Nodes = append(node.Nodes, stanza.Node{ + XMLName: xml.Name{Local: "password"}, + Content: b.Password, + }) + } + + for _, ext := range b.Extensions { + if extNode, ok := ext.(stanza.Node); ok { + node.Nodes = append(node.Nodes, extNode) + } + } + + return node +} + func init() { stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{Space: "urn:xmpp:bookmarks:1", Local: "conference"}, Bookmark{}) } diff --git a/xmpp-corrections.go b/xmpp-corrections.go new file mode 100644 index 0000000..959f69c --- /dev/null +++ b/xmpp-corrections.go @@ -0,0 +1,20 @@ +package main + +import ( + "encoding/xml" + "gosrc.io/xmpp/stanza" +) + +// Implementation of XEP-0308: Last Message Correction +// XEP is supposed to only allow you to correct last message and only show corrections to the user's last message +// Unfortunately, I do not give a fuck and will let any messages be corrected + +type Correction struct { + stanza.MsgExtension + XMLName xml.Name `xml:"urn:xmpp:message-correct:0 replace"` + ID string `xml:"id,attr"` +} + +func init() { + stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{Space: "urn:xmpp:message-correct:0", Local: "replace"}, Correction{}) +} diff --git a/xmpp-helpers.go b/xmpp-helpers.go index 375a267..cb3d4ac 100644 --- a/xmpp-helpers.go +++ b/xmpp-helpers.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "gosrc.io/xmpp" "gosrc.io/xmpp/stanza" @@ -140,3 +141,62 @@ func sendVoiceRequest(c xmpp.Sender, sendTo string) error { } return nil } + +func addPEPBookmark(c xmpp.Sender, jid string, name string, nick string, autojoin bool, password string) (stanza.IQ, error) { + item := new(stanza.Item) + item.Id = jid + + bookmark := Bookmark{} + bookmark.Name = name + bookmark.Autojoin = autojoin + bookmark.Nick = nick + bookmark.Password = password + node := bookmark.ToNode() + + options_form := new(stanza.Form) + options_form.Type = "submit" + + persist := new(stanza.Field) + persist.Type = "boolean" + persist.Var = "pubsub#persist_items" + persist.ValuesList = []string{"true"} + options_form.Fields = append(options_form.Fields, persist) + + max_items := new(stanza.Field) + max_items.Type = "text-single" + max_items.Var = "pubsub#max_items" + max_items.ValuesList = []string{"max"} + options_form.Fields = append(options_form.Fields, max_items) + + send_last_published_item := new(stanza.Field) + send_last_published_item.Type = "text-single" + send_last_published_item.Var = "pubsub#send_last_published_item" + send_last_published_item.ValuesList = []string{"never"} + options_form.Fields = append(options_form.Fields, send_last_published_item) + + access_model := new(stanza.Field) + access_model.Type = "text-single" + access_model.Var = "pubsub#access_model" + access_model.ValuesList = []string{"whitelist"} + options_form.Fields = append(options_form.Fields, access_model) + + options := new(stanza.PublishOptions) + options.Form = options_form + + item.Any = node + items := []stanza.Item{*item} + pubsub, err := stanza.NewPublishItemOptsRq("", "urn:xmpp:bookmarks:1", items, options) + if err != nil { + return stanza.IQ{}, err + } + ctx := context.TODO() + + channel, err := c.SendIQ(ctx, pubsub) + if err != nil { + return stanza.IQ{}, err + } + + result := <-channel + + return result, nil +}