BOOKMARKS! BOOKMARKS! WE GOT BOOKMARKS PEOPLE

This commit is contained in:
2026-02-16 09:52:25 +00:00
parent 09a809c102
commit e026e777f6
11 changed files with 160 additions and 17 deletions

View File

@@ -4,4 +4,4 @@ an XMPP client
icons are from Psi+ ([https://github.com/psi-im](https://github.com/psi-im))
additional icons are by Mark James's Silk Icon Set [https://peacocksoftware.com/sites/peacocksoftware/silk_icons/comment.png](https://peacocksoftware.com/sites/peacocksoftware/silk_icons/comment.png)
additional icons are by Mark James's Silk Icon Set [https://github.com/markjames/famfamfam-silk-icons](https://github.com/markjames/famfamfam-silk-icons)

BIN
assets/ban.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

BIN
assets/door_in.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 B

BIN
assets/door_out.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

BIN
assets/large_group.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 747 B

BIN
assets/world.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 B

View File

@@ -253,7 +253,13 @@ func switchToTab(jid string, w *gtk.Window) {
})
headerBox := gtk.NewBox(gtk.OrientationHorizontal, 0)
headerBox.Append(gtk.NewImageFromPaintable(clientAssets["group"]))
if i >= 500 {
headerBox.Append(gtk.NewImageFromPaintable(clientAssets["world"]))
} else if i >= 50 {
headerBox.Append(gtk.NewImageFromPaintable(clientAssets["large_group"]))
} else {
headerBox.Append(gtk.NewImageFromPaintable(clientAssets["group"]))
}
headerBox.Append(gtk.NewLabel(fmt.Sprintf("%d participant(s)", i)))
gen.Prepend(headerBox)

View File

@@ -19,6 +19,7 @@ import (
)
func generatePresenceWidget(p stanza.Packet) gtk.Widgetter {
b := gtk.NewBox(gtk.OrientationHorizontal, 0)
presence, ok := p.(stanza.Presence)
if !ok {
return gtk.NewLabel("Unsupported message.")
@@ -29,14 +30,21 @@ func generatePresenceWidget(p stanza.Packet) gtk.Widgetter {
ok := presence.Get(&mu)
if ok {
if mu.MucUserItem.Affiliation == "outcast" {
return gtk.NewLabel(jid.MustParse(presence.From).Resourcepart() + " has been banned!")
b.Append(gtk.NewImageFromPaintable(clientAssets["outcast"]))
b.Append(gtk.NewLabel(JidMustParse(presence.From).Resource + " has been banned by " + mu.MucUserItem.Actor.Nick + "!"))
return b
}
}
return gtk.NewLabel(JidMustParse(presence.From).Resource + " left the MUC")
b.Append(gtk.NewImageFromPaintable(clientAssets["door_out"]))
b.Append(gtk.NewLabel(JidMustParse(presence.From).Resource))
} else {
return gtk.NewLabel(JidMustParse(presence.From).Resource + " joined the MUC")
b.Append(gtk.NewImageFromPaintable(clientAssets["door_in"]))
b.Append(gtk.NewLabel(JidMustParse(presence.From).Resource))
}
b.SetTooltipText(presence.Status)
return b
}
func generateMessageWidget(p stanza.Packet) gtk.Widgetter {
@@ -65,7 +73,7 @@ func generateMessageWidget(p stanza.Packet) gtk.Widgetter {
if m.Error.Type != "" {
error_box := gtk.NewBox(gtk.OrientationHorizontal, 0)
cancel_img := gtk.NewImageFromPaintable(clientAssets["cancel"])
error_label := gtk.NewLabel(m.Error.Text+ ": ")
error_label := gtk.NewLabel(m.Error.Text + ": ")
error_box.Append(cancel_img)
error_box.Append(error_label)
@@ -147,7 +155,11 @@ func generateMessageWidget(p stanza.Packet) gtk.Widgetter {
// authorBox.Append(im)
al := gtk.NewLabel(jid.MustParse(m.From).Resourcepart())
n := jid.MustParse(m.From).Resourcepart()
if n == "" {
n = jid.MustParse(m.From).String()
}
al := gtk.NewLabel(n)
al.AddCSSClass("author")
al.SetSelectable(true)
@@ -195,6 +207,12 @@ func generateMessageWidget(p stanza.Packet) gtk.Widgetter {
mlabel.SetSelectable(true)
mlabel.SetHAlign(gtk.AlignFill)
mum := MucUser{}
ok = m.Get(&mum)
if ok {
mlabel.SetText(fmt.Sprintf("%s's affiliation has been changed to %s", mum.MucUserItem.JID, mum.MucUserItem.Affiliation))
}
contentBox.Append(mlabel)
mainBox.Append(authorBox)

107
main.go
View File

@@ -90,7 +90,6 @@ var cancelB64 string = base64.StdEncoding.EncodeToString(cancelBytes)
var tagBytes []byte
var tagB64 string = base64.StdEncoding.EncodeToString(tagBytes)
//go:embed assets/lambda-disabled.png
var logoDisabledBytes []byte
var logoDisabledB64 string = base64.StdEncoding.EncodeToString(logoDisabledBytes)
@@ -99,10 +98,28 @@ var logoDisabledB64 string = base64.StdEncoding.EncodeToString(logoDisabledBytes
var groupBytes []byte
var groupB64 string = base64.StdEncoding.EncodeToString(groupBytes)
//go:embed assets/door_in.png
var doorInBytes []byte
var doorInB64 string = base64.StdEncoding.EncodeToString(doorInBytes)
//go:embed assets/door_out.png
var doorOutBytes []byte
var doorOutB64 string = base64.StdEncoding.EncodeToString(doorOutBytes)
//go:embed assets/large_group.png
var largeGroupBytes []byte
var largeGroupB64 string = base64.StdEncoding.EncodeToString(largeGroupBytes)
//go:embed assets/world.png
var worldBytes []byte
var worldB64 string = base64.StdEncoding.EncodeToString(worldBytes)
var clientAssets map[string]gdk.Paintabler = make(map[string]gdk.Paintabler)
var lockedJIDs map[string]bool = make(map[string]bool)
func init() {
beeep.AppName = "Lambda"
go func() {
for fn := range uiQueue {
glib.IdleAdd(func() bool {
@@ -176,7 +193,6 @@ func init() {
clientAssets["outcast"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
loader = gdkpixbuf.NewPixbufLoader()
disabledLogoData, _ := base64.StdEncoding.DecodeString(logoDisabledB64)
@@ -192,6 +208,38 @@ func init() {
loader.Close()
clientAssets["group"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
loader = gdkpixbuf.NewPixbufLoader()
doorInData, _ := base64.StdEncoding.DecodeString(doorInB64)
loader.Write(doorInData)
loader.Close()
clientAssets["door_in"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
loader = gdkpixbuf.NewPixbufLoader()
doorOutData, _ := base64.StdEncoding.DecodeString(doorOutB64)
loader.Write(doorOutData)
loader.Close()
clientAssets["door_out"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
loader = gdkpixbuf.NewPixbufLoader()
largeGroupData, _ := base64.StdEncoding.DecodeString(largeGroupB64)
loader.Write(largeGroupData)
loader.Close()
clientAssets["large_group"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
loader = gdkpixbuf.NewPixbufLoader()
worldData, _ := base64.StdEncoding.DecodeString(worldB64)
loader.Write(worldData)
loader.Close()
clientAssets["world"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
}
func main() {
@@ -368,8 +416,8 @@ func main() {
_, ok := typed_unit.Members.Load(id)
if !ok {
glib.IdleAdd(func() {
b := gtk.NewLabel("")
ba, ok := generatePresenceWidget(p).(*gtk.Label)
b := gtk.NewBox(gtk.OrientationVertical, 0)
ba, ok := generatePresenceWidget(p).(*gtk.Box)
if ok {
b = ba
}
@@ -389,8 +437,8 @@ func main() {
} else {
typed_unit.Members.Delete(id)
glib.IdleAdd(func() {
b := gtk.NewLabel("")
ba, ok := generatePresenceWidget(p).(*gtk.Label)
b := gtk.NewBox(gtk.OrientationVertical, 0)
ba, ok := generatePresenceWidget(p).(*gtk.Box)
if ok {
b = ba
}
@@ -462,8 +510,49 @@ func main() {
cm := xmpp.NewStreamManager(c, func(c xmpp.Sender) {
fmt.Println("XMPP client connected")
/*
*/
books, err := stanza.NewItemsRequest("", "urn:xmpp:bookmarks:1", 0)
if err == nil {
mychan, err := c.SendIQ(context.TODO(), books)
result := <-mychan
if err == nil {
res, ok := result.Payload.(*stanza.PubSubGeneric)
if ok {
for _, item := range res.Items.List {
jid := item.Id
node := item.Any
autojoin := false
nick := loadedConfig.Nick
for _, attr := range node.Attrs {
if attr.Name.Local == "autojoin" {
autojoin = attr.Value == "true"
}
}
for _, node := range node.Nodes {
if node.XMLName.Local == "nick" {
nick = node.Content
}
}
_, ok := tabs.Load(jid)
if !ok && autojoin {
err := joinMuc(client, clientroot.Session.BindJid, jid, nick)
if err != nil {
panic(err)
}
createTab(jid, true)
b := gtk.NewButtonWithLabel(jid)
b.ConnectClicked(func() {
b.AddCSSClass("accent")
switchToTab(jid, &window.Window)
})
menu.Append(b)
}
}
}
}
}
})
go func() {
@@ -509,7 +598,7 @@ func activate(app *gtk.Application) {
destroymucAction := gio.NewSimpleAction("destroymuc", nil)
destroymucAction.ConnectActivate(func(p *glib.Variant) {
cur, ok := tabs.Load(current)
cur, ok := tabs.Load(current)
if ok {
cur := cur.(*chatTab)
if cur.isMuc {

22
xmpp-bookmarks.go Normal file
View File

@@ -0,0 +1,22 @@
package main
import (
"encoding/xml"
"gosrc.io/xmpp/stanza"
)
// Implementation of XEP-0402: PEP Native Bookmarks
// https://xmpp.org/extensions/xep-0402.html
type Bookmark struct {
XMLName xml.Name `xml:"urn:xmpp:bookmarks:1 conference"`
Name string `xml:"name,attr,omitempty"`
Autojoin bool `xml:"autojoin,attr,omitempty"`
Nick string `xml:"nick,omitempty"`
Password string `xml:"password,omitempty"`
Extensions []any `xml:"extensions,omitempty"`
}
func init() {
stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{Space: "urn:xmpp:bookmarks:1", Local: "conference"}, Bookmark{})
}

View File

@@ -10,6 +10,7 @@ import (
type MucUser struct {
stanza.PresExtension
stanza.MsgExtension
XMLName xml.Name `xml:"http://jabber.org/protocol/muc#user x"`
MucUserItem MucUserItem `xml:"item,omitempty"`
}
@@ -19,9 +20,16 @@ type MucUserItem struct {
Affiliation string `xml:"affiliation,attr,omitempty"` // TODO: Use enum
Role string `xml:"role,attr,omitempty"` // TODO: Use enum
JID string `xml:"jid,attr,omitempty"`
Reason string `xml:"reason,omitempty"`
Reason string `xml:"reason,omitempty"`
Actor Actor `xml:"actor,omitempty"`
}
type Actor struct {
JID string `xml:"jid,attr"`
Nick string `xml:"nick,attr"`
}
func init() {
stanza.TypeRegistry.MapExtension(stanza.PKTPresence, xml.Name{Space: "http://jabber.org/protocol/muc#user", Local: "x"}, MucUser{})
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{Space: "http://jabber.org/protocol/muc#user", Local: "x"}, MucUser{})
}