Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 359e8ed63e | |||
| 3b9df94cf6 | |||
| 634f451595 | |||
| 00d7945f70 | |||
| 60d6a287e5 | |||
| d5981b7475 | |||
| 523722bae6 | |||
| 71e6a58fbd | |||
| a7e90e4ae5 | |||
| 654ab8b618 | |||
| 7a201808e3 | |||
| 77e4e444d4 | |||
| bf1685a382 | |||
| 0ac43946b1 | |||
| 15ff7a20db | |||
| 1dd3f09fed | |||
| 87bdbc440a | |||
| 62b5a9db72 | |||
| 39156af48a | |||
| 3f40d3da29 | |||
| a7a49f7441 | |||
| e026e777f6 | |||
| 09a809c102 | |||
| 29ef37e237 | |||
| 713cb24508 | |||
| 589101c292 | |||
| f807565cb2 | |||
| 6626d35920 | |||
| 5c76729a6b | |||
| c260b8b231 | |||
| 971147dcb8 |
15
CONTRIBUTING.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Contributing
|
||||
|
||||
As you might have gathered, this Gitea instance does not allow registrations. If you want to have an account on this instance in order to contribute to this project, you must contact me for an account. I am available on:
|
||||
|
||||
- XMPP: `x@sunglocto.net`
|
||||
- Matrix: `@x:sunglocto.net`
|
||||
- E-Mail: sunglocto <img width=16px height=16px src="https://sunglocto.net/icons/email.gif"/> protonmail.com
|
||||
|
||||
For actually submitting your patches, you can just fork this repo and submit a pull request. I currently have no contribution policy that you have to follow however if you just use common sense you should be good.
|
||||
|
||||
## AI contributions
|
||||
|
||||
Obviously vibe-coded or fully AI-generated contributions will be immediately rejected. This is in order to ensure the stability of the project and decrease the chance of significant bugs or vulnerabilities.
|
||||
|
||||
AI contributions that you have reviewed, edited and **ensured that they compile, run and work successfully** are acceptable. AI is a tool, and like any other tools, can be used for good if used in moderation.
|
||||
@@ -1,3 +1,7 @@
|
||||
# lambda
|
||||
|
||||
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://github.com/markjames/famfamfam-silk-icons](https://github.com/markjames/famfamfam-silk-icons)
|
||||
|
||||
5
TODO.md
@@ -1,7 +1,6 @@
|
||||
# TODO
|
||||
- XEP-0393: Message Styling 0%
|
||||
- XEP-0402: PEP Native Bookmarks 0%
|
||||
- XEP-0066: Out of Band Data partial
|
||||
- XEP-0461: Message Replies partial
|
||||
- XEP-0402: PEP Native Bookmarks 50% (often lags out client if enabled)
|
||||
- XEP-0461: Message Replies 0%
|
||||
- XEP-0444: Message Reactions partial
|
||||
|
||||
|
||||
334
assets.go
Normal file
@@ -0,0 +1,334 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/base64"
|
||||
"github.com/diamondburned/gotk4/pkg/gdk/v4"
|
||||
"github.com/diamondburned/gotk4/pkg/gdkpixbuf/v2"
|
||||
)
|
||||
|
||||
//go:embed debug.png
|
||||
var defaultAvatarBytes []byte
|
||||
var defaultAvatarB64 string = base64.StdEncoding.EncodeToString(defaultAvatarBytes)
|
||||
|
||||
//go:embed failed_load.png
|
||||
var failedBytes []byte
|
||||
var failedB64 string = base64.StdEncoding.EncodeToString(failedBytes)
|
||||
|
||||
//go:embed assets/owner.png
|
||||
var ownerMedalBytes []byte
|
||||
var ownerMedalB64 string = base64.StdEncoding.EncodeToString(ownerMedalBytes)
|
||||
|
||||
//go:embed assets/admin.png
|
||||
var adminMedalBytes []byte
|
||||
var adminMedalB64 string = base64.StdEncoding.EncodeToString(adminMedalBytes)
|
||||
|
||||
//go:embed assets/member.png
|
||||
var memberMedalBytes []byte
|
||||
var memberMedalB64 string = base64.StdEncoding.EncodeToString(memberMedalBytes)
|
||||
|
||||
//go:embed assets/noaffiliation.png
|
||||
var noneMedalBytes []byte
|
||||
var noneMedalB64 string = base64.StdEncoding.EncodeToString(noneMedalBytes)
|
||||
|
||||
//go:embed assets/outcast.png
|
||||
var outcastMedalBytes []byte
|
||||
var outcastMedalB64 string = base64.StdEncoding.EncodeToString(outcastMedalBytes)
|
||||
|
||||
//go:embed assets/cancel.png
|
||||
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)
|
||||
|
||||
//go:embed assets/lambda-disabled.png
|
||||
var logoDisabledBytes []byte
|
||||
var logoDisabledB64 string = base64.StdEncoding.EncodeToString(logoDisabledBytes)
|
||||
|
||||
//go:embed assets/group.png
|
||||
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)
|
||||
|
||||
//go:embed assets/disconnect.png
|
||||
var disconnectBytes []byte
|
||||
var disconnectB64 string = base64.StdEncoding.EncodeToString(disconnectBytes)
|
||||
|
||||
//go:embed assets/chart_bar.png
|
||||
var barBytes []byte
|
||||
var barB64 string = base64.StdEncoding.EncodeToString(barBytes)
|
||||
|
||||
//go:embed assets/ok.png
|
||||
var okBytes []byte
|
||||
var okB64 string = base64.StdEncoding.EncodeToString(okBytes)
|
||||
|
||||
//go:embed assets/hourglass.png
|
||||
var hourglassBytes []byte
|
||||
var hourglassB64 string = base64.StdEncoding.EncodeToString(hourglassBytes)
|
||||
|
||||
//go:embed assets/connect_tls.png
|
||||
var connectBytes []byte
|
||||
var connectB64 string = base64.StdEncoding.EncodeToString(connectBytes)
|
||||
|
||||
//go:embed assets/comment.png
|
||||
var commentBytes []byte
|
||||
var commentB64 string = base64.StdEncoding.EncodeToString(commentBytes)
|
||||
|
||||
//go:embed assets/information.png
|
||||
var informationBytes []byte
|
||||
var informationB64 string = base64.StdEncoding.EncodeToString(informationBytes)
|
||||
|
||||
func init() {
|
||||
|
||||
loader := gdkpixbuf.NewPixbufLoader()
|
||||
|
||||
defaultAvatarData, _ := base64.StdEncoding.DecodeString(defaultAvatarB64)
|
||||
loader.Write(defaultAvatarData)
|
||||
loader.Close()
|
||||
clientAssets["DefaultAvatar"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||
|
||||
loader = gdkpixbuf.NewPixbufLoader()
|
||||
|
||||
failedData, _ := base64.StdEncoding.DecodeString(failedB64)
|
||||
loader.Write(failedData)
|
||||
loader.Close()
|
||||
|
||||
clientAssets["FailedAvatar"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||
|
||||
loader = gdkpixbuf.NewPixbufLoader()
|
||||
|
||||
ownerMedalData, _ := base64.StdEncoding.DecodeString(ownerMedalB64)
|
||||
loader.Write(ownerMedalData)
|
||||
loader.Close()
|
||||
|
||||
clientAssets["owner"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||
|
||||
loader = gdkpixbuf.NewPixbufLoader()
|
||||
|
||||
cancelData, _ := base64.StdEncoding.DecodeString(cancelB64)
|
||||
loader.Write(cancelData)
|
||||
loader.Close()
|
||||
|
||||
clientAssets["cancel"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||
|
||||
loader = gdkpixbuf.NewPixbufLoader()
|
||||
|
||||
tagData, _ := base64.StdEncoding.DecodeString(tagB64)
|
||||
loader.Write(tagData)
|
||||
loader.Close()
|
||||
|
||||
clientAssets["tag"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||
|
||||
loader = gdkpixbuf.NewPixbufLoader()
|
||||
|
||||
adminMedalData, _ := base64.StdEncoding.DecodeString(adminMedalB64)
|
||||
loader.Write(adminMedalData)
|
||||
loader.Close()
|
||||
|
||||
clientAssets["admin"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||
|
||||
loader = gdkpixbuf.NewPixbufLoader()
|
||||
|
||||
memberMedalData, _ := base64.StdEncoding.DecodeString(memberMedalB64)
|
||||
loader.Write(memberMedalData)
|
||||
loader.Close()
|
||||
|
||||
clientAssets["member"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||
|
||||
loader = gdkpixbuf.NewPixbufLoader()
|
||||
|
||||
noneMedalData, _ := base64.StdEncoding.DecodeString(noneMedalB64)
|
||||
loader.Write(noneMedalData)
|
||||
loader.Close()
|
||||
|
||||
clientAssets["none"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||
|
||||
loader = gdkpixbuf.NewPixbufLoader()
|
||||
|
||||
outcastMedalData, _ := base64.StdEncoding.DecodeString(outcastMedalB64)
|
||||
loader.Write(outcastMedalData)
|
||||
loader.Close()
|
||||
|
||||
clientAssets["outcast"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||
|
||||
loader = gdkpixbuf.NewPixbufLoader()
|
||||
|
||||
disabledLogoData, _ := base64.StdEncoding.DecodeString(logoDisabledB64)
|
||||
loader.Write(disabledLogoData)
|
||||
loader.Close()
|
||||
|
||||
clientAssets["disabled_logo"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||
|
||||
loader = gdkpixbuf.NewPixbufLoader()
|
||||
|
||||
groupData, _ := base64.StdEncoding.DecodeString(groupB64)
|
||||
loader.Write(groupData)
|
||||
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())
|
||||
|
||||
loader = gdkpixbuf.NewPixbufLoader()
|
||||
|
||||
disconnectData, _ := base64.StdEncoding.DecodeString(disconnectB64)
|
||||
loader.Write(disconnectData)
|
||||
loader.Close()
|
||||
|
||||
clientAssets["disconnect"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||
|
||||
loader = gdkpixbuf.NewPixbufLoader()
|
||||
|
||||
barData, _ := base64.StdEncoding.DecodeString(barB64)
|
||||
loader.Write(barData)
|
||||
loader.Close()
|
||||
|
||||
clientAssets["chart_bar"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||
|
||||
loader = gdkpixbuf.NewPixbufLoader()
|
||||
|
||||
okData, _ := base64.StdEncoding.DecodeString(okB64)
|
||||
loader.Write(okData)
|
||||
loader.Close()
|
||||
|
||||
clientAssets["ok"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||
|
||||
loader = gdkpixbuf.NewPixbufLoader()
|
||||
|
||||
hourglassData, _ := base64.StdEncoding.DecodeString(hourglassB64)
|
||||
loader.Write(hourglassData)
|
||||
loader.Close()
|
||||
|
||||
clientAssets["hourglass"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||
|
||||
loader = gdkpixbuf.NewPixbufLoader()
|
||||
|
||||
connectData, _ := base64.StdEncoding.DecodeString(connectB64)
|
||||
loader.Write(connectData)
|
||||
loader.Close()
|
||||
|
||||
clientAssets["connect"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||
|
||||
loader = gdkpixbuf.NewPixbufLoader()
|
||||
|
||||
commentData, _ := base64.StdEncoding.DecodeString(commentB64)
|
||||
loader.Write(commentData)
|
||||
loader.Close()
|
||||
|
||||
clientAssets["comment"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||
|
||||
loader = gdkpixbuf.NewPixbufLoader()
|
||||
|
||||
informationData, _ := base64.StdEncoding.DecodeString(informationB64)
|
||||
loader.Write(informationData)
|
||||
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/ban.png
Normal file
|
After Width: | Height: | Size: 762 B |
BIN
assets/cancel.png
Normal file
|
After Width: | Height: | Size: 607 B |
BIN
assets/chart_bar.png
Normal file
|
After Width: | Height: | Size: 541 B |
1
assets/client_icons/README
Normal file
@@ -0,0 +1 @@
|
||||
All client assets are owned by their respective owners
|
||||
BIN
assets/comment.png
Normal file
|
After Width: | Height: | Size: 413 B |
BIN
assets/connect_tls.png
Normal file
|
After Width: | Height: | Size: 721 B |
BIN
assets/disconnect.png
Normal file
|
After Width: | Height: | Size: 796 B |
BIN
assets/door_in.png
Normal file
|
After Width: | Height: | Size: 693 B |
BIN
assets/door_out.png
Normal file
|
After Width: | Height: | Size: 643 B |
BIN
assets/group.png
Normal file
|
After Width: | Height: | Size: 753 B |
BIN
assets/hourglass.png
Normal file
|
After Width: | Height: | Size: 744 B |
BIN
assets/icon.ico
Normal file
|
After Width: | Height: | Size: 264 KiB |
BIN
assets/icon.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
assets/information.png
Normal file
|
After Width: | Height: | Size: 778 B |
BIN
assets/jabber.png
Normal file
|
After Width: | Height: | Size: 730 B |
BIN
assets/kick.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
assets/lambda-disabled.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
assets/large_group.png
Normal file
|
After Width: | Height: | Size: 747 B |
BIN
assets/ok.png
Normal file
|
After Width: | Height: | Size: 460 B |
BIN
assets/status_away.png
Normal file
|
After Width: | Height: | Size: 794 B |
BIN
assets/status_busy.png
Normal file
|
After Width: | Height: | Size: 751 B |
BIN
assets/status_chatty.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
assets/status_online.png
Normal file
|
After Width: | Height: | Size: 722 B |
BIN
assets/status_xa.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
assets/tag.png
Normal file
|
After Width: | Height: | Size: 666 B |
BIN
assets/vcard.png
Normal file
|
After Width: | Height: | Size: 362 B |
BIN
assets/world.png
Normal file
|
After Width: | Height: | Size: 923 B |
3
cache.go
@@ -18,6 +18,9 @@ import (
|
||||
// global or app-level map/cache
|
||||
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)
|
||||
|
||||
func ensureCache() (string, error) {
|
||||
cachePath := configdir.LocalCache("lambda-im")
|
||||
err := configdir.MakePath(cachePath) // Ensure it exists.
|
||||
|
||||
BIN
failed_load.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
24
go.mod
@@ -5,22 +5,38 @@ go 1.25.5
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.6.0
|
||||
github.com/diamondburned/gotk4/pkg v0.3.1
|
||||
github.com/gen2brain/beeep v0.11.2
|
||||
github.com/go-analyze/charts v0.5.24
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/jacoblockett/sanitizefilename v1.0.1
|
||||
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/kr/pretty v0.2.0
|
||||
golang.org/x/net v0.29.0
|
||||
gosrc.io/xmpp v0.5.1
|
||||
mellium.im/xmpp v0.22.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.13.3 // indirect
|
||||
github.com/go-analyze/bulk v0.1.3 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // 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.1.0 // indirect
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||
github.com/sergeymakinen/go-bmp v1.0.0 // indirect
|
||||
github.com/sergeymakinen/go-ico v1.0.0-beta.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/net v0.29.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
golang.org/x/image v0.24.0 // indirect
|
||||
golang.org/x/sync v0.11.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/text v0.22.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
|
||||
|
||||
56
go.sum
@@ -1,3 +1,5 @@
|
||||
git.sr.ht/~jackmordaunt/go-toast v1.1.2 h1:/yrfI55LRt1M7H1vkaw+NaH1+L1CDxrqDltwm5euVuE=
|
||||
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=
|
||||
@@ -10,18 +12,35 @@ github.com/chromedp/cdproto v0.0.0-20190926234355-1b4886c6fad6/go.mod h1:0YChpVz
|
||||
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 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/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/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/esiqveland/notify v0.13.3 h1:QCMw6o1n+6rl+oLUfg8P1IIDSFsDEb2WlXvVvIJbI/o=
|
||||
github.com/esiqveland/notify v0.13.3/go.mod h1:hesw/IRYTO0x99u1JPweAl4+5mwXJibQVUcP0Iu5ORE=
|
||||
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/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.3 h1:pzRdBqzHDAT9PyROt0SlWE0YqPtdmTcEpIJY0C3vF0c=
|
||||
github.com/go-analyze/bulk v0.1.3/go.mod h1:afon/KtFJYnekIyN20H/+XUvcLFjE8sKR1CfpqfClgM=
|
||||
github.com/go-analyze/charts v0.5.24 h1:HamvYWEgbANbLb/P6uijygtG9qeSYev34ki1RelA1Lw=
|
||||
github.com/go-analyze/charts v0.5.24/go.mod h1:s1YvQhjiSwtLx1f2dOKfiV9x2TT49nVSL6v2rlRpTbY=
|
||||
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/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
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/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
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=
|
||||
@@ -33,6 +52,8 @@ github.com/google/pprof v0.0.0-20190908185732-236ed259b199/go.mod h1:zfwlbNMJ+OI
|
||||
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/jackmordaunt/icns/v3 v3.0.1 h1:xxot6aNuGrU+lNgxz5I5H0qSeCjNKp8uTXB1j8D4S3o=
|
||||
github.com/jackmordaunt/icns/v3 v3.0.1/go.mod h1:5sHL59nqTd2ynTnowxB/MDQFhKNqkK8X687uKNygaSQ=
|
||||
github.com/jacoblockett/sanitizefilename v1.0.1 h1:HhPMoPp05U5aKjhht+d7lsqhyF4trKSBme0FE6S/1C4=
|
||||
github.com/jacoblockett/sanitizefilename v1.0.1/go.mod h1:/cQHSz2fHeR1iDKpHTSW/MIyONa+Uqj0eszbvPuIxNs=
|
||||
github.com/jasonlovesdoggo/gopen v0.0.0-20250130105607-39c98c645030 h1:NFCJG3BerP/5ZLXwu08x9xDs+9p7AYFMeo5IXjGANxw=
|
||||
@@ -43,8 +64,9 @@ github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f/go.mod h1:4rEELDS
|
||||
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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
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=
|
||||
@@ -58,21 +80,37 @@ github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc
|
||||
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/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sergeymakinen/go-bmp v1.0.0 h1:SdGTzp9WvCV0A1V0mBeaS7kQAwNLdVJbmHlqNWq0R+M=
|
||||
github.com/sergeymakinen/go-bmp v1.0.0/go.mod h1:/mxlAQZRLxSvJFNIEGGLBE/m40f3ZnUifpgVDlcUIEY=
|
||||
github.com/sergeymakinen/go-ico v1.0.0-beta.0 h1:m5qKH7uPKLdrygMWxbamVn+tl2HfiA3K6MFJw4GfZvQ=
|
||||
github.com/sergeymakinen/go-ico v1.0.0-beta.0/go.mod h1:wQ47mTczswBO5F0NoDt7O0IXgnV4Xy3ojrroMQzyhUk=
|
||||
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/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/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
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/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
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=
|
||||
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=
|
||||
@@ -85,6 +123,8 @@ golang.org/x/crypto v0.0.0-20180426230345-b49d69b5da94/go.mod h1:6SG95UA2DQfeDnf
|
||||
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/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
|
||||
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
|
||||
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=
|
||||
@@ -97,8 +137,8 @@ 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/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.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=
|
||||
@@ -108,9 +148,12 @@ golang.org/x/sys v0.0.0-20190618155005-516e3c20635f/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
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/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
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=
|
||||
@@ -127,6 +170,9 @@ gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKW
|
||||
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=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
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=
|
||||
|
||||
321
gtk-helpers.go
@@ -3,8 +3,10 @@ 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"
|
||||
"gosrc.io/xmpp/stanza"
|
||||
)
|
||||
|
||||
@@ -19,7 +21,9 @@ func scrollToBottomAfterUpdate(scrolledWindow *gtk.ScrolledWindow) {
|
||||
func createTab(jid string, isMuc bool) bool {
|
||||
fmt.Println("Creating tab", jid, "isMuc:", isMuc)
|
||||
_, ok := tabs.Load(jid)
|
||||
if !ok {
|
||||
_, uok := userdevices.Load(jid)
|
||||
_, mok := mucmembers.Load(jid)
|
||||
if !ok && !uok && !mok {
|
||||
newTab := new(chatTab)
|
||||
newTab.isMuc = isMuc
|
||||
newTab.msgs = gtk.NewListBox()
|
||||
@@ -45,12 +49,21 @@ func switchToTab(jid string, w *gtk.Window) {
|
||||
|
||||
scroller.SetChild(typed_tab.msgs)
|
||||
if typed_tab.isMuc {
|
||||
m, _ := mucmembers.Load(jid)
|
||||
ma := m.(mucUnit)
|
||||
|
||||
m, ok := mucmembers.Load(jid)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ma, ok := m.(mucUnit)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
mm := ma.Members
|
||||
gen := gtk.NewBox(gtk.OrientationVertical, 0)
|
||||
|
||||
i := 0
|
||||
mm.Range(func(k, v any) bool {
|
||||
i++
|
||||
userbox := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||||
|
||||
u := v.(stanza.Presence)
|
||||
@@ -58,57 +71,203 @@ func switchToTab(jid string, w *gtk.Window) {
|
||||
var ocu OccupantID
|
||||
u.Get(&mu)
|
||||
u.Get(&ocu)
|
||||
//id := ocu.ID
|
||||
//if id == "" {
|
||||
id := JidMustParse(u.From).Resource
|
||||
//}
|
||||
|
||||
nick_label := gtk.NewLabel(JidMustParse(u.From).Resource)
|
||||
/*
|
||||
affil_label := gtk.NewLabel("")
|
||||
switch mu.MucUserItem.Affiliation {
|
||||
case "owner":
|
||||
affil_label.SetText("O")
|
||||
case "admin":
|
||||
affil_label.SetText("A")
|
||||
case "member":
|
||||
affil_label.SetText("M")
|
||||
case "none":
|
||||
affil_label.SetText("-")
|
||||
}
|
||||
*/
|
||||
|
||||
nick_label.SetEllipsize(pango.EllipsizeEnd)
|
||||
nick_label.AddCSSClass(mu.MucUserItem.Role)
|
||||
if mu.MucUserItem.Role == "visitor" {
|
||||
nick_label.SetOpacity(0.5)
|
||||
}
|
||||
|
||||
/*
|
||||
affil_label.SetHAlign(gtk.AlignEnd)
|
||||
affil_label.SetHExpand(true)
|
||||
|
||||
|
||||
affil_label.AddCSSClass(mu.MucUserItem.Affiliation)
|
||||
*/
|
||||
|
||||
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)
|
||||
// userbox.Append(affil_label)
|
||||
|
||||
var hats Hats
|
||||
ok := u.Get(&hats)
|
||||
if ok {
|
||||
for _, hat := range hats.Hats {
|
||||
tag := gtk.NewImageFromPaintable(clientAssets["tag"])
|
||||
tag.SetTooltipText(hat.Title)
|
||||
userbox.Prepend(tag)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
medal.SetHAlign(gtk.AlignEnd)
|
||||
medal.SetHExpand(true)
|
||||
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()
|
||||
win.SetDefaultSize(400, 400)
|
||||
profile_box := gtk.NewBox(gtk.OrientationVertical, 0)
|
||||
nick := gtk.NewLabel(JidMustParse(u.From).Resource)
|
||||
ver_text := gtk.NewLabel("Getting version...")
|
||||
ver_text.AddCSSClass("visitor")
|
||||
|
||||
win.SetTitle(JidMustParse(u.From).Resource)
|
||||
nick.AddCSSClass("author")
|
||||
profile_box.Append(nick)
|
||||
profile_box.Append(gtk.NewLabel(u.From))
|
||||
profile_box.Append(ver_text)
|
||||
fr := gtk.NewLabel(u.From)
|
||||
fr.AddCSSClass("jid")
|
||||
profile_box.Append(fr)
|
||||
profile_box.Append(ver_text)
|
||||
|
||||
iqResp, err := stanza.NewIQ(stanza.Attrs{
|
||||
Type: "get",
|
||||
@@ -129,7 +288,9 @@ func switchToTab(jid string, w *gtk.Window) {
|
||||
ok := u.Get(&hats)
|
||||
if ok {
|
||||
for _, hat := range hats.Hats {
|
||||
profile_box.Append(gtk.NewLabel(hat.Title))
|
||||
l := gtk.NewLabel(hat.Title)
|
||||
l.AddCSSClass("hat")
|
||||
profile_box.Append(l)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,12 +298,61 @@ func switchToTab(jid string, w *gtk.Window) {
|
||||
ok = u.Get(&mu)
|
||||
if ok {
|
||||
if mu.MucUserItem.JID != "" {
|
||||
profile_box.Append(gtk.NewLabel(mu.MucUserItem.JID))
|
||||
ji := (gtk.NewLabel(mu.MucUserItem.JID))
|
||||
ji.AddCSSClass("jid")
|
||||
profile_box.Append(ji)
|
||||
}
|
||||
profile_box.Append(gtk.NewLabel("Connected with role " + mu.MucUserItem.Role))
|
||||
profile_box.Append(gtk.NewLabel("Affiliated as " + mu.MucUserItem.Affiliation))
|
||||
}
|
||||
|
||||
go func() {
|
||||
fmt.Println("Attempting to get Disco info")
|
||||
|
||||
myIQ, err := stanza.NewIQ(stanza.Attrs{
|
||||
Type: "get",
|
||||
From: clientroot.Session.BindJid,
|
||||
To: u.From,
|
||||
Id: "dicks",
|
||||
Lang: "en",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
myIQ.Payload = &stanza.DiscoInfo{}
|
||||
|
||||
ctx := context.TODO()
|
||||
mychan, err := client.SendIQ(ctx, myIQ)
|
||||
if err == nil {
|
||||
result := <-mychan
|
||||
res, ok := result.Payload.(*stanza.DiscoInfo)
|
||||
if ok {
|
||||
idents := res.Identity
|
||||
for i, ident := range idents {
|
||||
profile_box.Append(gtk.NewLabel(fmt.Sprintf("%d: Name: %s, Category: %s, Type: %s", i+1, ident.Name, ident.Category, ident.Type)))
|
||||
}
|
||||
|
||||
/*
|
||||
s := fmt.Sprintf("%v", res.Features)
|
||||
h := sha1.New()
|
||||
h.Write([]byte(s))
|
||||
sha1_hash := hex.EncodeToString(h.Sum(nil))
|
||||
*/
|
||||
|
||||
sw := gtk.NewScrolledWindow()
|
||||
s := ""
|
||||
for _, feature := range res.Features {
|
||||
s = s + feature.Var + "\n"
|
||||
}
|
||||
sw.SetChild(gtk.NewLabel(s))
|
||||
|
||||
profile_box.Append(sw)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
ctx := context.TODO()
|
||||
mychan, err := client.SendIQ(ctx, iqResp)
|
||||
@@ -155,9 +365,18 @@ func switchToTab(jid string, w *gtk.Window) {
|
||||
version := ver.Version
|
||||
os := ver.OS
|
||||
|
||||
profile_box.Append(gtk.NewLabel(name))
|
||||
profile_box.Append(gtk.NewLabel(version))
|
||||
profile_box.Append(gtk.NewLabel(os))
|
||||
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)
|
||||
ver_text.RemoveCSSClass("visitor")
|
||||
ver_text.AddCSSClass("error")
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -166,7 +385,7 @@ func switchToTab(jid string, w *gtk.Window) {
|
||||
mo, _ := mucmembers.Load(JidMustParse(u.From).Bare())
|
||||
mm := mo.(mucUnit)
|
||||
mmm := mm.Members
|
||||
mmmm, ok := mmm.Load(ocu.ID)
|
||||
mmmm, ok := mmm.Load(id)
|
||||
if ok {
|
||||
pres := mmmm.(stanza.Presence)
|
||||
|
||||
@@ -176,18 +395,18 @@ func switchToTab(jid string, w *gtk.Window) {
|
||||
im := getAvatar(u.From, vu.Photo)
|
||||
im.SetPixelSize(80)
|
||||
im.AddCSSClass("author_img")
|
||||
profile_box.Append(im)
|
||||
profile_box.Prepend(im)
|
||||
} else {
|
||||
im := newImageFromPath("debug.png")
|
||||
im := gtk.NewImageFromPaintable(clientAssets["DefaultAvatar"])
|
||||
im.SetPixelSize(80)
|
||||
im.AddCSSClass("author_img")
|
||||
profile_box.Append(im)
|
||||
profile_box.Prepend(im)
|
||||
}
|
||||
} else {
|
||||
im := newImageFromPath("debug.png")
|
||||
im := gtk.NewImageFromPaintable(clientAssets["DefaultAvatar"])
|
||||
im.SetPixelSize(80)
|
||||
im.AddCSSClass("author_img")
|
||||
profile_box.Append(im)
|
||||
profile_box.Prepend(im)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -195,12 +414,32 @@ func switchToTab(jid string, w *gtk.Window) {
|
||||
win.SetTransientFor(win)
|
||||
win.Present()
|
||||
})
|
||||
userbox.AddController(gesture)
|
||||
|
||||
userbox.AddController(gesture)
|
||||
userbox.AddController(mod_gesture)
|
||||
|
||||
if mu.MucUserItem.Role == "moderator" {
|
||||
gen.Prepend(userbox)
|
||||
} else {
|
||||
gen.Append(userbox)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
headerBox := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||||
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)
|
||||
|
||||
muci := getAvatar(jid, jid)
|
||||
muci.SetPixelSize(80)
|
||||
gen.Prepend(muci)
|
||||
memberList.SetChild(gen)
|
||||
} else {
|
||||
memberList.SetChild(gtk.NewLabel(jid))
|
||||
|
||||
121
gtk-message.go
@@ -6,8 +6,8 @@ import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/diamondburned/gotk4/pkg/gtk/v4"
|
||||
"github.com/diamondburned/gotk4/pkg/gdk/v4"
|
||||
"github.com/diamondburned/gotk4/pkg/gtk/v4"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jacoblockett/sanitizefilename"
|
||||
"github.com/jasonlovesdoggo/gopen"
|
||||
@@ -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(jid.MustParse(presence.From).Resourcepart() + " left the MUC")
|
||||
b.Append(gtk.NewImageFromPaintable(clientAssets["door_out"]))
|
||||
b.Append(gtk.NewLabel(JidMustParse(presence.From).Resource))
|
||||
} else {
|
||||
return gtk.NewLabel(jid.MustParse(presence.From).Resourcepart() + " 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 {
|
||||
@@ -49,9 +57,29 @@ func generateMessageWidget(p stanza.Packet) gtk.Widgetter {
|
||||
readmarker := Marker{}
|
||||
ok = m.Get(&readmarker)
|
||||
if ok {
|
||||
return gtk.NewLabel(fmt.Sprintf("%s has read to this point", JidMustParse(m.From).Resource))
|
||||
b := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||||
b.Append(gtk.NewLabel(fmt.Sprintf("%s has read to this point", JidMustParse(m.From).Resource)))
|
||||
return b
|
||||
}
|
||||
|
||||
composing := stanza.StateComposing{}
|
||||
ok = m.Get(&composing)
|
||||
if ok {
|
||||
b := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||||
b.Append(gtk.NewLabel(fmt.Sprintf("%s is typing...", JidMustParse(m.From).Resource)))
|
||||
return b
|
||||
}
|
||||
|
||||
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_box.Append(cancel_img)
|
||||
error_box.Append(error_label)
|
||||
error_box.Append(gtk.NewLabel(m.Error.Reason))
|
||||
return error_box
|
||||
}
|
||||
|
||||
sid := StanzaID{}
|
||||
m.Get(&sid)
|
||||
@@ -60,7 +88,6 @@ func generateMessageWidget(p stanza.Packet) gtk.Widgetter {
|
||||
gesture := gtk.NewGestureClick()
|
||||
gesture.SetButton(3) // Right click
|
||||
|
||||
|
||||
popover := gtk.NewPopover()
|
||||
popover.SetParent(mainBox)
|
||||
popover.SetHasArrow(false)
|
||||
@@ -88,21 +115,11 @@ func generateMessageWidget(p stanza.Packet) gtk.Widgetter {
|
||||
|
||||
rc_box.Append(reactions)
|
||||
|
||||
if m.Type == stanza.MessageTypeGroupchat {
|
||||
moderate := gtk.NewButtonWithLabel("Moderate") // TODO: Implement proper support for moderations via extension
|
||||
moderate.ConnectClicked(func() {
|
||||
client.SendRaw(fmt.Sprintf(`
|
||||
<iq type='set' to='%s' id='%s'>
|
||||
<moderate id='%s' xmlns='urn:xmpp:message-moderate:1'>
|
||||
<retract xmlns='urn:xmpp-message-retract:1'/>
|
||||
<reason>Retracted</reason>
|
||||
</moderate>
|
||||
</iq>
|
||||
`, jid.MustParse(m.From).Bare().String(), uuid.New().String(), sid.ID))
|
||||
quote := gtk.NewButtonWithLabel("Quote")
|
||||
quote.ConnectClicked(func() {
|
||||
message_en.SetText("> " + m.Body + "\n")
|
||||
})
|
||||
rc_box.Append(moderate)
|
||||
}
|
||||
|
||||
rc_box.Append(quote)
|
||||
|
||||
popover.SetChild(rc_box)
|
||||
|
||||
@@ -114,32 +131,34 @@ func generateMessageWidget(p stanza.Packet) gtk.Widgetter {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
ocu := OccupantID{}
|
||||
|
||||
m.Get(&ocu)
|
||||
//id := ocu.ID
|
||||
// if id == "" {
|
||||
id := JidMustParse(m.From).Resource
|
||||
// }
|
||||
|
||||
authorBox := gtk.NewBox(gtk.OrientationHorizontal, 10)
|
||||
|
||||
contentBox := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||||
// im := newImageFromPath("debug.png")
|
||||
|
||||
// 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)
|
||||
|
||||
if m.Type == stanza.MessageTypeGroupchat {
|
||||
mo, _ := mucmembers.Load(jid.MustParse(m.From).Bare().String())
|
||||
mm := mo.(mucUnit)
|
||||
mmm := mm.Members
|
||||
mmmm, ok := mmm.Load(ocu.ID)
|
||||
mmmm, ok := mmm.Load(id)
|
||||
if ok {
|
||||
pres := mmmm.(stanza.Presence)
|
||||
|
||||
@@ -166,13 +185,29 @@ func generateMessageWidget(p stanza.Packet) gtk.Widgetter {
|
||||
al.SetText(al.Text() + " whispers")
|
||||
}
|
||||
|
||||
|
||||
authorBox.Append(al)
|
||||
|
||||
wxdc := XDCEl{}
|
||||
ok = m.Get(&wxdc)
|
||||
if ok {
|
||||
authorBox.Append(gtk.NewLabel("🎮"))
|
||||
}
|
||||
|
||||
mlabel := gtk.NewLabel(m.Body)
|
||||
if m.Body == "" {
|
||||
mlabel.SetText("No body set")
|
||||
mlabel.AddCSSClass("visitor")
|
||||
}
|
||||
mlabel.SetWrap(true)
|
||||
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)
|
||||
@@ -195,6 +230,15 @@ func generateMessageWidget(p stanza.Packet) gtk.Widgetter {
|
||||
authorBox.Append(mbtn)
|
||||
}
|
||||
|
||||
if m.Subject != "" {
|
||||
subjectlabel := gtk.NewLabel(m.Subject)
|
||||
subjectlabel.SetWrap(true)
|
||||
subjectlabel.SetSelectable(true)
|
||||
subjectlabel.SetHAlign(gtk.AlignFill)
|
||||
subjectlabel.AddCSSClass("subject")
|
||||
mainBox.Append(subjectlabel)
|
||||
}
|
||||
|
||||
return mainBox
|
||||
}
|
||||
|
||||
@@ -204,9 +248,10 @@ func getVAdjustment(scrolledWindow *gtk.ScrolledWindow) *gtk.Adjustment {
|
||||
}
|
||||
|
||||
func getAvatar(j, hash string) *gtk.Image { // TODO: This function probably shouldn't be here, and should probably be in xmpp-helpers or somewhere similar.
|
||||
oghash := hash
|
||||
p, err := ensureCache()
|
||||
if err != nil {
|
||||
return gtk.NewImageFromPaintable(clientAssets["DefaultAvatar"])
|
||||
return gtk.NewImageFromPaintable(clientAssets["FailedAvatar"])
|
||||
}
|
||||
|
||||
if hash == "" {
|
||||
@@ -214,6 +259,12 @@ func getAvatar(j, hash string) *gtk.Image { // TODO: This function probably shou
|
||||
return gtk.NewImageFromPaintable(clientAssets["DefaultAvatar"])
|
||||
}
|
||||
|
||||
_, ok := invalidImages[hash]
|
||||
if ok {
|
||||
fmt.Println("Image is invalid")
|
||||
return gtk.NewImageFromPaintable(clientAssets["FailedAvatar"])
|
||||
}
|
||||
|
||||
hash = filepath.Join(p, sanitizefilename.Sanitize(hash))
|
||||
|
||||
_, err = os.ReadFile(hash)
|
||||
@@ -247,8 +298,10 @@ func getAvatar(j, hash string) *gtk.Image { // TODO: This function probably shou
|
||||
}
|
||||
|
||||
base64_data := card.Photo.Binval
|
||||
if card.Photo.Binval == "" || (card.Photo.Type == "image/svg+xml" && runtime.GOOS == "windows") {
|
||||
return gtk.NewImageFromPaintable(clientAssets["DefaultAvatar"])
|
||||
if card.Photo.Binval == "" || ((card.Photo.Type == "image/svg+xml" || card.Photo.Type == "image/webp") && (runtime.GOOS == "windows" || runtime.GOOS == "netbsd")) {
|
||||
fmt.Println("Blocking image")
|
||||
invalidImages[oghash] = true
|
||||
return gtk.NewImageFromPaintable(clientAssets["FailedAvatar"])
|
||||
}
|
||||
|
||||
data, err := base64.StdEncoding.DecodeString(base64_data)
|
||||
|
||||
@@ -1,19 +1,29 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
|
||||
"os"
|
||||
"bytes"
|
||||
"os"
|
||||
|
||||
"github.com/diamondburned/gotk4/pkg/gio/v2"
|
||||
"github.com/diamondburned/gotk4/pkg/gtk/v4"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"math/rand/v2"
|
||||
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
func randomClientResource() string {
|
||||
chars := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZλ"
|
||||
str := ""
|
||||
for range 4 {
|
||||
str = str + string(chars[rand.IntN(len(chars))])
|
||||
}
|
||||
|
||||
return "lambda." + str
|
||||
}
|
||||
|
||||
func dropToSignInPage(err error) {
|
||||
app := gtk.NewApplication("net.sunglocto.lambda.login", gio.ApplicationFlagsNone)
|
||||
app.ConnectActivate(func() {
|
||||
@@ -52,7 +62,6 @@ func dropToSignInPage(err error) {
|
||||
insecure_check.SetHAlign(gtk.AlignEnd)
|
||||
insecure_check.SetHExpand(true)
|
||||
|
||||
|
||||
server_box.Append(server_label)
|
||||
server_box.Append(server_entry)
|
||||
|
||||
@@ -82,6 +91,8 @@ func dropToSignInPage(err error) {
|
||||
conf.Password = password_entry.Text()
|
||||
conf.Nick = nickname_entry.Text()
|
||||
conf.Insecure = insecure_check.Active()
|
||||
conf.JoinBookmarks = true
|
||||
conf.Resource = randomClientResource()
|
||||
|
||||
var b bytes.Buffer
|
||||
e := toml.NewEncoder(&b)
|
||||
@@ -93,7 +104,6 @@ func dropToSignInPage(err error) {
|
||||
}
|
||||
os.WriteFile(filepath.Join(p, "lambda.toml"), b.Bytes(), 0644)
|
||||
|
||||
|
||||
window.SetVisible(false)
|
||||
main()
|
||||
os.Exit(0)
|
||||
|
||||
596
main.go
@@ -2,16 +2,19 @@ package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/diamondburned/gotk4/pkg/gdk/v4"
|
||||
"github.com/diamondburned/gotk4/pkg/gdkpixbuf/v2"
|
||||
"github.com/diamondburned/gotk4/pkg/gio/v2"
|
||||
"github.com/diamondburned/gotk4/pkg/glib/v2"
|
||||
"github.com/diamondburned/gotk4/pkg/gtk/v4"
|
||||
"github.com/diamondburned/gotk4/pkg/gdkpixbuf/v2"
|
||||
_ "github.com/kr/pretty"
|
||||
"github.com/gen2brain/beeep"
|
||||
"github.com/go-analyze/charts"
|
||||
"golang.org/x/net/html/charset"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
@@ -22,14 +25,22 @@ import (
|
||||
|
||||
_ "embed"
|
||||
"encoding/xml"
|
||||
"math/rand/v2"
|
||||
"github.com/kr/pretty"
|
||||
"io"
|
||||
"runtime"
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
var loadedConfig lambdaConfig
|
||||
|
||||
var empty_dialog *gtk.Label
|
||||
var empty_dialog *gtk.Image
|
||||
|
||||
var connectionStatus *gtk.Label
|
||||
var connectionIcon *gtk.Image
|
||||
|
||||
var mStatus *gtk.Label
|
||||
var mIcon *gtk.Image
|
||||
|
||||
var pingStatus *gtk.Label
|
||||
|
||||
// var msgs *gtk.ListBox
|
||||
var content *gtk.Widgetter
|
||||
@@ -41,6 +52,7 @@ var current string
|
||||
var scroller *gtk.ScrolledWindow
|
||||
var memberList *gtk.ScrolledWindow
|
||||
var menu *gtk.Box
|
||||
var message_en *gtk.Entry
|
||||
|
||||
//go:embed style.css
|
||||
var styleCSS string
|
||||
@@ -57,34 +69,13 @@ var mucmembers sync.Map
|
||||
// stores devices of users
|
||||
var userdevices sync.Map
|
||||
|
||||
//go:embed debug.png
|
||||
var defaultAvatarBytes []byte
|
||||
var defaultAvatarB64 string = base64.StdEncoding.EncodeToString(defaultAvatarBytes)
|
||||
|
||||
//go:embed assets/owner.png
|
||||
var ownerMedalBytes []byte
|
||||
var ownerMedalB64 string = base64.StdEncoding.EncodeToString(ownerMedalBytes)
|
||||
|
||||
//go:embed assets/admin.png
|
||||
var adminMedalBytes []byte
|
||||
var adminMedalB64 string = base64.StdEncoding.EncodeToString(adminMedalBytes)
|
||||
|
||||
//go:embed assets/member.png
|
||||
var memberMedalBytes []byte
|
||||
var memberMedalB64 string = base64.StdEncoding.EncodeToString(memberMedalBytes)
|
||||
|
||||
//go:embed assets/noaffiliation.png
|
||||
var noneMedalBytes []byte
|
||||
var noneMedalB64 string = base64.StdEncoding.EncodeToString(noneMedalBytes)
|
||||
|
||||
//go:embed assets/outcast.png
|
||||
var outcastMedalBytes []byte
|
||||
var outcastMedalB64 string = base64.StdEncoding.EncodeToString(outcastMedalBytes)
|
||||
|
||||
var pingTimes = [][]float64{}
|
||||
|
||||
var clientAssets map[string]gdk.Paintabler = make(map[string]gdk.Paintabler)
|
||||
|
||||
func init() {
|
||||
beeep.AppName = "Lambda"
|
||||
|
||||
go func() {
|
||||
for fn := range uiQueue {
|
||||
glib.IdleAdd(func() bool {
|
||||
@@ -95,56 +86,10 @@ func init() {
|
||||
}
|
||||
}()
|
||||
|
||||
loader := gdkpixbuf.NewPixbufLoader()
|
||||
|
||||
defaultAvatarData, _ := base64.StdEncoding.DecodeString(defaultAvatarB64)
|
||||
loader.Write(defaultAvatarData)
|
||||
loader.Close()
|
||||
clientAssets["DefaultAvatar"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||
|
||||
|
||||
loader = gdkpixbuf.NewPixbufLoader()
|
||||
|
||||
ownerMedalData, _ := base64.StdEncoding.DecodeString(ownerMedalB64)
|
||||
loader.Write(ownerMedalData)
|
||||
loader.Close()
|
||||
|
||||
clientAssets["owner"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||
|
||||
loader = gdkpixbuf.NewPixbufLoader()
|
||||
|
||||
adminMedalData, _ := base64.StdEncoding.DecodeString(adminMedalB64)
|
||||
loader.Write(adminMedalData)
|
||||
loader.Close()
|
||||
|
||||
clientAssets["admin"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||
|
||||
loader = gdkpixbuf.NewPixbufLoader()
|
||||
|
||||
memberMedalData, _ := base64.StdEncoding.DecodeString(memberMedalB64)
|
||||
loader.Write(memberMedalData)
|
||||
loader.Close()
|
||||
|
||||
clientAssets["member"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||
|
||||
loader = gdkpixbuf.NewPixbufLoader()
|
||||
|
||||
noneMedalData, _ := base64.StdEncoding.DecodeString(noneMedalB64)
|
||||
loader.Write(noneMedalData)
|
||||
loader.Close()
|
||||
|
||||
clientAssets["none"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||
|
||||
loader = gdkpixbuf.NewPixbufLoader()
|
||||
|
||||
outcastMedalData, _ := base64.StdEncoding.DecodeString(outcastMedalB64)
|
||||
loader.Write(outcastMedalData)
|
||||
loader.Close()
|
||||
|
||||
clientAssets["outcast"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||
}
|
||||
|
||||
func main() {
|
||||
pingTimes = append(pingTimes, []float64{})
|
||||
p, err := ensureConfig()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -162,21 +107,23 @@ func main() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Put 4 random characters at the end
|
||||
chars := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZλ"
|
||||
str := ""
|
||||
for range 4 {
|
||||
str = str + string(chars[rand.IntN(len(chars))])
|
||||
if loadedConfig.Resource == "" {
|
||||
fmt.Println("Config resource is empty! Generating a random one")
|
||||
loadedConfig.Resource = randomClientResource()
|
||||
}
|
||||
|
||||
config := xmpp.Config{
|
||||
TransportConfiguration: xmpp.TransportConfiguration{
|
||||
Address: loadedConfig.Server,
|
||||
CharsetReader: func(c string, input io.Reader) (io.Reader, error) {
|
||||
return charset.NewReaderLabel(c, input)
|
||||
},
|
||||
Jid: loadedConfig.Username + "/lambda." + str,
|
||||
},
|
||||
Jid: loadedConfig.Username + "/" + loadedConfig.Resource,
|
||||
Credential: xmpp.Password(loadedConfig.Password),
|
||||
Insecure: loadedConfig.Insecure,
|
||||
StreamLogger: os.Stdout,
|
||||
// StreamLogger: os.Stdout,
|
||||
StreamManagementEnable: true,
|
||||
}
|
||||
router := xmpp.NewRouter()
|
||||
|
||||
@@ -208,8 +155,8 @@ func main() {
|
||||
{Var: "jabber:iq:version"},
|
||||
{Var: "urn:xmpp:delegation:1"},
|
||||
{Var: "http://jabber.org/protocol/muc"},
|
||||
{Var: "urn:xmpp:reply:0"},
|
||||
{Var: "λ"},
|
||||
{Var: "urn:xmpp:attention:0"},
|
||||
},
|
||||
}
|
||||
iqResp.Payload = &payload
|
||||
@@ -241,23 +188,73 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
e := stanza.PubSubEvent{}
|
||||
ok = m.Get(&e)
|
||||
if ok {
|
||||
fmt.Println(e)
|
||||
}
|
||||
|
||||
/*
|
||||
if m.Body == "" {
|
||||
return
|
||||
}
|
||||
*/
|
||||
|
||||
originator := jid.MustParse(m.From).Bare().String()
|
||||
originator := JidMustParse(m.From).Bare()
|
||||
mStatus.SetText(originator)
|
||||
|
||||
at := new(Attention)
|
||||
ok = m.Get(at)
|
||||
if ok {
|
||||
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 {
|
||||
fm, ok := sc.Forwarded.Stanza.(stanza.Message)
|
||||
if ok {
|
||||
if JidMustParse(fm.From).Bare() == JidMustParse(m.From).Bare() {
|
||||
p = sc.Forwarded.Stanza
|
||||
m = sc.Forwarded.Stanza.(stanza.Message)
|
||||
} else {
|
||||
panic(fmt.Sprintln("Impersonation: ", fm.From, m.From))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rc := new(SentCarbon)
|
||||
ok = m.Get(rc)
|
||||
if ok {
|
||||
fm, ok := rc.Forwarded.Stanza.(stanza.Message)
|
||||
if ok {
|
||||
if JidMustParse(fm.From).Bare() == JidMustParse(m.From).Bare() {
|
||||
p = rc.Forwarded.Stanza
|
||||
m = rc.Forwarded.Stanza.(stanza.Message)
|
||||
} else {
|
||||
panic(fmt.Sprintln("Impersonation: ", fm.From, m.From))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glib.IdleAdd(func() {
|
||||
//uiQueue <- func() {
|
||||
b := gtk.NewBox(gtk.OrientationVertical, 0)
|
||||
ba, ok := generateMessageWidget(p).(*gtk.Box)
|
||||
if ok {
|
||||
b = ba
|
||||
}
|
||||
|
||||
tab, ok := tabs.Load(originator)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
typed_tab := tab.(*chatTab)
|
||||
|
||||
if ok {
|
||||
@@ -266,6 +263,11 @@ func main() {
|
||||
} else {
|
||||
fmt.Println("Got message when the tab does not exist!")
|
||||
}
|
||||
|
||||
ba, ok := generateMessageWidget(p).(*gtk.Box)
|
||||
if ok {
|
||||
b.Append(ba)
|
||||
}
|
||||
//}
|
||||
})
|
||||
})
|
||||
@@ -276,7 +278,8 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
if presence.Error != *new(stanza.Err) {
|
||||
if presence.Error.Reason != "" {
|
||||
beeep.Notify(fmt.Sprintf("%s : %s", presence.From, presence.Error.Reason), presence.Error.Text, cancelBytes)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -287,6 +290,10 @@ func main() {
|
||||
|
||||
if ok { // This is a presence stanza from a user in a MUC
|
||||
presence.Get(&ocu)
|
||||
// id := ocu.ID
|
||||
// if id == "" {
|
||||
id := JidMustParse(presence.From).Resource
|
||||
// }
|
||||
from, _ := stanza.NewJid(presence.From)
|
||||
muc := from.Bare()
|
||||
_, ok = mucmembers.Load(muc)
|
||||
@@ -302,13 +309,32 @@ func main() {
|
||||
typed_unit := unit.(mucUnit)
|
||||
|
||||
if presence.Type != "unavailable" {
|
||||
typed_unit.Members.Store(ocu.ID, presence)
|
||||
} else {
|
||||
typed_unit.Members.Delete(ocu.ID)
|
||||
_, ok := typed_unit.Members.Load(id)
|
||||
if !ok {
|
||||
glib.IdleAdd(func() {
|
||||
//uiQueue <- 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
|
||||
}
|
||||
|
||||
tab, ok := tabs.Load(muc)
|
||||
typed_tab := tab.(*chatTab)
|
||||
|
||||
if ok {
|
||||
typed_tab.msgs.Append(b)
|
||||
scrollToBottomAfterUpdate(scroller)
|
||||
} else {
|
||||
fmt.Println("Got message when the tab does not exist!")
|
||||
}
|
||||
})
|
||||
}
|
||||
typed_unit.Members.Store(id, presence)
|
||||
} else {
|
||||
typed_unit.Members.Delete(id)
|
||||
glib.IdleAdd(func() {
|
||||
b := gtk.NewBox(gtk.OrientationVertical, 0)
|
||||
ba, ok := generatePresenceWidget(p).(*gtk.Box)
|
||||
if ok {
|
||||
b = ba
|
||||
}
|
||||
@@ -322,7 +348,6 @@ func main() {
|
||||
} else {
|
||||
fmt.Println("Got message when the tab does not exist!")
|
||||
}
|
||||
//}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -338,11 +363,14 @@ func main() {
|
||||
if ok {
|
||||
userdevices.Store(user, userUnit{})
|
||||
|
||||
b := gtk.NewButtonWithLabel(user)
|
||||
b.ConnectClicked(func() {
|
||||
b.AddCSSClass("accent")
|
||||
b := gtk.NewLabel(user)
|
||||
gesture1 := gtk.NewGestureClick()
|
||||
gesture1.SetButton(1)
|
||||
gesture1.Connect("pressed", func() {
|
||||
switchToTab(user, &window.Window)
|
||||
})
|
||||
|
||||
b.AddController(gesture1)
|
||||
menu.Append(b)
|
||||
}
|
||||
}
|
||||
@@ -364,12 +392,14 @@ func main() {
|
||||
|
||||
userdevices.Store(user, typed_unit)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
})
|
||||
|
||||
c, err := xmpp.NewClient(&config, router, func(err error) {
|
||||
showErrorDialog(err)
|
||||
panic(err)
|
||||
connectionStatus.SetText(fmt.Sprintf("Disconnected: %s", err.Error()))
|
||||
connectionIcon.SetFromPaintable(clientAssets["disconnect"])
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
showErrorDialog(err)
|
||||
panic(err)
|
||||
@@ -379,21 +409,114 @@ func main() {
|
||||
|
||||
cm := xmpp.NewStreamManager(c, func(c xmpp.Sender) {
|
||||
fmt.Println("XMPP client connected")
|
||||
/*
|
||||
*/
|
||||
})
|
||||
|
||||
go func() {
|
||||
time.Sleep(3 * time.Second)
|
||||
err = cm.Run()
|
||||
for {
|
||||
time.Sleep(5 * time.Second)
|
||||
pingStatus.AddCSSClass("pending")
|
||||
before := time.Now()
|
||||
iq := new(stanza.IQ)
|
||||
iq.From = clientroot.Session.BindJid
|
||||
iq.To = iq.From
|
||||
iq.Type = "get"
|
||||
|
||||
ctx, _ := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
mychan, err := client.SendIQ(ctx, iq)
|
||||
if err != nil {
|
||||
showErrorDialog(err)
|
||||
panic(err)
|
||||
continue
|
||||
}
|
||||
_ = <-mychan
|
||||
|
||||
pingStatus.RemoveCSSClass("pending")
|
||||
delay := time.Since(before) / time.Millisecond
|
||||
pingStatus.SetText(fmt.Sprintf("%d ms", delay))
|
||||
pingTimes[0] = append(pingTimes[0], float64(delay))
|
||||
|
||||
}
|
||||
}()
|
||||
connectionStatus.SetText(fmt.Sprintf("Connected as %s", JidMustParse(clientroot.Session.BindJid).Bare()))
|
||||
connectionStatus.SetTooltipText(fmt.Sprintf("Binded JID: %s\nUsing TLS: %t", clientroot.Session.BindJid, clientroot.Session.TlsEnabled))
|
||||
connectionIcon.SetFromPaintable(clientAssets["connect"])
|
||||
// Enable carbons
|
||||
client.SendRaw(fmt.Sprintf(
|
||||
`<iq xmlns='jabber:client'
|
||||
from='%s'
|
||||
id='enable1'
|
||||
type='set'>
|
||||
<enable xmlns='urn:xmpp:carbons:2'/>
|
||||
</iq>
|
||||
`, clientroot.Session.BindJid))
|
||||
|
||||
// Join rooms in bookmarks
|
||||
if loadedConfig.JoinBookmarks {
|
||||
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 {
|
||||
go func() {
|
||||
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.NewLabel(jid)
|
||||
gesture1 := gtk.NewGestureClick()
|
||||
gesture1.SetButton(1)
|
||||
gesture1.Connect("pressed", func() {
|
||||
switchToTab(jid, &window.Window)
|
||||
})
|
||||
|
||||
b.AddController(gesture1)
|
||||
menu.Append(b)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
conc := func() {
|
||||
time.Sleep(3 * time.Second)
|
||||
connectionStatus.SetText("Connecting...")
|
||||
connectionIcon.SetFromPaintable(clientAssets["hourglass"])
|
||||
|
||||
err = cm.Run()
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
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)
|
||||
@@ -413,6 +536,103 @@ func activate(app *gtk.Application) {
|
||||
fileMenu := gio.NewMenu()
|
||||
fileMenu.Append("Join MUC", "app.join")
|
||||
fileMenu.Append("Start DM", "app.dm")
|
||||
fileMenu.Append("Destroy MUC", "app.destroymuc")
|
||||
|
||||
helpMenu := gio.NewMenu()
|
||||
helpMenu.Append("About", "app.about")
|
||||
|
||||
aboutAction := gio.NewSimpleAction("about", nil)
|
||||
aboutAction.ConnectActivate(func(p *glib.Variant) {
|
||||
a := gtk.AboutDialog{}
|
||||
a.SetVisible(true)
|
||||
})
|
||||
|
||||
destroymucAction := gio.NewSimpleAction("destroymuc", nil)
|
||||
destroymucAction.ConnectActivate(func(p *glib.Variant) {
|
||||
cur, ok := tabs.Load(current)
|
||||
if ok {
|
||||
cur := cur.(*chatTab)
|
||||
if cur.isMuc {
|
||||
win := gtk.NewWindow()
|
||||
win.SetTitle("Destroy MUC")
|
||||
win.SetDefaultSize(400, 1)
|
||||
win.SetResizable(false)
|
||||
|
||||
box := gtk.NewBox(gtk.OrientationVertical, 0)
|
||||
box.Append(gtk.NewLabel("Are you sure? This MUC will be gone forever! (a very long time)"))
|
||||
box.Append(gtk.NewLabel("If you wish to continue, type 'I understand'"))
|
||||
cancel := gtk.NewButtonWithLabel("Cancel")
|
||||
cancel.ConnectClicked(func() {
|
||||
win.SetVisible(false)
|
||||
})
|
||||
en := gtk.NewEntry()
|
||||
en.SetPlaceholderText("...")
|
||||
|
||||
submit := gtk.NewButtonWithLabel("Destroy")
|
||||
submit.ConnectClicked(func() {
|
||||
fmt.Println(en.Text())
|
||||
if en.Text() == "I understand" {
|
||||
cur, ok := tabs.Load(current)
|
||||
if ok {
|
||||
cur := cur.(*chatTab)
|
||||
if cur.isMuc {
|
||||
client.SendRaw(fmt.Sprintf(`
|
||||
<iq from='%s'
|
||||
id='begone'
|
||||
to='%s'
|
||||
type='set'>
|
||||
<query xmlns='http://jabber.org/protocol/muc#owner'>
|
||||
<destroy jid='%s'>
|
||||
<reason>User requested</reason>
|
||||
</destroy>
|
||||
</query>
|
||||
</iq>
|
||||
`, clientroot.Session.BindJid, current, JidMustParse(clientroot.Session.BindJid).Bare()))
|
||||
}
|
||||
}
|
||||
win.SetVisible(false)
|
||||
}
|
||||
})
|
||||
|
||||
box.Append(en)
|
||||
box.Append(submit)
|
||||
box.Append(cancel)
|
||||
|
||||
mu, ok := mucmembers.Load(current)
|
||||
if ok {
|
||||
typed_mu := mu.(mucUnit)
|
||||
typed_mu.Members.Range(func(k, v any) bool {
|
||||
user, ok := v.(stanza.Presence)
|
||||
if ok {
|
||||
mu := MucUser{}
|
||||
ok := user.Get(&mu)
|
||||
if ok {
|
||||
if mu.MucUserItem.JID != "" {
|
||||
if JidMustParse(mu.MucUserItem.JID).Bare() == JidMustParse(clientroot.Session.BindJid).Bare() {
|
||||
if mu.MucUserItem.Affiliation != "owner" {
|
||||
box.Append(gtk.NewLabel("You are not an owner of this MUC and thus will most likely not be able to delete it"))
|
||||
}
|
||||
// return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic("not ok")
|
||||
}
|
||||
} else {
|
||||
panic("not ok")
|
||||
}
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
panic("not ok")
|
||||
}
|
||||
|
||||
win.SetChild(box)
|
||||
win.SetVisible(true)
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
joinAction := gio.NewSimpleAction("join", nil)
|
||||
joinAction.ConnectActivate(func(p *glib.Variant) {
|
||||
@@ -447,7 +667,8 @@ func activate(app *gtk.Application) {
|
||||
|
||||
win := gtk.NewWindow()
|
||||
win.SetTitle("Join MUC")
|
||||
win.SetDefaultSize(200, 200)
|
||||
win.SetDefaultSize(400, 1)
|
||||
win.SetResizable(false)
|
||||
win.SetChild(box)
|
||||
|
||||
btn.ConnectClicked(func() {
|
||||
@@ -460,11 +681,14 @@ func activate(app *gtk.Application) {
|
||||
}
|
||||
|
||||
createTab(t, true)
|
||||
b := gtk.NewButtonWithLabel(t)
|
||||
b.ConnectClicked(func() {
|
||||
b.AddCSSClass("accent")
|
||||
b := gtk.NewLabel(t)
|
||||
gesture1 := gtk.NewGestureClick()
|
||||
gesture1.SetButton(1)
|
||||
gesture1.Connect("pressed", func() {
|
||||
switchToTab(t, &window.Window)
|
||||
})
|
||||
|
||||
b.AddController(gesture1)
|
||||
menu.Append(b)
|
||||
}
|
||||
win.SetVisible(false)
|
||||
@@ -475,8 +699,11 @@ func activate(app *gtk.Application) {
|
||||
})
|
||||
|
||||
app.AddAction(joinAction)
|
||||
app.AddAction(aboutAction)
|
||||
app.AddAction(destroymucAction)
|
||||
|
||||
the_menu.AppendSubmenu("File", fileMenu)
|
||||
the_menu.AppendSubmenu("Help", helpMenu)
|
||||
|
||||
the_menuBar := gtk.NewPopoverMenuBarFromModel(the_menu)
|
||||
app.SetMenubar(gio.NewMenu())
|
||||
@@ -486,7 +713,8 @@ func activate(app *gtk.Application) {
|
||||
window.Window.SetDefaultSize(500, 500)
|
||||
menu = gtk.NewBox(gtk.OrientationVertical, 0)
|
||||
|
||||
empty_dialog = gtk.NewLabel("You are not focused on any chats.")
|
||||
empty_dialog = gtk.NewImageFromPaintable(clientAssets["disabled_logo"])
|
||||
empty_dialog.SetPixelSize(100)
|
||||
empty_dialog.SetVExpand(true)
|
||||
|
||||
scroller = gtk.NewScrolledWindow()
|
||||
@@ -498,6 +726,99 @@ func activate(app *gtk.Application) {
|
||||
box := gtk.NewBox(gtk.OrientationVertical, 0)
|
||||
box.Append(the_menuBar)
|
||||
|
||||
statBar := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||||
|
||||
cBox := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||||
connectionIcon = gtk.NewImageFromPaintable((clientAssets["disconnect"]))
|
||||
connectionIcon.AddCSSClass("icon")
|
||||
connectionStatus = gtk.NewLabel("Disconnected")
|
||||
|
||||
cBox.Append(connectionIcon)
|
||||
cBox.Append(connectionStatus)
|
||||
|
||||
statBar.Append(cBox)
|
||||
|
||||
mBox := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||||
|
||||
gesture1 := gtk.NewGestureClick()
|
||||
gesture1.SetButton(1)
|
||||
gesture1.Connect("pressed", func() {
|
||||
current = mStatus.Text()
|
||||
switchToTab(current, &window.Window)
|
||||
})
|
||||
|
||||
mIcon = gtk.NewImageFromPaintable((clientAssets["comment"]))
|
||||
mIcon.AddCSSClass("icon")
|
||||
mStatus = gtk.NewLabel("-")
|
||||
mStatus.AddController(gesture1)
|
||||
|
||||
cBox.Append(mIcon)
|
||||
cBox.Append(mStatus)
|
||||
|
||||
statBar.Append(mBox)
|
||||
|
||||
pBox := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||||
pBox.SetTooltipText("Ping between you and your XMPP server\nRight-click to see graph")
|
||||
gesture := gtk.NewGestureClick()
|
||||
gesture.SetButton(3)
|
||||
gesture.Connect("pressed", func() {
|
||||
opt := charts.NewLineChartOptionWithData(pingTimes)
|
||||
opt.Title = charts.TitleOption{
|
||||
Text: "Server latency",
|
||||
}
|
||||
/*
|
||||
opt.XAxis.Labels = []string{
|
||||
// The 7 labels here match to the 7 values above
|
||||
"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
|
||||
}*/
|
||||
opt.Legend = charts.LegendOption{
|
||||
SeriesNames: []string{
|
||||
"Ping (ms)",
|
||||
},
|
||||
}
|
||||
|
||||
opt.StrokeSmoothingTension = 0.9
|
||||
|
||||
p := charts.NewPainter(charts.PainterOptions{
|
||||
Width: 600,
|
||||
Height: 400,
|
||||
})
|
||||
err := p.LineChart(opt)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
buf, err := p.Bytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
loader := gdkpixbuf.NewPixbufLoader()
|
||||
loader.Write(buf)
|
||||
loader.Close()
|
||||
|
||||
i := gtk.NewPictureForPaintable(gdk.NewTextureForPixbuf(loader.Pixbuf()))
|
||||
win := gtk.NewWindow()
|
||||
win.SetDefaultSize(600, 400)
|
||||
win.SetTitle("Server latency")
|
||||
box := gtk.NewBox(gtk.OrientationVertical, 0)
|
||||
box.Append(i)
|
||||
win.SetChild(box)
|
||||
win.SetVisible(true)
|
||||
})
|
||||
pBox.AddController(gesture)
|
||||
|
||||
i := (gtk.NewImageFromPaintable(clientAssets["chart_bar"]))
|
||||
i.AddCSSClass("icon")
|
||||
pBox.Append(i)
|
||||
pingStatus = gtk.NewLabel("...")
|
||||
pBox.Append(pingStatus)
|
||||
statBar.Append(pBox)
|
||||
|
||||
scrollerStatBar := gtk.NewScrolledWindow()
|
||||
scrollerStatBar.SetChild(statBar)
|
||||
box.Append(scrollerStatBar)
|
||||
|
||||
// scroller.SetChild(empty_dialog)
|
||||
scroller.SetChild(empty_dialog)
|
||||
menu_scroll := gtk.NewScrolledWindow()
|
||||
@@ -523,12 +844,15 @@ func activate(app *gtk.Application) {
|
||||
|
||||
entry_box := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||||
|
||||
en := gtk.NewEntry()
|
||||
en.SetPlaceholderText("Say something, what else are you gonna do here?")
|
||||
oob_en := gtk.NewEntry()
|
||||
oob_en.SetPlaceholderText("Embed URL")
|
||||
|
||||
message_en = gtk.NewEntry()
|
||||
message_en.SetPlaceholderText("Say something, what else are you gonna do here?")
|
||||
b := gtk.NewButtonWithLabel("Send")
|
||||
|
||||
sendtxt := func() {
|
||||
t := en.Text()
|
||||
t := message_en.Text()
|
||||
if t == "" {
|
||||
dialog := >k.AlertDialog{}
|
||||
dialog.SetDetail("detail")
|
||||
@@ -548,21 +872,45 @@ func activate(app *gtk.Application) {
|
||||
message_type = stanza.MessageTypeGroupchat
|
||||
}
|
||||
|
||||
err := sendMessage(client, current, message_type, t, "", "")
|
||||
exts := []stanza.MsgExtension{}
|
||||
if oob_en.Text() != "" {
|
||||
new_oob := new(stanza.OOB)
|
||||
new_oob.URL = oob_en.Text()
|
||||
exts = append(exts, new_oob)
|
||||
}
|
||||
|
||||
if strings.Contains(t, "@everyone") {
|
||||
start := strings.Index(t, "@everyone")
|
||||
end := start + len("@everyone")
|
||||
|
||||
new_mention := new(Mention)
|
||||
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)
|
||||
exts = append(exts, new_attention)
|
||||
}
|
||||
|
||||
err := sendMessage(client, current, message_type, t, "", "", exts)
|
||||
if err != nil {
|
||||
panic(err) // TODO: Show error message via GTK
|
||||
}
|
||||
en.SetText("")
|
||||
message_en.SetText("")
|
||||
scrollToBottomAfterUpdate(scroller)
|
||||
}
|
||||
|
||||
en.Connect("activate", sendtxt)
|
||||
message_en.Connect("activate", sendtxt)
|
||||
|
||||
b.ConnectClicked(sendtxt)
|
||||
|
||||
en.SetHExpand(true)
|
||||
message_en.SetHExpand(true)
|
||||
|
||||
entry_box.Append(en)
|
||||
entry_box.Append(oob_en)
|
||||
entry_box.Append(message_en)
|
||||
entry_box.Append(b)
|
||||
|
||||
box.Append(entry_box)
|
||||
|
||||
BIN
please_wait.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
rsrc_windows_amd64.syso
Normal file
28
style.css
@@ -33,3 +33,31 @@
|
||||
.visitor {
|
||||
color: grey;
|
||||
}
|
||||
|
||||
.pending {
|
||||
color: grey;
|
||||
transition-duration: 0.5s;
|
||||
}
|
||||
|
||||
.hat {
|
||||
background-color: orange;
|
||||
color: black;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.subject {
|
||||
color: lime;
|
||||
}
|
||||
|
||||
.icon {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.jid {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: white;
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
6
types.go
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/diamondburned/gotk4/pkg/gtk/v4"
|
||||
"mellium.im/xmpp/color"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -13,13 +14,16 @@ type chatTab struct {
|
||||
type lambdaConfig struct {
|
||||
Server string
|
||||
Username string
|
||||
Resource string
|
||||
Password string
|
||||
Insecure bool
|
||||
Nick string
|
||||
JoinBookmarks bool
|
||||
CVD color.CVD
|
||||
}
|
||||
|
||||
type mucUnit struct {
|
||||
// key: OccupantID
|
||||
// key: Resource
|
||||
// value: last user presence
|
||||
Members sync.Map
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package main
|
||||
|
||||
var lambda_version string = "0.1.0"
|
||||
var lambda_version string = "26w11a"
|
||||
|
||||
17
xmpp-attention.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"gosrc.io/xmpp/stanza"
|
||||
)
|
||||
|
||||
// Implementation of XEP-0224: Attention
|
||||
|
||||
type Attention struct {
|
||||
stanza.MsgExtension
|
||||
XMLName xml.Name `xml:"urn:xmpp:attention:0 attention"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{Space: "urn:xmpp:attention:0", Local: "attention"}, Attention{})
|
||||
}
|
||||
22
xmpp-bookmarks.go
Normal 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{})
|
||||
}
|
||||
26
xmpp-carbons.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"gosrc.io/xmpp/stanza"
|
||||
)
|
||||
|
||||
// Implementation of XEP-0280: Message Carbons
|
||||
// https://xmpp.org/extensions/xep-0280.html
|
||||
|
||||
type ReceivedCarbon struct {
|
||||
stanza.MsgExtension
|
||||
XMLName xml.Name `xml:"urn:xmpp:carbons:2 received"`
|
||||
Forwarded stanza.Forwarded
|
||||
}
|
||||
|
||||
type SentCarbon struct {
|
||||
stanza.MsgExtension
|
||||
XMLName xml.Name `xml:"urn:xmpp:carbons:2 sent"`
|
||||
Forwarded stanza.Forwarded
|
||||
}
|
||||
|
||||
func init() {
|
||||
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{Space: "urn:xmpp:carbons:2", Local: "received"}, ReceivedCarbon{})
|
||||
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{Space: "urn:xmpp:carbons:2", Local: "sent"}, SentCarbon{})
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
// 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 {
|
||||
func sendMessage(c xmpp.Sender, sendTo string, msgType stanza.StanzaType, body string, subject string, thread string, exts []stanza.MsgExtension) error {
|
||||
m := stanza.Message{
|
||||
Attrs: stanza.Attrs{
|
||||
To: sendTo,
|
||||
@@ -18,6 +18,7 @@ func sendMessage(c xmpp.Sender, sendTo string, msgType stanza.StanzaType, body s
|
||||
Body: body,
|
||||
Subject: subject,
|
||||
Thread: thread,
|
||||
Extensions: exts,
|
||||
}
|
||||
err := c.Send(m)
|
||||
if err != nil {
|
||||
@@ -50,7 +51,7 @@ func joinMuc(c xmpp.Sender, jid string, muc string, nick string) error {
|
||||
// jid MustParse but using gosrc's instead of mellium
|
||||
// This function will panic if its an invalid JID
|
||||
|
||||
func JidMustParse(s string) (*stanza.Jid) {
|
||||
func JidMustParse(s string) *stanza.Jid {
|
||||
j, err := stanza.NewJid(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
23
xmpp-mentions.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"gosrc.io/xmpp/stanza"
|
||||
)
|
||||
|
||||
// Experimental implementation of XEP-XXXX: Explicit Mentions
|
||||
// https://git.isekai.rocks/snit/protoxeps/tree/explicit-mentions.xml
|
||||
|
||||
type Mention struct {
|
||||
stanza.MsgExtension
|
||||
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() {
|
||||
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{Space: "urn:xmpp:mentions:0", Local: "mention"}, Mention{})
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -20,8 +21,15 @@ type MucUserItem struct {
|
||||
Role string `xml:"role,attr,omitempty"` // TODO: Use enum
|
||||
JID string `xml:"jid,attr,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{})
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
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{})
|
||||
}
|
||||
21
xmpp-webxdc.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
// Implementation of XEP-0491: WebXDC
|
||||
// https://xmpp.org/extensions/xep-0491.html
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"gosrc.io/xmpp/stanza"
|
||||
)
|
||||
|
||||
type XDCEl struct {
|
||||
stanza.MsgExtension
|
||||
XMLName xml.Name `xml:"urn:xmpp:webxdc:0 x"`
|
||||
Document string `xml:"document"`
|
||||
Summary string `xml:"summary"`
|
||||
Payload string `xml:"urn:xmpp:json:0 json"` // TODO: Independent JSOn container type (XEP-0335)
|
||||
}
|
||||
|
||||
func init() {
|
||||
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{Space: "urn:xmpp:webxdc:0", Local: "x"}, XDCEl{})
|
||||
}
|
||||