ban, kick and change role&affil menu, status icons in muc, blocking avatars that are invalid so we dont fetch them over and over again

This commit is contained in:
2026-03-15 09:55:26 +00:00
parent a7e90e4ae5
commit 71e6a58fbd
13 changed files with 256 additions and 22 deletions

View File

@@ -39,6 +39,27 @@ var outcastMedalB64 string = base64.StdEncoding.EncodeToString(outcastMedalBytes
var cancelBytes []byte
var cancelB64 string = base64.StdEncoding.EncodeToString(cancelBytes)
//go:embed assets/status_away.png
var sABytes []byte
var sAB64 string = base64.StdEncoding.EncodeToString(sABytes)
//go:embed assets/status_busy.png
var sBBytes []byte
var sBB64 string = base64.StdEncoding.EncodeToString(sBBytes)
//go:embed assets/status_chatty.png
var sCBytes []byte
var sCB64 string = base64.StdEncoding.EncodeToString(sCBytes)
//go:embed assets/status_online.png
var sOBytes []byte
var sOB64 string = base64.StdEncoding.EncodeToString(sOBytes)
//go:embed assets/status_xa.png
var xaBytes []byte
var xaB64 string = base64.StdEncoding.EncodeToString(xaBytes)
//go:embed assets/tag.png
var tagBytes []byte
var tagB64 string = base64.StdEncoding.EncodeToString(tagBytes)
@@ -104,7 +125,6 @@ func init() {
loader.Close()
clientAssets["DefaultAvatar"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
loader = gdkpixbuf.NewPixbufLoader()
failedData, _ := base64.StdEncoding.DecodeString(failedB64)
@@ -272,4 +292,46 @@ func init() {
loader.Close()
clientAssets["information"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
loader = gdkpixbuf.NewPixbufLoader()
sAData, _ := base64.StdEncoding.DecodeString(sAB64)
loader.Write(sAData)
loader.Close()
clientAssets["status_away"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
loader = gdkpixbuf.NewPixbufLoader()
sBData, _ := base64.StdEncoding.DecodeString(sBB64)
loader.Write(sBData)
loader.Close()
clientAssets["status_dnd"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
loader = gdkpixbuf.NewPixbufLoader()
sCData, _ := base64.StdEncoding.DecodeString(sCB64)
loader.Write(sCData)
loader.Close()
clientAssets["status_chat"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
loader = gdkpixbuf.NewPixbufLoader()
xaData, _ := base64.StdEncoding.DecodeString(xaB64)
loader.Write(xaData)
loader.Close()
clientAssets["status_xa"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
loader = gdkpixbuf.NewPixbufLoader()
sOData, _ := base64.StdEncoding.DecodeString(sOB64)
loader.Write(sOData)
loader.Close()
clientAssets["status_"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
}

BIN
assets/status_away.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

BIN
assets/status_busy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 751 B

BIN
assets/status_chatty.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
assets/status_online.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 B

BIN
assets/status_xa.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -19,7 +19,7 @@ import (
var textureCache = make(map[string]gdk.Paintabler)
// Invalid images, if an image/avatar cannot be loaded on the system (e.g: incompatible format) it's put here
var invalidImages = make(map[string]bool)
var invalidImages = make(map[string]bool)
func ensureCache() (string, error) {
cachePath := configdir.LocalCache("lambda-im")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@@ -3,6 +3,7 @@ package main
import (
"context"
"fmt"
"github.com/diamondburned/gotk4/pkg/gdk/v4"
"github.com/diamondburned/gotk4/pkg/glib/v2"
"github.com/diamondburned/gotk4/pkg/gtk/v4"
"github.com/diamondburned/gotk4/pkg/pango"
@@ -82,7 +83,7 @@ func switchToTab(jid string, w *gtk.Window) {
nick_label.SetOpacity(0.5)
}
userbox.SetTooltipText(fmt.Sprintf("%s\n%s\n%s\nRight-click for more information", u.From, mu.MucUserItem.Role, mu.MucUserItem.Affiliation))
userbox.SetTooltipText(fmt.Sprintf("%s\n%s\n%s\nClick for more information", u.From, mu.MucUserItem.Role, mu.MucUserItem.Affiliation))
userbox.Append(nick_label)
var hats Hats
@@ -95,6 +96,13 @@ func switchToTab(jid string, w *gtk.Window) {
}
}
status := gtk.NewImageFromPaintable(clientAssets["status_"+string(u.Show)])
status.SetTooltipText(string(u.Show))
status.SetHAlign(gtk.AlignEnd)
// medal.SetHExpand(true)
userbox.Prepend(status)
medal := gtk.NewImageFromPaintable(clientAssets[mu.MucUserItem.Affiliation])
medal.SetTooltipText(mu.MucUserItem.Affiliation)
@@ -103,7 +111,145 @@ func switchToTab(jid string, w *gtk.Window) {
userbox.Append(medal)
gesture := gtk.NewGestureClick()
gesture.SetButton(3) // Right click
gesture.SetButton(1)
mod_gesture := gtk.NewGestureClick()
mod_gesture.SetButton(3)
popover := gtk.NewPopover()
popover.SetHasArrow(false)
popover.SetParent(userbox)
rc_box := gtk.NewBox(gtk.OrientationVertical, 0)
bb := gtk.NewButtonWithLabel("Ban")
kb := gtk.NewButtonWithLabel("Kick")
ab := gtk.NewButtonWithLabel("Set affil")
rb := gtk.NewButtonWithLabel("Set role")
kb.ConnectClicked(func() {
client.SendRaw(fmt.Sprintf(`
<iq from='%s'
id='kick1'
to='%s'
type='set'>
<query xmlns='http://jabber.org/protocol/muc#admin'>
<item nick='%s' role='none'>
</item>
</query>
</iq>
`, clientroot.Session.BindJid, jid, JidMustParse(u.From).Resource))
})
bb.ConnectClicked(func() {
var mu MucUser
ok = u.Get(&mu)
if ok {
if mu.MucUserItem.JID != "" {
client.SendRaw(fmt.Sprintf(`
<iq from='%s'
id='ban1'
to='%s'
type='set'>
<query xmlns='http://jabber.org/protocol/muc#admin'>
<item affiliation='outcast' jid='%s'/>
</query>
</iq>
`, clientroot.Session.BindJid, jid, JidMustParse(mu.MucUserItem.JID).Bare()))
}
}
})
ab.ConnectClicked(func() {
var mu MucUser
ok = u.Get(&mu)
if ok {
if mu.MucUserItem.JID != "" {
win := gtk.NewWindow()
win.SetDefaultSize(400, 1)
win.SetResizable(false)
box := gtk.NewBox(gtk.OrientationVertical, 0)
box.Append(gtk.NewLabel("Set "+JidMustParse(u.From).Resource+"'s affiliation"))
the_entry := gtk.NewEntry()
the_entry.SetText(mu.MucUserItem.Affiliation)
submit := gtk.NewButtonWithLabel("Submit")
submit.ConnectClicked(func() {
client.SendRaw(fmt.Sprintf(`
<iq from='%s'
id='ba1'
to='%s'
type='set'>
<query xmlns='http://jabber.org/protocol/muc#admin'>
<item affiliation='%s' jid='%s'/>
</query>
</iq>
`, clientroot.Session.BindJid, jid, the_entry.Text(), JidMustParse(mu.MucUserItem.JID).Bare()))
win.SetVisible(false)
})
box.Append(the_entry)
box.Append(submit)
win.SetChild(box)
win.SetVisible(true)
}}
})
rb.ConnectClicked(func() {
var mu MucUser
ok = u.Get(&mu)
if ok {
if mu.MucUserItem.JID != "" {
win := gtk.NewWindow()
win.SetDefaultSize(400, 1)
win.SetResizable(false)
box := gtk.NewBox(gtk.OrientationVertical, 0)
box.Append(gtk.NewLabel("Set "+JidMustParse(u.From).Resource+"'s role"))
box.Append(gtk.NewLabel("Important: if you want this to be permanent, set their affiliation instead"))
the_entry := gtk.NewEntry()
the_entry.SetText(mu.MucUserItem.Role)
submit := gtk.NewButtonWithLabel("Submit")
submit.ConnectClicked(func() {
client.SendRaw(fmt.Sprintf(`
<iq from='%s'
id='kick1'
to='%s'
type='set'>
<query xmlns='http://jabber.org/protocol/muc#admin'>
<item nick='%s' role='%s'>
</item>
</query>
</iq>
`, clientroot.Session.BindJid, jid, JidMustParse(u.From).Resource, the_entry.Text()))
})
box.Append(the_entry)
box.Append(submit)
win.SetChild(box)
win.SetVisible(true)
}}
})
rc_box.Append(bb)
rc_box.Append(kb)
rc_box.Append(ab)
rc_box.Append(rb)
popover.SetChild(rc_box)
mod_gesture.Connect("pressed", func(n_press, x, y int) {
rect := gdk.NewRectangle(x, y, 1, 1)
popover.SetPointingTo(&rect)
popover.Popup()
})
gesture.Connect("pressed", func(n_press, x, y int) {
win := gtk.NewWindow()
@@ -218,8 +364,13 @@ func switchToTab(jid string, w *gtk.Window) {
version := ver.Version
os := ver.OS
ver_text.SetText(fmt.Sprintf("%s %s %s", name, version, os))
ver_text.RemoveCSSClass("visitor")
vr := fmt.Sprintf("%s %s %s", name, version, os)
if name == "" && version == "" && os == "" {
ver_text.SetText("Client responded with empty version")
} else {
ver_text.SetText(vr)
ver_text.RemoveCSSClass("visitor")
}
} else if result.Error != nil && result.Error.Type != "" {
ver_text.SetText("Got error trying to get version")
ver_text.SetTooltipText(result.Error.Reason + ": " + result.Error.Text)
@@ -262,7 +413,10 @@ func switchToTab(jid string, w *gtk.Window) {
win.SetTransientFor(win)
win.Present()
})
userbox.AddController(gesture)
userbox.AddController(mod_gesture)
if mu.MucUserItem.Role == "moderator" {
gen.Prepend(userbox)
} else {

31
main.go
View File

@@ -1,8 +1,8 @@
package main
import (
"strings"
"os"
"strings"
"sync"
"context"
@@ -188,8 +188,6 @@ func main() {
return
}
pretty.Println(m)
e := stanza.PubSubEvent{}
ok = m.Get(&e)
if ok {
@@ -211,6 +209,15 @@ func main() {
beeep.Notify("Attention", fmt.Sprintf("%s: %s", JidMustParse(m.From).Resource, m.Body), commentBytes) // TODO: Use localpart if DM
}
// Handle mentions
for _, ext := range m.Extensions {
mention, ok := ext.(*Mention)
if ok {
pretty.Println(mention)
}
}
sc := new(SentCarbon)
ok = m.Get(sc)
if ok {
@@ -492,7 +499,7 @@ func main() {
}
})
go func() {
conc := func() {
time.Sleep(3 * time.Second)
connectionStatus.SetText("Connecting...")
connectionIcon.SetFromPaintable(clientAssets["hourglass"])
@@ -503,10 +510,13 @@ func main() {
connectionStatus.SetText(fmt.Sprintf("Disconnected: %s", err.Error()))
connectionIcon.SetFromPaintable(clientAssets["disconnect"])
}
}()
}
app := gtk.NewApplication("net.sunglocto.lambda", gio.ApplicationFlagsNone)
app.ConnectActivate(func() { activate(app) })
app.ConnectActivate(func() {
go conc()
activate(app)
})
if code := app.Run(os.Args); code > 0 {
os.Exit(code)
@@ -870,8 +880,15 @@ func activate(app *gtk.Application) {
}
if strings.Contains(t, "@everyone") {
start := strings.Index(t, "@everyone")
end := start + len("@everyone")
new_mention := new(Mention)
new_mention.Mentions = "urn:xmpp:mentions:0#channel"
new_mention.Type = "urn:xmpp:mentions:0#channel"
new_mention.Begin = start
new_mention.End = end
exts = append(exts, new_mention)
} else if strings.Contains(t, "@here") {
new_attention := new(Attention)

View File

@@ -2,6 +2,7 @@ package main
import (
"github.com/diamondburned/gotk4/pkg/gtk/v4"
"mellium.im/xmpp/color"
"sync"
)
@@ -18,10 +19,11 @@ type lambdaConfig struct {
Insecure bool
Nick string
JoinBookmarks bool
CVD color.CVD
}
type mucUnit struct {
// key: OccupantID
// key: Resource
// value: last user presence
Members sync.Map
}

View File

@@ -1,3 +1,3 @@
package main
var lambda_version string = "0.1.0"
var lambda_version string = "26w11a"

View File

@@ -10,13 +10,12 @@ import (
type Mention struct {
stanza.MsgExtension
XMLName xml.Name `xml:"urn:xmpp:mentions:0 mention"`
Mentions string `xml:"mentions,attr,omitempty"`
URI string `xml:"uri,attr,omitempty"`
Begin int `xml:"begin,attr,omitempty"`
End int `xml:"end,attr,omitempty"`
OccupantID string `xml:"occupantid,attr,omitempty"`
JID string `xml:"ji,attr,omitempty"`
XMLName xml.Name `xml:"urn:xmpp:mentions:0 mention"`
URI string `xml:"uri,attr,omitempty"`
Begin int `xml:"begin,attr,omitempty"`
End int `xml:"end,attr,omitempty"`
Type string `xml:"type,attr"`
Target string `xml:"target,attr,omitempty"`
}
func init() {