Several changes

This commit is contained in:
2026-01-30 10:40:38 +00:00
parent 1ef42695f0
commit 92b01844c4
9 changed files with 155 additions and 15 deletions

8
TODO.md Normal file
View File

@@ -0,0 +1,8 @@
# TODO
- XEP-0153: vCard-Based Avatars 0%
- XEP-0393: Message Styling 0%
- XEP-0402: PEP Native Bookmarks 0%
- XEP-0066: Out of Band Data partial
- XEP-0461: Message Replies partial
- XEP-0444: Message Reactions partial

View File

@@ -4,19 +4,29 @@ package main
// It also does the same for images that need to be grabbed from HTTP. // It also does the same for images that need to be grabbed from HTTP.
import ( import (
"github.com/diamondburned/gotk4/pkg/gdk/v4"
"github.com/diamondburned/gotk4/pkg/gtk/v4"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"net/http" "github.com/diamondburned/gotk4/pkg/gdk/v4"
"github.com/diamondburned/gotk4/pkg/gtk/v4"
"io" "io"
"net/http"
"os" "os"
"path/filepath"
"github.com/kirsle/configdir"
) )
// global or app-level map/cache // global or app-level map/cache
var textureCache = make(map[string]gdk.Paintabler) var textureCache = make(map[string]gdk.Paintabler)
func ensureCache() (string, error) {
cachePath := configdir.LocalCache("lambda-im")
err := configdir.MakePath(cachePath) // Ensure it exists.
if err != nil {
return "", err
}
return cachePath, nil
}
func getTexture(path string) gdk.Paintabler { func getTexture(path string) gdk.Paintabler {
if tex, exists := textureCache[path]; exists { if tex, exists := textureCache[path]; exists {
@@ -43,6 +53,7 @@ func newImageFromPath(path string) *gtk.Image {
} }
func newPictureFromWeb(url string) *gtk.Picture { func newPictureFromWeb(url string) *gtk.Picture {
pa, _ := ensureCache()
// step 1: get a sha256 sum of the URL // step 1: get a sha256 sum of the URL
sum := fmt.Sprintf("%x", sha256.Sum256([]byte(url))) sum := fmt.Sprintf("%x", sha256.Sum256([]byte(url)))
@@ -62,16 +73,19 @@ func newPictureFromWeb(url string) *gtk.Picture {
return nil return nil
} }
fullpath := filepath.Join(pa, sum)
// step 3: save it // step 3: save it
err = os.WriteFile(sum, b, 0644) err = os.WriteFile(fullpath, b, 0644)
if err != nil { if err != nil {
return nil return nil
} }
return newPictureFromPath(sum) return newPictureFromPath(fullpath)
} }
func newImageFromWeb(url string) *gtk.Image { func newImageFromWeb(url string) *gtk.Image {
pa, _ := ensureCache()
// step 1: get a sha256 sum of the URL // step 1: get a sha256 sum of the URL
sum := fmt.Sprintf("%x", sha256.Sum256([]byte(url))) sum := fmt.Sprintf("%x", sha256.Sum256([]byte(url)))
@@ -91,11 +105,14 @@ func newImageFromWeb(url string) *gtk.Image {
return nil return nil
} }
fullpath := filepath.Join(pa, sum)
// step 3: save it // step 3: save it
err = os.WriteFile(sum, b, 0644) err = os.WriteFile(fullpath, b, 0644)
if err != nil { if err != nil {
return nil return nil
} }
return newImageFromPath(sum) return newImageFromPath(fullpath)
} }

3
go.mod
View File

@@ -7,6 +7,8 @@ require (
github.com/diamondburned/gotk4/pkg v0.3.1 github.com/diamondburned/gotk4/pkg v0.3.1
github.com/google/uuid v1.1.1 github.com/google/uuid v1.1.1
github.com/jasonlovesdoggo/gopen v0.0.0-20250130105607-39c98c645030 github.com/jasonlovesdoggo/gopen v0.0.0-20250130105607-39c98c645030
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f
github.com/kr/pretty v0.1.0
github.com/sqweek/dialog v0.0.0-20260123140253-64c163d53aac github.com/sqweek/dialog v0.0.0-20260123140253-64c163d53aac
gosrc.io/xmpp v0.5.1 gosrc.io/xmpp v0.5.1
mellium.im/xmpp v0.22.0 mellium.im/xmpp v0.22.0
@@ -15,6 +17,7 @@ require (
require ( require (
github.com/KarpelesLab/weak v0.1.1 // indirect github.com/KarpelesLab/weak v0.1.1 // indirect
github.com/TheTitanrain/w32 v0.0.0-20180517000239-4f5cfb03fabf // indirect github.com/TheTitanrain/w32 v0.0.0-20180517000239-4f5cfb03fabf // indirect
github.com/kr/text v0.1.0 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 // indirect go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 // indirect
golang.org/x/net v0.29.0 // indirect golang.org/x/net v0.29.0 // indirect
golang.org/x/sync v0.8.0 // indirect golang.org/x/sync v0.8.0 // indirect

4
go.sum
View File

@@ -38,11 +38,15 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
github.com/jasonlovesdoggo/gopen v0.0.0-20250130105607-39c98c645030 h1:NFCJG3BerP/5ZLXwu08x9xDs+9p7AYFMeo5IXjGANxw= github.com/jasonlovesdoggo/gopen v0.0.0-20250130105607-39c98c645030 h1:NFCJG3BerP/5ZLXwu08x9xDs+9p7AYFMeo5IXjGANxw=
github.com/jasonlovesdoggo/gopen v0.0.0-20250130105607-39c98c645030/go.mod h1:+YdGDBjXJho3QTsEntqzdm0YaiALOsz3sL6b67QLC8M= github.com/jasonlovesdoggo/gopen v0.0.0-20250130105607-39c98c645030/go.mod h1:+YdGDBjXJho3QTsEntqzdm0YaiALOsz3sL6b67QLC8M=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f h1:dKccXx7xA56UNqOcFIbuqFjAWPVtP688j5QMgmo6OHU=
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f/go.mod h1:4rEELDSfUAlBSyUjPG0JnaNGjf13JySHFeRdD/3dLP0=
github.com/knq/sysutil v0.0.0-20181215143952-f05b59f0f307/go.mod h1:BjPj+aVjl9FW/cCGiF3nGh5v+9Gd3VCgBQbod/GlMaQ= github.com/knq/sysutil v0.0.0-20181215143952-f05b59f0f307/go.mod h1:BjPj+aVjl9FW/cCGiF3nGh5v+9Gd3VCgBQbod/GlMaQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=

View File

@@ -4,6 +4,8 @@ import (
"github.com/diamondburned/gotk4/pkg/glib/v2" "github.com/diamondburned/gotk4/pkg/glib/v2"
"github.com/diamondburned/gotk4/pkg/gtk/v4" "github.com/diamondburned/gotk4/pkg/gtk/v4"
"github.com/sqweek/dialog" "github.com/sqweek/dialog"
"gosrc.io/xmpp/stanza"
Jid "mellium.im/xmpp/jid"
) )
func scrollToBottomAfterUpdate(scrolledWindow *gtk.ScrolledWindow) { func scrollToBottomAfterUpdate(scrolledWindow *gtk.ScrolledWindow) {
@@ -31,6 +33,19 @@ func createTab(jid string, isMuc bool) {
func switchToTab(jid string) { func switchToTab(jid string) {
current = jid current = jid
scroller.SetChild(tabs[current].msgs) scroller.SetChild(tabs[current].msgs)
m, _ := mucmembers.Load(jid)
ma := m.(mucUnit)
mm := ma.Members
gen := gtk.NewBox(gtk.OrientationVertical, 0)
mm.Range(func(k, v any) bool {
u := v.(stanza.Presence)
gen.Append(gtk.NewLabel(Jid.MustParse(u.From).Resourcepart()))
return true
})
memberList.SetChild(gen)
} }
func showErrorDialog(err error) { func showErrorDialog(err error) {

View File

@@ -97,6 +97,7 @@ func generateMessageWidget(p stanza.Packet) gtk.Widgetter {
// mainBox.Append(media) // mainBox.Append(media)
// media.AddCSSClass("chat_image") // media.AddCSSClass("chat_image")
mbtn := gtk.NewButtonWithLabel("🖼️") mbtn := gtk.NewButtonWithLabel("🖼️")
// mbtn.SetChild(newImageFromWeb(oob.URL))
mbtn.ConnectClicked(func(){ mbtn.ConnectClicked(func(){
gopen.Open(oob.URL) gopen.Open(oob.URL)
}) })

72
main.go
View File

@@ -2,6 +2,7 @@ package main
import ( import (
"os" "os"
"sync"
"context" "context"
"fmt" "fmt"
@@ -18,7 +19,7 @@ import (
_ "embed" _ "embed"
"encoding/xml" "encoding/xml"
"errors" "github.com/kr/pretty"
) )
var loadedConfig lambdaConfig var loadedConfig lambdaConfig
@@ -32,6 +33,7 @@ var tabs map[string]*chatTab = make(map[string]*chatTab)
var current string var current string
var scroller *gtk.ScrolledWindow var scroller *gtk.ScrolledWindow
var memberList *gtk.ScrolledWindow
//go:embed style.css //go:embed style.css
var styleCSS string var styleCSS string
@@ -40,6 +42,9 @@ var clientroot *xmpp.Client
var uiQueue = make(chan func(), 100) var uiQueue = make(chan func(), 100)
// stores members of mucs
var mucmembers sync.Map
func init() { func init() {
go func() { go func() {
for fn := range uiQueue { for fn := range uiQueue {
@@ -66,10 +71,10 @@ func main() {
TransportConfiguration: xmpp.TransportConfiguration{ TransportConfiguration: xmpp.TransportConfiguration{
Address: loadedConfig.Server, Address: loadedConfig.Server,
}, },
Jid: loadedConfig.Username, Jid: loadedConfig.Username,
Credential: xmpp.Password(loadedConfig.Password), Credential: xmpp.Password(loadedConfig.Password),
Insecure: loadedConfig.Insecure, Insecure: loadedConfig.Insecure,
StreamLogger: os.Stdout, // StreamLogger: os.Stdout,
} }
router := xmpp.NewRouter() router := xmpp.NewRouter()
@@ -159,6 +164,52 @@ func main() {
} }
}) })
}) })
router.HandleFunc("presence", func(s xmpp.Sender, p stanza.Packet) {
presence, ok := p.(stanza.Presence)
pretty.Println(presence)
if !ok {
return
}
var mu MucUser
var ocu OccupantID
ok = presence.Get(&mu)
if ok { // This is a presence stanza from a user in a MUC
presence.Get(&ocu)
muc := jid.MustParse(presence.From).Bare().String()
_, ok = mucmembers.Load(muc)
if !ok {
mucmembers.Store(muc, mucUnit{})
}
unit, ok := mucmembers.Load(muc)
if !ok {
return
}
typed_unit := unit.(mucUnit)
/*
if typed_unit.Members == nil {
typed_unit.Members = make(map[string]stanza.Presence)
mucmembers.Store(muc, typed_unit)
}
*/
if presence.Type != "unavailable" {
typed_unit.Members.Store(ocu.ID, presence)
} else {
typed_unit.Members.Delete(ocu.ID)
// delete(typed_unit.Members, ocu.ID)
}
mucmembers.Store(muc, typed_unit)
}
})
c, err := xmpp.NewClient(&config, router, func(err error) { c, err := xmpp.NewClient(&config, router, func(err error) {
showErrorDialog(err) showErrorDialog(err)
panic(err) panic(err)
@@ -236,6 +287,10 @@ func activate(app *gtk.Application) {
empty_dialog.SetVExpand(true) empty_dialog.SetVExpand(true)
scroller = gtk.NewScrolledWindow() scroller = gtk.NewScrolledWindow()
memberList = gtk.NewScrolledWindow()
scroller.SetHExpand(true)
memberList.SetHExpand(true)
box := gtk.NewBox(gtk.OrientationVertical, 0) box := gtk.NewBox(gtk.OrientationVertical, 0)
// scroller.SetChild(empty_dialog) // scroller.SetChild(empty_dialog)
@@ -243,7 +298,11 @@ func activate(app *gtk.Application) {
menu_scroll := gtk.NewScrolledWindow() menu_scroll := gtk.NewScrolledWindow()
menu_scroll.SetChild(menu) menu_scroll.SetChild(menu)
box.Append(menu_scroll) box.Append(menu_scroll)
box.Append(scroller)
chatbox := gtk.NewBox(gtk.OrientationHorizontal, 0)
chatbox.Append(scroller)
chatbox.Append(memberList)
box.Append(chatbox)
entry_box := gtk.NewBox(gtk.OrientationHorizontal, 0) entry_box := gtk.NewBox(gtk.OrientationHorizontal, 0)
@@ -281,7 +340,6 @@ func activate(app *gtk.Application) {
debug_btn := gtk.NewButtonWithLabel("Join muc") debug_btn := gtk.NewButtonWithLabel("Join muc")
debug_btn.ConnectClicked(func() { debug_btn.ConnectClicked(func() {
showErrorDialog(errors.New("test error"))
t := en.Text() t := en.Text()
_, ok := tabs[t] _, ok := tabs[t]
if !ok { if !ok {

View File

@@ -1,7 +1,7 @@
package main package main
import ( import (
"sync"
"github.com/diamondburned/gotk4/pkg/gtk/v4" "github.com/diamondburned/gotk4/pkg/gtk/v4"
) )
@@ -17,3 +17,10 @@ type lambdaConfig struct {
Insecure bool Insecure bool
Nick string Nick string
} }
type mucUnit struct {
// key: OccupantID
// value: last user presence
Members sync.Map
}

27
xmpp-vcard.go Normal file
View File

@@ -0,0 +1,27 @@
package main
// Implementation of XEP-0054
// https://xmpp.org/extensions/xep-0054.html
import (
"encoding/xml"
"gosrc.io/xmpp/stanza"
)
type VCard struct {
XMLName xml.Name `xml:"vcard-temp vCard"`
Photo Photo `xml:"PHOTO"`
}
func (v *VCard) Namespace() string {
return v.XMLName.Space
}
type Photo struct {
Type string `xml:"TYPE"`
Binval string `xml:"BINVAL"`
}
func init() {
stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{Space: "vcard-temp", Local: "vCard"}, VCard{})
}