From b0f9a7048ad85c00039846d0c590f7506f5e57db Mon Sep 17 00:00:00 2001 From: sunglocto Date: Thu, 29 Jan 2026 21:35:36 +0000 Subject: [PATCH] initial commit --- .gitignore | 5 + README.md | 1 + cache.go | 101 +++++++++++++++ debug.png | Bin 0 -> 2577 bytes go.mod | 26 ++++ go.sum | 142 ++++++++++++++++++++ gtk-helpers.go | 38 ++++++ gtk-message.go | 112 ++++++++++++++++ gtk-xmpp-markup.go | 19 +++ main.go | 317 +++++++++++++++++++++++++++++++++++++++++++++ style.css | 8 ++ types.go | 19 +++ xmpp-helpers.go | 49 +++++++ xmpp-mucuser.go | 26 ++++ xmpp-occupantid.go | 21 +++ xmpp-reply.go | 20 +++ xmpp-sid.go | 19 +++ 17 files changed, 923 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 cache.go create mode 100644 debug.png create mode 100644 go.mod create mode 100644 go.sum create mode 100644 gtk-helpers.go create mode 100644 gtk-message.go create mode 100644 gtk-xmpp-markup.go create mode 100644 main.go create mode 100644 style.css create mode 100644 types.go create mode 100644 xmpp-helpers.go create mode 100644 xmpp-mucuser.go create mode 100644 xmpp-occupantid.go create mode 100644 xmpp-reply.go create mode 100644 xmpp-sid.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7b46dfb --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.dll +lambda.toml +lambda-im +lambda-im.exe +*.exe diff --git a/README.md b/README.md new file mode 100644 index 0000000..4c13d6b --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# gay diff --git a/cache.go b/cache.go new file mode 100644 index 0000000..e400ba4 --- /dev/null +++ b/cache.go @@ -0,0 +1,101 @@ +package main + +// This file caches images in memory so we are not loading them from disk every time we need them +// It also does the same for images that need to be grabbed from HTTP. + +import ( + "github.com/diamondburned/gotk4/pkg/gdk/v4" + "github.com/diamondburned/gotk4/pkg/gtk/v4" + "crypto/sha256" + "fmt" + "net/http" + "io" + "os" +) + +// global or app-level map/cache +var textureCache = make(map[string]gdk.Paintabler) + + + +func getTexture(path string) gdk.Paintabler { + if tex, exists := textureCache[path]; exists { + return tex + } + tex, err := gdk.NewTextureFromFilename(path) // load once + if err != nil { + panic(err) + } + textureCache[path] = tex + return tex +} + +func newPictureFromPath(path string) *gtk.Picture { + tex := getTexture(path) + img := gtk.NewPictureForPaintable(tex) + return img +} + +func newImageFromPath(path string) *gtk.Image { + tex := getTexture(path) + img := gtk.NewImageFromPaintable(tex) + return img +} + +func newPictureFromWeb(url string) *gtk.Picture { + // step 1: get a sha256 sum of the URL + sum := fmt.Sprintf("%x", sha256.Sum256([]byte(url))) + + p, ok := textureCache[sum] + if ok { + return gtk.NewPictureForPaintable(p) + } + + // step 2: download it + resp, err := http.Get(url) + if err != nil { + return nil + } + + b, err := io.ReadAll(resp.Body) + if err != nil { + return nil + } + + // step 3: save it + err = os.WriteFile(sum, b, 0644) + if err != nil { + return nil + } + + return newPictureFromPath(sum) +} + +func newImageFromWeb(url string) *gtk.Image { + // step 1: get a sha256 sum of the URL + sum := fmt.Sprintf("%x", sha256.Sum256([]byte(url))) + + p, ok := textureCache[sum] + if ok { + return gtk.NewImageFromPaintable(p) + } + + // step 2: download it + resp, err := http.Get(url) + if err != nil { + return nil + } + + b, err := io.ReadAll(resp.Body) + if err != nil { + return nil + } + + // step 3: save it + err = os.WriteFile(sum, b, 0644) + if err != nil { + return nil + } + + return newImageFromPath(sum) +} diff --git a/debug.png b/debug.png new file mode 100644 index 0000000000000000000000000000000000000000..c1018877967f00839378f5574d9ab76f2d0337c7 GIT binary patch literal 2577 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 WWKTJ7&jmYBQ_s`Y&t;ucLK6VpwH!16 literal 0 HcmV?d00001 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..fbe0091 --- /dev/null +++ b/go.mod @@ -0,0 +1,26 @@ +module lambda-im + +go 1.25.6 + +require ( + github.com/BurntSushi/toml v1.6.0 + github.com/diamondburned/gotk4/pkg v0.3.1 + github.com/google/uuid v1.1.1 + github.com/jasonlovesdoggo/gopen v0.0.0-20250130105607-39c98c645030 + github.com/sqweek/dialog v0.0.0-20260123140253-64c163d53aac + gosrc.io/xmpp v0.5.1 + mellium.im/xmpp v0.22.0 +) + +require ( + github.com/KarpelesLab/weak v0.1.1 // indirect + github.com/TheTitanrain/w32 v0.0.0-20180517000239-4f5cfb03fabf // 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/sync v0.8.0 // indirect + golang.org/x/text v0.18.0 // indirect + golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 // indirect + mellium.im/reader v0.1.0 // indirect + mellium.im/xmlstream v0.15.4 // indirect + nhooyr.io/websocket v1.6.5 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..afc599c --- /dev/null +++ b/go.sum @@ -0,0 +1,142 @@ +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/TheTitanrain/w32 v0.0.0-20180517000239-4f5cfb03fabf h1:FPsprx82rdrX2jiKyS17BH6IrTmUBYqZa/CXT4uvb+I= +github.com/TheTitanrain/w32 v0.0.0-20180517000239-4f5cfb03fabf/go.mod h1:peYoMncQljjNS6tZwI9WVyQB3qZS6u79/N3mBOcnd3I= +github.com/agnivade/wasmbrowsertest v0.3.1/go.mod h1:zQt6ZTdl338xxRaMW395qccVE2eQm0SjC/SDz0mPWQI= +github.com/chromedp/cdproto v0.0.0-20190614062957-d6d2f92b486d/go.mod h1:S8mB5wY3vV+vRIzf39xDXsw3XKYewW9X6rW2aEmkrSw= +github.com/chromedp/cdproto v0.0.0-20190621002710-8cbd498dd7a0/go.mod h1:S8mB5wY3vV+vRIzf39xDXsw3XKYewW9X6rW2aEmkrSw= +github.com/chromedp/cdproto v0.0.0-20190812224334-39ef923dcb8d/go.mod h1:0YChpVzuLJC5CPr+x3xkHN6Z8KOSXjNbL7qV8Wc4GW0= +github.com/chromedp/cdproto v0.0.0-20190926234355-1b4886c6fad6/go.mod h1:0YChpVzuLJC5CPr+x3xkHN6Z8KOSXjNbL7qV8Wc4GW0= +github.com/chromedp/chromedp v0.3.1-0.20190619195644-fd957a4d2901/go.mod h1:mJdvfrVn594N9tfiPecUidF6W5jPRKHymqHfzbobPsM= +github.com/chromedp/chromedp v0.4.0/go.mod h1:DC3QUn4mJ24dwjcaGQLoZrhm4X/uPHZ6spDbS2uFhm4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-interpreter/wagon v0.5.1-0.20190713202023-55a163980b6c/go.mod h1:5+b/MBYkclRZngKF5s6qrgWxSLgE9F5dFdO1hAueZLc= +github.com/go-interpreter/wagon v0.6.0/go.mod h1:5+b/MBYkclRZngKF5s6qrgWxSLgE9F5dFdO1hAueZLc= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190908185732-236ed259b199/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +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/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +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.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +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/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-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190620125010-da37f6c1e481/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/sqweek/dialog v0.0.0-20260123140253-64c163d53aac h1:/QqP+ajFMma4hNWQyBDVaQQhz9Z1kDyXScNWMO3owx0= +github.com/sqweek/dialog v0.0.0-20260123140253-64c163d53aac/go.mod h1:/qNPSY91qTz/8TgHEMioAUc6q7+3SOybeKczHMXFcXw= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/twitchyliquid64/golang-asm v0.0.0-20190126203739-365674df15fc/go.mod h1:NoCfSFWosfqMqmmD7hApkirIK9ozpHjxRnRxs1l413A= +go.coder.com/go-tools v0.0.0-20190317003359-0c6a35b74a16/go.mod h1:iKV5yK9t+J5nG9O3uF6KYdPEz3dyfMyB15MN1rbQ8Qw= +go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +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.0.0-20180426230345-b49d69b5da94/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190306220234-b354f8bf4d9e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190618155005-516e3c20635f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190927073244-c990c680b611/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= +golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gosrc.io/xmpp v0.5.1 h1:Rgrm5s2rt+npGggJH3HakQxQXR8ZZz3+QRzakRQqaq4= +gosrc.io/xmpp v0.5.1/go.mod h1:L3NFMqYOxyLz3JGmgFyWf7r9htE91zVGiK40oW4RwdY= +gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/gotestsum v0.3.5/go.mod h1:Mnf3e5FUzXbkCfynWBGOwLssY7gTQgCHObK9tMpAriY= +mellium.im/reader v0.1.0 h1:UUEMev16gdvaxxZC7fC08j7IzuDKh310nB6BlwnxTww= +mellium.im/reader v0.1.0/go.mod h1:F+X5HXpkIfJ9EE1zHQG9lM/hO946iYAmU7xjg5dsQHI= +mellium.im/sasl v0.3.2 h1:PT6Xp7ccn9XaXAnJ03FcEjmAn7kK1x7aoXV6F+Vmrl0= +mellium.im/sasl v0.3.2/go.mod h1:NKXDi1zkr+BlMHLQjY3ofYuU4KSPFxknb8mfEu6SveY= +mellium.im/xmlstream v0.15.4 h1:gLKxcWl4rLMUpKgtzrTBvr4OexPeO/edYus+uK3F6ZI= +mellium.im/xmlstream v0.15.4/go.mod h1:yXaCW2++fmVO4L9piKVkyLDqnCmictVYF7FDQW8prb4= +mellium.im/xmpp v0.22.0 h1:UthQVSwEAr7SNrmyc90c2ykGpVHxjn/3yw8Ey4+Im8s= +mellium.im/xmpp v0.22.0/go.mod h1:WSjq12nhREFD88Vy/0WD6Q8inE8t6a8w7QjzwivWitw= +mvdan.cc/sh v2.6.4+incompatible/go.mod h1:IeeQbZq+x2SUGBensq/jge5lLQbS3XT2ktyp3wrt4x8= +nhooyr.io/websocket v1.6.5 h1:8TzpkldRfefda5JST+CnOH135bzVPz5uzfn/AF+gVKg= +nhooyr.io/websocket v1.6.5/go.mod h1:F259lAzPRAH0htX2y3ehpJe09ih1aSHN7udWki1defY= diff --git a/gtk-helpers.go b/gtk-helpers.go new file mode 100644 index 0000000..4bd4989 --- /dev/null +++ b/gtk-helpers.go @@ -0,0 +1,38 @@ +package main + +import ( + "github.com/diamondburned/gotk4/pkg/glib/v2" + "github.com/diamondburned/gotk4/pkg/gtk/v4" + "github.com/sqweek/dialog" +) + +func scrollToBottomAfterUpdate(scrolledWindow *gtk.ScrolledWindow) { + glib.IdleAdd(func() bool { + vAdj := scrolledWindow.VAdjustment() + vAdj.SetValue(vAdj.Upper() - vAdj.PageSize()) + return false // Return false to run only once + }) +} + +func createTab(jid string, isMuc bool) { + _, ok := tabs[jid] + if !ok { + newTab := new(chatTab) + newTab.isMuc = isMuc + newTab.msgs = gtk.NewListBox() + newTab.msgs.SetVExpand(true) + newTab.msgs.SetShowSeparators(true) + + newTab.msgs.Append(gtk.NewButtonWithLabel("Get past messages...")) + tabs[jid] = newTab + } +} + +func switchToTab(jid string) { + current = jid + scroller.SetChild(tabs[current].msgs) +} + +func showErrorDialog(err error) { + dialog.Message("%s", err.Error()).Title("Error").Error() +} diff --git a/gtk-message.go b/gtk-message.go new file mode 100644 index 0000000..d8a89cf --- /dev/null +++ b/gtk-message.go @@ -0,0 +1,112 @@ +package main + +// This file contains the function that generates message widgets depending on message stanza + +import ( + "fmt" + "github.com/diamondburned/gotk4/pkg/gtk/v4" + "gosrc.io/xmpp/stanza" + "mellium.im/xmpp/jid" + "github.com/google/uuid" + "github.com/jasonlovesdoggo/gopen" +) + +func generateMessageWidget(p stanza.Packet) gtk.Widgetter { + m, ok := p.(stanza.Message) + if !ok { + return gtk.NewLabel("Unsupported message.") + } + sid := StanzaID{} + m.Get(&sid) + + mainBox := gtk.NewBox(gtk.OrientationVertical, 0) + gesture := gtk.NewGestureClick() + gesture.SetButton(3) // Right click + + vis := false + reactions := gtk.NewBox(gtk.OrientationHorizontal, 0) + reactions.SetVisible(false) + + reaction := []string{"👍", "👎", "♥️", "🤣", "😭",} + for _, v := range reaction { + like := gtk.NewButton() + like.SetLabel(v) + like.SetHExpand(true) + like.ConnectClicked(func() { + fmt.Println("licked") + client.SendRaw(fmt.Sprintf(` + + + %s + + + `, m.To, jid.MustParse(m.From).Bare().String(), uuid.New().String(), m.Type, sid.ID, v)) + }) + reactions.Append(like) + } + + gesture.Connect("pressed", func(n_press, x, y int) { + if !vis { + vis = true + reactions.SetVisible(true) + } else { + vis = false + reactions.SetVisible(false) + } + }) + + mainBox.AddController(gesture) + + reply := Reply{} + ok = m.Get(&reply) + if ok { + replyBox := gtk.NewBox(gtk.OrientationHorizontal, 0) + replyBox.Append(gtk.NewLabel("↱ " + jid.MustParse(reply.To).Resourcepart())) + mainBox.Append(replyBox) + } + + + authorBox := gtk.NewBox(gtk.OrientationHorizontal, 10) + contentBox := gtk.NewBox(gtk.OrientationHorizontal, 0) + im := newImageFromPath("debug.png") + + im.SetPixelSize(40) + authorBox.Append(im) + al := gtk.NewLabel(jid.MustParse(m.From).Resourcepart()) + al.AddCSSClass("author") + + authorBox.Append(al) + mlabel := gtk.NewLabel(m.Body) + // mlabel.SetMarkup(convertXEPToPango(m.Body)) + mlabel.SetWrap(true) + mlabel.SetSelectable(true) + mlabel.SetHAlign(gtk.AlignFill) + + contentBox.Append(mlabel) + + mainBox.Append(authorBox) + mainBox.Append(contentBox) + + mainBox.Append(reactions) + + oob := stanza.OOB{} + ok = m.Get(&oob) + if ok { + // media := newPictureFromWeb(oob.URL) + // media.SetCanShrink(false) + // mainBox.Append(media) + // media.AddCSSClass("chat_image") + mbtn := gtk.NewButtonWithLabel("🖼️") + mbtn.ConnectClicked(func(){ + gopen.Open(oob.URL) + }) + authorBox.Append(mbtn) + } + + return mainBox +} + +func getVAdjustment(scrolledWindow *gtk.ScrolledWindow) *gtk.Adjustment { + val := scrolledWindow.ObjectProperty("vadjustment").(*gtk.Adjustment) + return val +} diff --git a/gtk-xmpp-markup.go b/gtk-xmpp-markup.go new file mode 100644 index 0000000..f4ddceb --- /dev/null +++ b/gtk-xmpp-markup.go @@ -0,0 +1,19 @@ +package main + +import ( + _ "regexp" +) + +// This file implements XEP-0393 partially by providing a function to get a Pango string. +// https://xmpp.org/extensions/xep-0393.html + + +func convertXEPToPango(text string) string { + /* FIXME this RegEx causes certain strings to appear invisible + text = regexp.MustCompile(`\*([^*]+)\*`).ReplaceAllString(text, "$1") + text = regexp.MustCompile(`_([^_]+)_`).ReplaceAllString(text, "$1") + text = regexp.MustCompile(`~([^~]+)~`).ReplaceAllString(text, "$1") + text = regexp.MustCompile("`([^`]+)`").ReplaceAllString(text, "$1") + */ + return text +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..5ad8118 --- /dev/null +++ b/main.go @@ -0,0 +1,317 @@ +package main + +import ( + "os" + + "context" + "fmt" + "github.com/diamondburned/gotk4/pkg/gdk/v4" + "github.com/diamondburned/gotk4/pkg/gio/v2" + "github.com/diamondburned/gotk4/pkg/glib/v2" + "github.com/diamondburned/gotk4/pkg/gtk/v4" + + "github.com/BurntSushi/toml" + "gosrc.io/xmpp" + "gosrc.io/xmpp/stanza" + "mellium.im/xmpp/jid" + "time" + + _ "embed" + "encoding/xml" + "errors" +) + +var loadedConfig lambdaConfig + +var empty_dialog *gtk.Label + +// var msgs *gtk.ListBox +var content *gtk.Widgetter + +var tabs map[string]*chatTab = make(map[string]*chatTab) +var current string + +var scroller *gtk.ScrolledWindow + +//go:embed style.css +var styleCSS string +var client xmpp.Sender +var clientroot *xmpp.Client + +var uiQueue = make(chan func(), 100) + +func init() { + go func() { + for fn := range uiQueue { + glib.IdleAdd(func() bool { + fn() + return false + }) + time.Sleep(10 * time.Millisecond) // Small delay between updates + } + }() +} + +func main() { + + b, err := os.ReadFile("lambda.toml") + if err != nil { + showErrorDialog(err) + panic(err) + } + + _, err = toml.Decode(string(b), &loadedConfig) + + config := xmpp.Config{ + TransportConfiguration: xmpp.TransportConfiguration{ + Address: loadedConfig.Server, + }, + Jid: loadedConfig.Username, + Credential: xmpp.Password(loadedConfig.Password), + Insecure: loadedConfig.Insecure, + StreamLogger: os.Stdout, + } + router := xmpp.NewRouter() + + router.NewRoute().IQNamespaces(stanza.NSDiscoInfo).HandlerFunc(func(s xmpp.Sender, p stanza.Packet) { + iq, ok := p.(*stanza.IQ) + if !ok || iq.Type != stanza.IQTypeGet { + return + } + + iqResp, err := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id}) + if err != nil { + panic(err) + } + + identity := stanza.Identity{ + Name: "Lambda", + Category: "client", // TODO: Allow spoofing on user request + Type: "pc", + } + payload := stanza.DiscoInfo{ + XMLName: xml.Name{ + Space: stanza.NSDiscoInfo, + Local: "query", + }, + Identity: []stanza.Identity{identity}, + Features: []stanza.Feature{ + {Var: stanza.NSDiscoInfo}, + {Var: stanza.NSDiscoItems}, + {Var: "jabber:iq:version"}, + {Var: "urn:xmpp:delegation:1"}, + {Var: "http://jabber.org/protocol/muc"}, + {Var: "urn:xmpp:reply:0"}, + }, + } + iqResp.Payload = &payload + s.Send(iqResp) + }) + + router.NewRoute().IQNamespaces("jabber:iq:version").HandlerFunc(func(s xmpp.Sender, p stanza.Packet) { + iq, ok := p.(*stanza.IQ) + if !ok || iq.Type != stanza.IQTypeGet { + return + + } + + iqResp, err := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id}) + if err != nil { + panic(err) + } + + v := &stanza.Version{} + v = v.SetInfo("Lambda", "1.0", "Windows") // TODO: Allow spoofing and report correct information + + iqResp.Payload = v + s.Send(iqResp) + }) + + router.HandleFunc("message", func(s xmpp.Sender, p stanza.Packet) { + m, ok := p.(stanza.Message) + if !ok { + return + } + + if m.Body == "" { + return + } + + originator := jid.MustParse(m.From).Bare().String() + + glib.IdleAdd(func() { + uiQueue <- func() { + b := gtk.NewBox(gtk.OrientationVertical, 0) + ba, ok := generateMessageWidget(p).(*gtk.Box) + if ok { + b = ba + } + + _, ok = tabs[originator] + + if ok { + fmt.Println("valid") + tabs[originator].msgs.Append(b) + scrollToBottomAfterUpdate(scroller) + } else { + fmt.Println("Got message when the tab does not exist!") + } + } + }) + }) + c, err := xmpp.NewClient(&config, router, func(err error) { + showErrorDialog(err) + panic(err) + }) + if err != nil { + showErrorDialog(err) + panic(err) + } + client = c + clientroot = c + + cm := xmpp.NewStreamManager(c, func(c xmpp.Sender) { + fmt.Println("XMPP client connected") + /* + */ + }) + + go func() { + err = cm.Run() + if err != nil { + showErrorDialog(err) + panic(err) + } + }() + + app := gtk.NewApplication("net.sunglocto.lambda", gio.ApplicationFlagsNone) + app.ConnectActivate(func() { activate(app) }) + + if code := app.Run(os.Args); code > 0 { + os.Exit(code) + } +} + +func activate(app *gtk.Application) { + // Load the CSS and apply it globally. + gtk.StyleContextAddProviderForDisplay( + gdk.DisplayGetDefault(), loadCSS(styleCSS), + gtk.STYLE_PROVIDER_PRIORITY_APPLICATION, + ) + + window := gtk.NewApplicationWindow(app) + app.SetMenubar(gio.NewMenu()) + + window.SetTitle("Lambda") + menu := gtk.NewBox(gtk.OrientationHorizontal, 0) + /* + f_menu := gtk.NewMenuButton() + f_menu.SetLabel("File") + f_menu.SetAlwaysShowArrow(false) + + e_menu := gtk.NewMenuButton() + e_menu.SetLabel("Edit") + e_menu.SetAlwaysShowArrow(false) + + v_menu := gtk.NewMenuButton() + v_menu.SetLabel("View") + v_menu.SetAlwaysShowArrow(false) + + b_menu := gtk.NewMenuButton() + b_menu.SetLabel("Bookmarks") + b_menu.SetAlwaysShowArrow(false) + + h_menu := gtk.NewMenuButton() + h_menu.SetLabel("Help") + h_menu.SetAlwaysShowArrow(false) + + menu.Append(f_menu) + menu.Append(e_menu) + menu.Append(v_menu) + menu.Append(b_menu) + menu.Append(h_menu) + */ + + empty_dialog = gtk.NewLabel("You are not focused on any chats.") + empty_dialog.SetVExpand(true) + + scroller = gtk.NewScrolledWindow() + + box := gtk.NewBox(gtk.OrientationVertical, 0) + // scroller.SetChild(empty_dialog) + scroller.SetChild(empty_dialog) + menu_scroll := gtk.NewScrolledWindow() + menu_scroll.SetChild(menu) + box.Append(menu_scroll) + box.Append(scroller) + + entry_box := gtk.NewBox(gtk.OrientationHorizontal, 0) + + en := gtk.NewEntry() + en.SetPlaceholderText("Say something, what else are you gonna do here?") + b := gtk.NewButtonWithLabel("Send") + + sendtxt := func() { + t := en.Text() + if t == "" { + dialog := >k.AlertDialog{} + dialog.SetDetail("detail") + dialog.SetMessage("message") + dialog.SetButtons([]string{"yes, no"}) + dialog.Choose(context.TODO(), &window.Window, nil) + } + + err := sendMessage(client, current, stanza.MessageTypeGroupchat, t, "", "") + if err != nil { + panic(err) // TODO: Show error message via GTK + } + en.SetText("") + scrollToBottomAfterUpdate(scroller) + } + + en.Connect("activate", sendtxt) + + b.ConnectClicked(sendtxt) + + en.SetHExpand(true) + + entry_box.Append(en) + entry_box.Append(b) + + debug_btn := gtk.NewButtonWithLabel("Join muc") + + debug_btn.ConnectClicked(func() { + showErrorDialog(errors.New("test error")) + t := en.Text() + _, ok := tabs[t] + if !ok { + err := joinMuc(client, clientroot.Session.BindJid, t, loadedConfig.Nick) + if err != nil { + panic(err) + } + + createTab(t, true) + b := gtk.NewButtonWithLabel(t) + b.ConnectClicked(func() { + b.AddCSSClass("accent") + switchToTab(t) + }) + menu.Append(b) + } + + }) + + entry_box.Append(debug_btn) + + box.Append(entry_box) + + window.SetChild(box) + + window.SetVisible(true) +} + +func loadCSS(content string) *gtk.CSSProvider { + prov := gtk.NewCSSProvider() + prov.LoadFromString(content) + return prov +} diff --git a/style.css b/style.css new file mode 100644 index 0000000..f9a924a --- /dev/null +++ b/style.css @@ -0,0 +1,8 @@ +.author { + font-size: 150%; +} + +.chat_img { + max-width: 50px; + max-height: 50px; +} diff --git a/types.go b/types.go new file mode 100644 index 0000000..960bd21 --- /dev/null +++ b/types.go @@ -0,0 +1,19 @@ +package main + +import ( + + "github.com/diamondburned/gotk4/pkg/gtk/v4" +) + +type chatTab struct { + isMuc bool + msgs *gtk.ListBox +} + +type lambdaConfig struct { + Server string + Username string + Password string + Insecure bool + Nick string +} diff --git a/xmpp-helpers.go b/xmpp-helpers.go new file mode 100644 index 0000000..b28b0ce --- /dev/null +++ b/xmpp-helpers.go @@ -0,0 +1,49 @@ +package main + +import ( + "fmt" + "gosrc.io/xmpp" + "gosrc.io/xmpp/stanza" +) + +// This file has small functions that can be used to do XMPP stuff without writing tons of boilerplate + +// Basic message sender. Anything more complex should be written by hand +func sendMessage(c xmpp.Sender, sendTo string, msgType stanza.StanzaType, body string, subject string, thread string) error { + m := stanza.Message{ + Attrs: stanza.Attrs{ + To: sendTo, + Type: msgType, + }, + Body: body, + Subject: subject, + Thread: thread, + } + err := c.Send(m) + if err != nil { + return err + } + return nil +} + +// Joins a MUC +func joinMuc(c xmpp.Sender, jid string, muc string, nick string) error{ + addr := muc + "/" + nick + fmt.Println(addr) + joinPresence := stanza.Presence{ + Attrs: stanza.Attrs{ + From: jid, + To: addr, + }, + Extensions: []stanza.PresExtension{ + &stanza.MucPresence{ + }, + }, + } + + err := client.Send(joinPresence) + if err != nil { + return err + } + return nil +} diff --git a/xmpp-mucuser.go b/xmpp-mucuser.go new file mode 100644 index 0000000..ba7f432 --- /dev/null +++ b/xmpp-mucuser.go @@ -0,0 +1,26 @@ +package main + +// Implementation of XEP-0045: Multi User Chat 7.2.2 +// https://xmpp.org/extensions/xep-0045.html#enter-pres + +import ( + "encoding/xml" + "gosrc.io/xmpp/stanza" +) + +type MucUser struct { + stanza.PresExtension + XMLName xml.Name `xml:"http://jabber.org/protocol/muc#user x"` + MucUserItem MucUserItem `xml:"item,omitempty"` +} + +type MucUserItem struct { + XMLName xml.Name + Affiliation string `xml:"affiliation,attr,omitempty"` // TODO: Use enum + Role string `xml:"role,attr,omitempty"` // TODO: Use enum + JID string `xml:"jid,attr,omitempty"` +} + +func init() { + stanza.TypeRegistry.MapExtension(stanza.PKTPresence, xml.Name{Space: "http://jabber.org/protocol/muc#user", Local: "x"}, MucUser{}) +} diff --git a/xmpp-occupantid.go b/xmpp-occupantid.go new file mode 100644 index 0000000..23525eb --- /dev/null +++ b/xmpp-occupantid.go @@ -0,0 +1,21 @@ +package main + +import ( + "encoding/xml" + "gosrc.io/xmpp/stanza" +) + +// Implementation of XEP-0421: Occupant identifiers for semi-anonymous MUCs +// https://xmpp.org/extensions/xep-0421.html + +type OccupantID struct { + stanza.PresExtension + stanza.MsgExtension + XMLName xml.Name `xml:"urn:xmpp:occupant-id:0 occupant-id"` + ID string `xml:"id,attr"` +} + +func init() { + stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{Space: "urn:xmpp:occupant-id:0", Local: "occupant-id"}, OccupantID{}) + stanza.TypeRegistry.MapExtension(stanza.PKTPresence, xml.Name{Space: "urn:xmpp:occupant-id:0", Local: "occupant-id"}, OccupantID{}) +} diff --git a/xmpp-reply.go b/xmpp-reply.go new file mode 100644 index 0000000..fc19f60 --- /dev/null +++ b/xmpp-reply.go @@ -0,0 +1,20 @@ +package main + +// Implementation of XEP-0461 +// https://xmpp.org/extensions/xep-0461.html#business-id + +import ( + "encoding/xml" + "gosrc.io/xmpp/stanza" +) + +type Reply struct { + stanza.MsgExtension + XMLName xml.Name `xml:"urn:xmpp:reply:0 reply"` + To string `xml:"to,attr"` + ID string `xml:"id,attr"` +} + +func init() { + stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{Space: "urn:xmpp:reply:0", Local: "reply"}, Reply{}) +} diff --git a/xmpp-sid.go b/xmpp-sid.go new file mode 100644 index 0000000..a0a191f --- /dev/null +++ b/xmpp-sid.go @@ -0,0 +1,19 @@ +package main + +import ( + "encoding/xml" + "gosrc.io/xmpp/stanza" +) + +// Implementation of XEP-0359: Unique and Stable Stanza IDs + +type StanzaID struct { + stanza.MsgExtension + XMLName xml.Name `xml:"urn:xmpp:sid:0 stanza-id"` + By string `xml:"by,attr"` + ID string `xml:"id,attr"` +} + +func init() { + stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{Space: "urn:xmpp:sid:0", Local: "stanza-id"}, StanzaID{}) +}