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
+}