Compare commits

..

42 Commits

Author SHA1 Message Date
sunglocto 82aa2abfbd t 2026-05-03 11:10:15 +01:00
sunglocto 58c7165ce5 fix avatars 2026-05-02 06:56:08 +01:00
sunglocto 5bcf0eda0b t 2026-05-01 21:05:14 +01:00
sunglocto 0bfb140dc7 Fix lag issues 2026-04-30 14:55:17 +01:00
sunglocto 7b63799f0b Add extra fields to vCard and format code 2026-04-28 13:08:16 +01:00
sunglocto a97c42323c Do not let ebassi see this code 2026-04-28 12:58:00 +01:00
sunglocto fc0ed5ac2c somehow sunglocto returned 2026-04-26 10:40:13 +01:00
sunglocto 69994d9856 remove base64 encoding/decoding which was not needed for embedding assets 2026-04-18 11:09:59 +01:00
sunglocto 843687ff2b add throughput, format code, and begin to add MUC preview window 2026-04-18 10:29:07 +01:00
sunglocto 7416aa37e0 Merge branch 'master' of https://forge.sunglocto.net/sunglocto/lambda 2026-04-06 11:05:49 +01:00
sunglocto ce83000e0c i forgor to commit sorry 2026-04-06 11:04:53 +01:00
sunglocto 359e8ed63e 'I like small icons, the big ones weird me out' 2026-03-17 19:04:43 +00:00
sunglocto 3b9df94cf6 add CONTRIBUTING.md 2026-03-17 19:02:52 +00:00
sunglocto 634f451595 Merge branch 'master' of https://forge.sunglocto.net/sunglocto/lambda 2026-03-17 18:56:02 +00:00
sunglocto 00d7945f70 add kick png (not initialised yet) 2026-03-17 18:53:08 +00:00
sunglocto 60d6a287e5 oops 2026-03-15 16:14:27 +00:00
sunglocto d5981b7475 icl i forgot i had this file 2026-03-15 09:58:27 +00:00
sunglocto 523722bae6 Format code 2026-03-15 09:57:08 +00:00
sunglocto 71e6a58fbd ban, kick and change role&affil menu, status icons in muc, blocking avatars that are invalid so we dont fetch them over and over again 2026-03-15 09:55:26 +00:00
sunglocto a7e90e4ae5 fix avatars in rc menu 2026-03-12 16:17:29 +00:00
sunglocto 654ab8b618 add some icons and only store members by resource 2026-03-12 16:09:22 +00:00
sunglocto 7a201808e3 format code & add additional assets 2026-03-10 16:48:10 +00:00
sunglocto 77e4e444d4 Attention and experimental mentions impl 2026-03-10 16:35:56 +00:00
sunglocto bf1685a382 MODS 2026-02-28 16:24:37 +00:00
sunglocto 0ac43946b1 this commit was sponsored by RAID SHADOW LEGENDS 2026-02-22 08:16:14 +00:00
sunglocto 15ff7a20db prevent loading svg and webp if on netbsd (thanks gtk) 2026-02-20 09:01:40 +00:00
sunglocto 1dd3f09fed ping graph 2026-02-20 06:28:31 +00:00
sunglocto 87bdbc440a i'll do as much, for my true love, as any young girl may 2026-02-19 14:30:37 +00:00
sunglocto 62b5a9db72 cold blows the wind over my true love, cold blows the drops of rain 2026-02-19 14:30:04 +00:00
sunglocto 39156af48a format code, add confirmation to destroy muc, implement some disconnect logic 2026-02-19 11:28:18 +00:00
sunglocto 3f40d3da29 stat bar 2026-02-17 09:27:46 +00:00
sunglocto a7a49f7441 format code 2026-02-16 09:54:37 +00:00
sunglocto e026e777f6 BOOKMARKS! BOOKMARKS! WE GOT BOOKMARKS PEOPLE 2026-02-16 09:52:25 +00:00
sunglocto 09a809c102 add Destroy room button, show error notification on presence error and show disabled lambda icon when not focused on any chat 2026-02-15 13:08:52 +00:00
sunglocto 29ef37e237 Format code, remove use of Mellium JID parser, and add indicator for messages originating from WebXDC apps 2026-02-14 22:38:32 +00:00
sunglocto 713cb24508 Add hat icon, change some CSS and remove all reply support 2026-02-08 09:44:45 +00:00
sunglocto 589101c292 add info to readme 2026-02-04 19:29:56 +00:00
sunglocto f807565cb2 Add nickname fallback for rooms that don't have XEP-0421 2026-02-04 17:57:24 +00:00
sunglocto 6626d35920 fix some crashes and debug smtn 2026-02-04 10:12:49 +00:00
sunglocto 5c76729a6b Attempt to fix duplicated user tabs 2026-02-03 10:38:41 +00:00
sunglocto c260b8b231 add more support for other message types 2026-02-03 10:07:33 +00:00
sunglocto 971147dcb8 add icon 2026-02-02 18:57:11 +00:00
76 changed files with 2369 additions and 405 deletions
+15
View 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.
+4
View File
@@ -1,3 +1,7 @@
# lambda # lambda
an XMPP client 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)
+2 -3
View File
@@ -1,7 +1,6 @@
# TODO # TODO
- XEP-0393: Message Styling 0% - XEP-0393: Message Styling 0%
- XEP-0402: PEP Native Bookmarks 0% - XEP-0402: PEP Native Bookmarks 50% (often lags out client if enabled)
- XEP-0066: Out of Band Data partial - XEP-0461: Message Replies 0%
- XEP-0461: Message Replies partial
- XEP-0444: Message Reactions partial - XEP-0444: Message Reactions partial
+199
View File
@@ -0,0 +1,199 @@
package main
import (
_ "embed"
"github.com/diamondburned/gotk4/pkg/gdk/v4"
"github.com/diamondburned/gotk4/pkg/gdkpixbuf/v2"
)
//go:embed debug.png
var defaultAvatarBytes []byte
//go:embed failed_load.png
var failedBytes []byte
//go:embed assets/owner.png
var ownerMedalBytes []byte
//go:embed assets/admin.png
var adminMedalBytes []byte
//go:embed assets/member.png
var memberMedalBytes []byte
//go:embed assets/noaffiliation.png
var noneMedalBytes []byte
//go:embed assets/outcast.png
var outcastMedalBytes []byte
//go:embed assets/cancel.png
var cancelBytes []byte
//go:embed assets/status_away.png
var sABytes []byte
//go:embed assets/status_busy.png
var sBBytes []byte
//go:embed assets/status_chatty.png
var sCBytes []byte
//go:embed assets/status_online.png
var sOBytes []byte
//go:embed assets/status_xa.png
var xaBytes []byte
//go:embed assets/tag.png
var tagBytes []byte
//go:embed assets/lambda-disabled.png
var logoDisabledBytes []byte
//go:embed assets/group.png
var groupBytes []byte
//go:embed assets/door_in.png
var doorInBytes []byte
//go:embed assets/door_out.png
var doorOutBytes []byte
//go:embed assets/large_group.png
var largeGroupBytes []byte
//go:embed assets/world.png
var worldBytes []byte
//go:embed assets/disconnect.png
var disconnectBytes []byte
//go:embed assets/chart_bar.png
var barBytes []byte
//go:embed assets/chart_bar_laggy.png
var barLaggyBytes []byte
//go:embed assets/ok.png
var okBytes []byte
//go:embed assets/hourglass.png
var hourglassBytes []byte
//go:embed assets/connect_tls.png
var connectBytes []byte
//go:embed assets/comment.png
var commentBytes []byte
//go:embed assets/information.png
var informationBytes []byte
//go:embed assets/car.png
var carBytes []byte
//go:embed assets/car_high.png
var carHighBytes []byte
// muc icons
//go:embed assets/muc_open.png
var mucOpenBytes []byte
//go:embed assets/muc_membersonly.png
var mucMembersOnlyBytes []byte
//go:embed assets/muc_passwordprotected.png
var mucPasswordProtectedBytes []byte
//go:embed assets/muc_unsecured.png
var mucUnsecuredBytes []byte
//go:embed assets/muc_hidden.png
var mucHiddenBytes []byte
//go:embed assets/muc_public.png
var mucPublicBytes []byte
//go:embed assets/muc_unmoderated.png
var mucUnmoderatedBytes []byte
//go:embed assets/muc_moderated.png
var mucModeratedBytes []byte
//go:embed assets/muc_nonanonymous.png
var mucNonAnonymousBytes []byte
//go:embed assets/muc_semianonymous.png
var mucSemiAnonymousBytes []byte
//go:embed assets/muc_persistent.png
var mucPersistentBytes []byte
//go:embed assets/muc_temporary.png
var mucTemporaryBytes []byte
//go:embed assets/moderate.png
var moderateBytes []byte
//go:embed assets/jabber.png
var jabberBytes []byte
func loadAsset(key string, data []byte) {
loader := gdkpixbuf.NewPixbufLoader()
loader.Write(data)
loader.Close()
clientAssets[key] = gdk.NewTextureForPixbuf(loader.Pixbuf())
}
func init() {
for key, data := range map[string][]byte{
"DefaultAvatar": defaultAvatarBytes,
"FailedAvatar": failedBytes,
"owner": ownerMedalBytes,
"admin": adminMedalBytes,
"member": memberMedalBytes,
"none": noneMedalBytes,
"outcast": outcastMedalBytes,
"cancel": cancelBytes,
"tag": tagBytes,
"disabled_logo": logoDisabledBytes,
"group": groupBytes,
"door_in": doorInBytes,
"door_out": doorOutBytes,
"large_group": largeGroupBytes,
"world": worldBytes,
"disconnect": disconnectBytes,
"chart_bar": barBytes,
"chart_bar_laggy": barLaggyBytes,
"ok": okBytes,
"hourglass": hourglassBytes,
"connect": connectBytes,
"comment": commentBytes,
"information": informationBytes,
"status_away": sABytes,
"status_dnd": sBBytes,
"status_chat": sCBytes,
"status_xa": xaBytes,
"status_": sOBytes,
"car": carBytes,
"car_high": carHighBytes,
"muc_open": mucOpenBytes,
"muc_membersonly": mucMembersOnlyBytes,
"muc_passwordprotected": mucPasswordProtectedBytes,
"muc_unsecured": mucUnsecuredBytes,
"muc_hidden": mucHiddenBytes,
"muc_public": mucPublicBytes,
"muc_unmoderated": mucUnmoderatedBytes,
"muc_moderated": mucModeratedBytes,
"muc_nonanonymous": mucNonAnonymousBytes,
"muc_semianonymous": mucSemiAnonymousBytes,
"muc_persistent": mucPersistentBytes,
"muc_temporary": mucTemporaryBytes,
"moderate": moderateBytes,
"jabber": jabberBytes,
} {
loadAsset(key, data)
}
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 607 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 B

+1
View File
@@ -0,0 +1 @@
All client assets are owned by their respective owners
Binary file not shown.

After

Width:  |  Height:  |  Size: 413 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 796 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 730 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 747 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 793 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 935 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 970 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 806 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 622 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 924 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 882 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 460 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 751 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 910 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 362 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 923 B

+38 -25
View File
@@ -13,10 +13,15 @@ import (
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"sync"
) )
// global or app-level map/cache // global or app-level map/cache
var textureCache = make(map[string]gdk.Paintabler) // var textureCache = make(map[string]gdk.Paintabler)
var textureCache sync.Map
// 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) { func ensureCache() (string, error) {
cachePath := configdir.LocalCache("lambda-im") cachePath := configdir.LocalCache("lambda-im")
@@ -28,49 +33,57 @@ func ensureCache() (string, error) {
return cachePath, nil return cachePath, nil
} }
func getTexture(path string) gdk.Paintabler { func getTexture(path string) (gdk.Paintabler, error) {
if tex, exists := textureCache[path]; exists { tex, exists := textureCache.Load(path)
return tex if exists {
return tex.(gdk.Paintabler), nil
} }
tex, err := gdk.NewTextureFromFilename(path) // load once tex, err := gdk.NewTextureFromFilename(path) // load once
if err != nil { if err != nil {
panic(err) return nil, err
} }
textureCache[path] = tex textureCache.Store(path, tex)
return tex return tex.(gdk.Paintabler), nil
} }
func newPictureFromPath(path string) *gtk.Picture { func newPictureFromPath(path string) (*gtk.Picture, error) {
tex := getTexture(path) tex, err := getTexture(path)
if err != nil {
return nil, err
}
img := gtk.NewPictureForPaintable(tex) img := gtk.NewPictureForPaintable(tex)
return img return img, nil
} }
func newImageFromPath(path string) *gtk.Image { func newImageFromPath(path string) (*gtk.Image, error) {
tex := getTexture(path) tex, err := getTexture(path)
if err != nil {
return nil, err
}
img := gtk.NewImageFromPaintable(tex) img := gtk.NewImageFromPaintable(tex)
return img return img, nil
} }
func newPictureFromWeb(url string) *gtk.Picture { func newPictureFromWeb(url string) (*gtk.Picture, error) {
pa, _ := ensureCache() pa, _ := ensureCache()
// step 1: get a sha256 sum of the URL // step 1: get a sha256 sum of the URL
sum := fmt.Sprintf("%x", sha256.Sum256([]byte(url))) sum := fmt.Sprintf("%x", sha256.Sum256([]byte(url)))
p, ok := textureCache[sum] p, ok := textureCache.Load(sum)
if ok { if ok {
return gtk.NewPictureForPaintable(p) return gtk.NewPictureForPaintable(p.(gdk.Paintabler)), nil
} }
// step 2: download it // step 2: download it
resp, err := http.Get(url) resp, err := http.Get(url)
if err != nil { if err != nil {
return nil return nil, err
} }
b, err := io.ReadAll(resp.Body) b, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil return nil, err
} }
fullpath := filepath.Join(pa, sum) fullpath := filepath.Join(pa, sum)
@@ -78,31 +91,31 @@ func newPictureFromWeb(url string) *gtk.Picture {
// step 3: save it // step 3: save it
err = os.WriteFile(fullpath, b, 0644) err = os.WriteFile(fullpath, b, 0644)
if err != nil { if err != nil {
return nil return nil, err
} }
return newPictureFromPath(fullpath) return newPictureFromPath(fullpath)
} }
func newImageFromWeb(url string) *gtk.Image { func newImageFromWeb(url string) (*gtk.Image, error) {
pa, _ := ensureCache() pa, _ := ensureCache()
// step 1: get a sha256 sum of the URL // step 1: get a sha256 sum of the URL
sum := fmt.Sprintf("%x", sha256.Sum256([]byte(url))) sum := fmt.Sprintf("%x", sha256.Sum256([]byte(url)))
p, ok := textureCache[sum] p, ok := textureCache.Load(sum)
if ok { if ok {
return gtk.NewImageFromPaintable(p) return gtk.NewImageFromPaintable(p.(gdk.Paintabler)), nil
} }
// step 2: download it // step 2: download it
resp, err := http.Get(url) resp, err := http.Get(url)
if err != nil { if err != nil {
return nil return nil, err
} }
b, err := io.ReadAll(resp.Body) b, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil return nil, err
} }
fullpath := filepath.Join(pa, sum) fullpath := filepath.Join(pa, sum)
@@ -110,7 +123,7 @@ func newImageFromWeb(url string) *gtk.Image {
// step 3: save it // step 3: save it
err = os.WriteFile(fullpath, b, 0644) err = os.WriteFile(fullpath, b, 0644)
if err != nil { if err != nil {
return nil return nil, err
} }
return newImageFromPath(fullpath) return newImageFromPath(fullpath)
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

+26 -4
View File
@@ -4,23 +4,45 @@ go 1.25.5
require ( require (
github.com/BurntSushi/toml v1.6.0 github.com/BurntSushi/toml v1.6.0
github.com/boxes-ltd/imaging v1.7.5
github.com/crazy3lf/colorconv v1.2.0
github.com/diamondburned/gotk4/pkg v0.3.1 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/google/uuid v1.1.1
github.com/jacoblockett/sanitizefilename v1.0.1 github.com/jacoblockett/sanitizefilename v1.0.1
github.com/jasonlovesdoggo/gopen v0.0.0-20250130105607-39c98c645030 github.com/jasonlovesdoggo/gopen v0.0.0-20250130105607-39c98c645030
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f
github.com/kr/pretty v0.1.0 github.com/kr/pretty v0.2.0
github.com/mskrha/svg2png v0.0.0-20240706085601-64fa78f4eb07
github.com/rrivera/identicon v0.0.0-20240116195454-d5ba35832c0d
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef
golang.org/x/net v0.29.0
gosrc.io/xmpp v0.5.1 gosrc.io/xmpp v0.5.1
mellium.im/xmpp v0.22.0 mellium.im/xmpp v0.22.0
) )
require ( require (
git.sr.ht/~jackmordaunt/go-toast v1.1.2 // indirect
github.com/KarpelesLab/weak v0.1.1 // 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/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 go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 // indirect
golang.org/x/net v0.29.0 // indirect golang.org/x/image v0.36.0 // indirect
golang.org/x/sync v0.8.0 // indirect golang.org/x/sync v0.19.0 // indirect
golang.org/x/text v0.18.0 // indirect golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 // indirect golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 // indirect
mellium.im/reader v0.1.0 // indirect mellium.im/reader v0.1.0 // indirect
mellium.im/xmlstream v0.15.4 // indirect mellium.im/xmlstream v0.15.4 // indirect
+67 -9
View File
@@ -1,27 +1,50 @@
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 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 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 h1:fNnlPo3aypS9tBzoEQluY13XyUfd/eWaSE/vMvo9s4g=
github.com/KarpelesLab/weak v0.1.1/go.mod h1:pzXsWs5f2bf+fpgHayTlBE1qJpO3MpJKo5sRaLu1XNw= github.com/KarpelesLab/weak v0.1.1/go.mod h1:pzXsWs5f2bf+fpgHayTlBE1qJpO3MpJKo5sRaLu1XNw=
github.com/agnivade/wasmbrowsertest v0.3.1/go.mod h1:zQt6ZTdl338xxRaMW395qccVE2eQm0SjC/SDz0mPWQI= github.com/agnivade/wasmbrowsertest v0.3.1/go.mod h1:zQt6ZTdl338xxRaMW395qccVE2eQm0SjC/SDz0mPWQI=
github.com/boxes-ltd/imaging v1.7.5 h1:k4kYxJEhysoGhEEN1IEeKoSbnG8/8snjj7M48Ok0fnk=
github.com/boxes-ltd/imaging v1.7.5/go.mod h1:+8H+oRvis3InOFtTpcoCCB1RDXqo6p9tQBtjZfWnrC8=
github.com/chromedp/cdproto v0.0.0-20190614062957-d6d2f92b486d/go.mod h1:S8mB5wY3vV+vRIzf39xDXsw3XKYewW9X6rW2aEmkrSw= 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-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-20190812224334-39ef923dcb8d/go.mod h1:0YChpVzuLJC5CPr+x3xkHN6Z8KOSXjNbL7qV8Wc4GW0=
github.com/chromedp/cdproto v0.0.0-20190926234355-1b4886c6fad6/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.3.1-0.20190619195644-fd957a4d2901/go.mod h1:mJdvfrVn594N9tfiPecUidF6W5jPRKHymqHfzbobPsM=
github.com/chromedp/chromedp v0.4.0/go.mod h1:DC3QUn4mJ24dwjcaGQLoZrhm4X/uPHZ6spDbS2uFhm4= github.com/chromedp/chromedp v0.4.0/go.mod h1:DC3QUn4mJ24dwjcaGQLoZrhm4X/uPHZ6spDbS2uFhm4=
github.com/crazy3lf/colorconv v1.2.0 h1:UM7kSZWnwFMGiC+PpYrjxQSOd6sEyWb+dRKKTd3KslA=
github.com/crazy3lf/colorconv v1.2.0/go.mod h1:2jTJ7QCWCj2sSLOhF4Gzi0J5/hoX8/VY8VzNvXAlD1I=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/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 h1:uhkXSUPUsCyz3yujdvl7DSN8jiLS2BgNTQE95hk6ygg=
github.com/diamondburned/gotk4/pkg v0.3.1/go.mod h1:DqeOW+MxSZFg9OO+esk4JgQk0TiUJJUBfMltKhG+ub4= 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/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.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.7.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/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.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-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/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/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= 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.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 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -33,6 +56,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 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/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 h1:HhPMoPp05U5aKjhht+d7lsqhyF4trKSBme0FE6S/1C4=
github.com/jacoblockett/sanitizefilename v1.0.1/go.mod h1:/cQHSz2fHeR1iDKpHTSW/MIyONa+Uqj0eszbvPuIxNs= 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= github.com/jasonlovesdoggo/gopen v0.0.0-20250130105607-39c98c645030 h1:NFCJG3BerP/5ZLXwu08x9xDs+9p7AYFMeo5IXjGANxw=
@@ -43,8 +68,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/knq/sysutil v0.0.0-20181215143952-f05b59f0f307/go.mod h1:BjPj+aVjl9FW/cCGiF3nGh5v+9Gd3VCgBQbod/GlMaQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/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/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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
@@ -58,21 +84,45 @@ 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.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.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mskrha/svg2png v0.0.0-20240706085601-64fa78f4eb07 h1:7fan6wzUXasMPMHho2ePSkB+QTEb0Rh/f6B+IkkP1Sc=
github.com/mskrha/svg2png v0.0.0-20240706085601-64fa78f4eb07/go.mod h1:KFdfdIgpr48ODxdkxKvpcYwuyLpQ6rfkAsFB2UQ6jD4=
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.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.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/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.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rrivera/identicon v0.0.0-20240116195454-d5ba35832c0d h1:l3+2LWCbVxn5itfvXAfH9n4YL9jh8l1g5zcncbIc1cs=
github.com/rrivera/identicon v0.0.0-20240116195454-d5ba35832c0d/go.mod h1:TbpErkob6SY7cyozRVSGoB3OlO2qOAgVN8O3KAJ4fMI=
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.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 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.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE=
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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.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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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.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= 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.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 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
@@ -85,9 +135,11 @@ 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.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 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/image v0.36.0 h1:Iknbfm1afbgtwPTmHnS2gTM/6PPZfH+z2EFuOkSbqwc=
golang.org/x/image v0.36.0/go.mod h1:YsWD2TyyGKiIX1kZlu9QfKIsQ4nAAK9bdgdrIsE7xy4=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 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.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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-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-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -97,8 +149,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-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-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.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -108,15 +160,18 @@ 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-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-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190927073244-c990c680b611/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.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= 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/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-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.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.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= 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= 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/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
@@ -127,6 +182,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/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.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/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 h1:Rgrm5s2rt+npGggJH3HakQxQXR8ZZz3+QRzakRQqaq4=
gosrc.io/xmpp v0.5.1/go.mod h1:L3NFMqYOxyLz3JGmgFyWf7r9htE91zVGiK40oW4RwdY= gosrc.io/xmpp v0.5.1/go.mod h1:L3NFMqYOxyLz3JGmgFyWf7r9htE91zVGiK40oW4RwdY=
gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
+461 -52
View File
@@ -1,11 +1,22 @@
package main package main
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"github.com/boxes-ltd/imaging"
"github.com/crazy3lf/colorconv"
"github.com/diamondburned/gotk4/pkg/gdk/v4"
"github.com/diamondburned/gotk4/pkg/gdkpixbuf/v2"
"github.com/diamondburned/gotk4/pkg/glib/v2" "github.com/diamondburned/gotk4/pkg/glib/v2"
"github.com/diamondburned/gotk4/pkg/gtk/v4" "github.com/diamondburned/gotk4/pkg/gtk/v4"
"github.com/diamondburned/gotk4/pkg/pango"
"github.com/rrivera/identicon"
"gosrc.io/xmpp/stanza" "gosrc.io/xmpp/stanza"
"image"
"image/png"
xmpp_color "mellium.im/xmpp/color"
"strconv"
) )
func scrollToBottomAfterUpdate(scrolledWindow *gtk.ScrolledWindow) { func scrollToBottomAfterUpdate(scrolledWindow *gtk.ScrolledWindow) {
@@ -16,17 +27,22 @@ func scrollToBottomAfterUpdate(scrolledWindow *gtk.ScrolledWindow) {
}) })
} }
func createTab(jid string, isMuc bool) bool { func createTab(jid string, isMuc bool, name string) bool {
fmt.Println("Creating tab", jid, "isMuc:", isMuc) if name == "" {
name = jid
}
_, ok := tabs.Load(jid) _, ok := tabs.Load(jid)
if !ok { _, uok := userdevices.Load(jid)
_, mok := mucmembers.Load(jid)
if !ok && !uok && !mok {
newTab := new(chatTab) newTab := new(chatTab)
newTab.isMuc = isMuc newTab.isMuc = isMuc
newTab.msgs = gtk.NewListBox() newTab.msgs = gtk.NewListBox()
newTab.msgs.SetVExpand(true) newTab.msgs.SetVExpand(true)
newTab.msgs.SetShowSeparators(true) newTab.msgs.SetShowSeparators(true)
newTab.name = name
newTab.msgs.Append(gtk.NewButtonWithLabel("Get past messages...")) newTab.msgs.Append(gtk.NewButtonWithLabel(loadedLocale["getPastMessages"]))
tabs.Store(jid, newTab) tabs.Store(jid, newTab)
return true return true
} }
@@ -42,73 +58,279 @@ func switchToTab(jid string, w *gtk.Window) {
} }
typed_tab := tab.(*chatTab) typed_tab := tab.(*chatTab)
scroller.SetChild(typed_tab.msgs) scroller.SetChild(typed_tab.msgs)
typingStatus.SetText("")
if typed_tab.isMuc { if typed_tab.isMuc {
m, _ := mucmembers.Load(jid) m, ok := mucmembers.Load(jid)
ma := m.(mucUnit) if !ok {
return
}
ma, ok := m.(mucUnit)
if !ok {
return
}
mm := ma.Members mm := ma.Members
gen := gtk.NewBox(gtk.OrientationVertical, 0) gen := gtk.NewBox(gtk.OrientationVertical, 10)
mm.Range(func(k, v any) bool { i := 0
userbox := gtk.NewBox(gtk.OrientationHorizontal, 0) rangeOrdered(&mm, (func(k, v any) bool {
i++
u, ok := v.(stanza.Presence)
if !ok {
return true
}
userbox := gtk.NewBox(gtk.OrientationHorizontal, 2)
u := v.(stanza.Presence)
var mu MucUser var mu MucUser
var ocu OccupantID var ocu OccupantID
u.Get(&mu) u.Get(&mu)
u.Get(&ocu) u.Get(&ocu)
nick_label := gtk.NewLabel(JidMustParse(u.From).Resource) if mu.MucUserItem.Role == "moderator" {
/* gen.Prepend(userbox)
affil_label := gtk.NewLabel("") } else {
switch mu.MucUserItem.Affiliation { gen.Append(userbox)
case "owner":
affil_label.SetText("O")
case "admin":
affil_label.SetText("A")
case "member":
affil_label.SetText("M")
case "none":
affil_label.SetText("-")
} }
*/
//id := ocu.ID
//if id == "" {
id := JidMustParse(u.From).Resource
//}
nick_label := gtk.NewLabel(JidMustParse(u.From).Resource)
nick_label.SetEllipsize(pango.EllipsizeEnd)
nick_label.AddCSSClass(mu.MucUserItem.Role) nick_label.AddCSSClass(mu.MucUserItem.Role)
if mu.MucUserItem.Role == "visitor" { if mu.MucUserItem.Role == "visitor" {
nick_label.SetOpacity(0.5) nick_label.SetOpacity(0.5)
} }
/* userbox.SetTooltipText(fmt.Sprintf("%s\n%s\n%s\n%s", u.From, mu.MucUserItem.Role, mu.MucUserItem.Affiliation, loadedLocale["clickForMoreInfo"]))
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.Append(nick_label) userbox.Append(nick_label)
// userbox.Append(affil_label)
var hats Hats
ok = u.Get(&hats)
if ok {
for _, hat := range hats.Hats {
var val float64
if hat.Hue != "" {
tval, _ := strconv.Atoi(hat.Hue)
val = float64(tval)
} else {
xc := xmpp_color.String(hat.URI, 255, loadedConfig.CVD)
r, g, b, _ := xc.RGBA()
val, _, _ = colorconv.RGBToHSV(uint8(r), uint8(g), uint8(b))
}
tB := tagBytes
img, _, _ := image.Decode(bytes.NewReader(tB))
i_rgba := imaging.AdjustHue(img, val)
var buf bytes.Buffer
png.Encode(&buf, i_rgba)
tB = buf.Bytes()
loader := gdkpixbuf.NewPixbufLoader()
loader.Write(tB)
loader.Close()
tag := gtk.NewPictureForPaintable(gdk.NewTextureForPixbuf(loader.Pixbuf()))
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 := gtk.NewImageFromPaintable(clientAssets[mu.MucUserItem.Affiliation])
medal.SetTooltipText(mu.MucUserItem.Affiliation)
medal.SetHAlign(gtk.AlignEnd) medal.SetHAlign(gtk.AlignEnd)
medal.SetHExpand(true) medal.SetHExpand(true)
userbox.Append(medal) userbox.Append(medal)
default_av := createIdenticon(u.From)
userbox.Prepend(default_av)
var vcu VCardUpdate
ok = u.Get(&vcu)
if ok {
photo := vcu.Photo
go func() {
new_im := getAvatar(u.From, photo)
glib.IdleAdd(func() {
userbox.Remove(default_av)
userbox.Prepend(new_im)
})
}()
}
gesture := gtk.NewGestureClick() 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(loadedLocale["ban"])
kb := gtk.NewButtonWithLabel(loadedLocale["kick"])
ab := gtk.NewButtonWithLabel(loadedLocale["setAffil"])
rb := gtk.NewButtonWithLabel(loadedLocale["setRole"])
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(loadedLocale["setAffilDescPartOne"] + JidMustParse(u.From).Resource + loadedLocale["setAffilDescPartTwo"]))
the_entry := gtk.NewEntry()
the_entry.SetText(mu.MucUserItem.Affiliation)
submit := gtk.NewButtonWithLabel(loadedLocale["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(loadedLocale["setRoleDescPartOne"] + JidMustParse(u.From).Resource + loadedLocale["setRoleDescPartTwo"]))
box.Append(gtk.NewLabel(loadedLocale["setRoleWarning"]))
the_entry := gtk.NewEntry()
the_entry.SetText(mu.MucUserItem.Role)
submit := gtk.NewButtonWithLabel(loadedLocale["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) { gesture.Connect("pressed", func(n_press, x, y int) {
win := gtk.NewWindow() win := gtk.NewWindow()
win.SetDefaultSize(400, 400) win.SetDefaultSize(400, 400)
profile_box := gtk.NewBox(gtk.OrientationVertical, 0) profile_box := gtk.NewBox(gtk.OrientationVertical, 0)
nick := gtk.NewLabel(JidMustParse(u.From).Resource) nick := gtk.NewLabel(JidMustParse(u.From).Resource)
ver_text := gtk.NewLabel(loadedLocale["gettingVersion"])
ver_text.AddCSSClass("visitor")
win.SetTitle(JidMustParse(u.From).Resource) win.SetTitle(JidMustParse(u.From).Resource)
nick.AddCSSClass("author") nick.AddCSSClass("author")
nick.SetSelectable(true)
profile_box.Append(nick) profile_box.Append(nick)
profile_box.Append(gtk.NewLabel(u.From)) profile_box.Append(ver_text)
fr := gtk.NewLabel(u.From)
fr.AddCSSClass("jid")
fr.SetSelectable(true)
profile_box.Append(fr)
profile_box.Append(ver_text)
iqResp, err := stanza.NewIQ(stanza.Attrs{ iqResp, err := stanza.NewIQ(stanza.Attrs{
Type: "get", Type: "get",
@@ -129,7 +351,9 @@ func switchToTab(jid string, w *gtk.Window) {
ok := u.Get(&hats) ok := u.Get(&hats)
if ok { if ok {
for _, hat := range hats.Hats { 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 +361,61 @@ func switchToTab(jid string, w *gtk.Window) {
ok = u.Get(&mu) ok = u.Get(&mu)
if ok { if ok {
if mu.MucUserItem.JID != "" { if mu.MucUserItem.JID != "" {
profile_box.Append(gtk.NewLabel(mu.MucUserItem.JID)) ji := (gtk.NewLabel(mu.MucUserItem.JID))
ji.AddCSSClass("jid")
ji.SetSelectable(true)
profile_box.Append(ji)
} }
profile_box.Append(gtk.NewLabel("Connected with role " + mu.MucUserItem.Role)) profile_box.Append(gtk.NewLabel(loadedLocale["connectedWithRole"] + mu.MucUserItem.Role))
profile_box.Append(gtk.NewLabel("Affiliated as " + mu.MucUserItem.Affiliation)) profile_box.Append(gtk.NewLabel(loadedLocale["affiliatedAs"] + mu.MucUserItem.Affiliation))
} }
go func() {
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() { go func() {
ctx := context.TODO() ctx := context.TODO()
mychan, err := client.SendIQ(ctx, iqResp) mychan, err := client.SendIQ(ctx, iqResp)
@@ -155,9 +428,18 @@ func switchToTab(jid string, w *gtk.Window) {
version := ver.Version version := ver.Version
os := ver.OS os := ver.OS
profile_box.Append(gtk.NewLabel(name)) vr := fmt.Sprintf("%s %s %s", name, version, os)
profile_box.Append(gtk.NewLabel(version)) if name == "" && version == "" && os == "" {
profile_box.Append(gtk.NewLabel(os)) ver_text.SetText(loadedLocale["versionQueryEmpty"])
} else {
ver_text.SetText(vr)
ver_text.RemoveCSSClass("visitor")
}
} else if result.Error != nil && result.Error.Type != "" {
ver_text.SetText(loadedLocale["versionQueryError"])
ver_text.SetTooltipText(result.Error.Reason + ": " + result.Error.Text)
ver_text.RemoveCSSClass("visitor")
ver_text.AddCSSClass("error")
} }
} }
}() }()
@@ -166,7 +448,7 @@ func switchToTab(jid string, w *gtk.Window) {
mo, _ := mucmembers.Load(JidMustParse(u.From).Bare()) mo, _ := mucmembers.Load(JidMustParse(u.From).Bare())
mm := mo.(mucUnit) mm := mo.(mucUnit)
mmm := mm.Members mmm := mm.Members
mmmm, ok := mmm.Load(ocu.ID) mmmm, ok := mmm.Load(id)
if ok { if ok {
pres := mmmm.(stanza.Presence) pres := mmmm.(stanza.Presence)
@@ -176,18 +458,18 @@ func switchToTab(jid string, w *gtk.Window) {
im := getAvatar(u.From, vu.Photo) im := getAvatar(u.From, vu.Photo)
im.SetPixelSize(80) im.SetPixelSize(80)
im.AddCSSClass("author_img") im.AddCSSClass("author_img")
profile_box.Append(im) profile_box.Prepend(im)
} else { } else {
im := newImageFromPath("debug.png") im := createIdenticon(u.From)
im.SetPixelSize(80) im.SetPixelSize(80)
im.AddCSSClass("author_img") im.AddCSSClass("author_img")
profile_box.Append(im) profile_box.Prepend(im)
} }
} else { } else {
im := newImageFromPath("debug.png") im := createIdenticon(u.From)
im.SetPixelSize(80) im.SetPixelSize(80)
im.AddCSSClass("author_img") im.AddCSSClass("author_img")
profile_box.Append(im) profile_box.Prepend(im)
} }
}() }()
@@ -195,12 +477,30 @@ func switchToTab(jid string, w *gtk.Window) {
win.SetTransientFor(win) win.SetTransientFor(win)
win.Present() win.Present()
}) })
userbox.AddController(gesture) userbox.AddController(gesture)
userbox.AddController(mod_gesture)
gen.Append(userbox)
return true 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 %s", i, loadedLocale["participants"])))
gen.Prepend(headerBox)
muci := getAvatar(jid, jid)
muci.SetPixelSize(80)
gen.Prepend(muci)
muc_name := gtk.NewLabel(typed_tab.name)
muc_name.AddCSSClass("author")
gen.Prepend(muc_name)
memberList.SetChild(gen) memberList.SetChild(gen)
} else { } else {
memberList.SetChild(gtk.NewLabel(jid)) memberList.SetChild(gtk.NewLabel(jid))
@@ -209,5 +509,114 @@ func switchToTab(jid string, w *gtk.Window) {
} }
func showErrorDialog(err error) { func showErrorDialog(err error) {
fmt.Println(err.Error()) err_win := gtk.NewWindow()
err_win.SetTitle(loadedLocale["error"])
err_win.SetDefaultSize(400, 200)
err_win.SetResizable(false)
box := gtk.NewBox(gtk.OrientationVertical, 0)
err_label := gtk.NewLabel(err.Error())
err_label.SetSelectable(true)
box.Append(err_label)
close_btn := gtk.NewButtonWithLabel(loadedLocale["close"])
close_btn.ConnectClicked(func() {
err_win.SetVisible(false)
})
box.Append(close_btn)
err_win.SetChild(box)
err_win.Present()
}
func createIdenticon(word string) *gtk.Image { // This function generates an identicon
if !loadedConfig.Identicons {
i := gtk.NewImageFromPaintable(clientAssets["DefaultAvatar"])
i.AddCSSClass(loadedConfig.CVD.String() + "_CVD")
return i
}
gen, _ := identicon.New("github", 5, 3)
ii, _ := gen.Draw(word)
im := ii.Image(250)
buf := new(bytes.Buffer)
err := png.Encode(buf, im)
if err != nil {
panic(err)
}
loader := gdkpixbuf.NewPixbufLoader()
loader.Write(buf.Bytes())
loader.Close()
i := gtk.NewImageFromPaintable(gdk.NewTextureForPixbuf(loader.Pixbuf()))
i.AddCSSClass(loadedConfig.CVD.String() + "_CVD")
return i
}
func jidBuilder(en *gtk.Entry) { // This function spawns a window that allows the user to interactively build a JID
// TODO: Localise this
win := gtk.NewWindow()
win.SetTitle("Build-A-JID")
win.SetDefaultSize(400, 1)
win.SetResizable(false)
box := gtk.NewBox(gtk.OrientationVertical, 2)
header := gtk.NewLabel("Build-A-JID")
header.AddCSSClass("author")
box.Append(header)
box.Append(gtk.NewLabel("All fields except for domain are optional"))
jid_builder := gtk.NewBox(gtk.OrientationHorizontal, 2)
localPartEntry := gtk.NewEntry()
localPartEntry.SetPlaceholderText("localpart")
jid_builder.Append(localPartEntry)
at_sign := gtk.NewLabel("@")
at_sign.AddCSSClass("author")
jid_builder.Append(at_sign)
domainEntry := gtk.NewEntry()
domainEntry.SetPlaceholderText("domain")
jid_builder.Append(domainEntry)
resource_sign := gtk.NewLabel("/")
resource_sign.AddCSSClass("author")
jid_builder.Append(resource_sign)
resourceEntry := gtk.NewEntry()
resourceEntry.SetPlaceholderText("resource")
jid_builder.Append(resourceEntry)
box.Append(jid_builder)
submit := gtk.NewButtonWithLabel(loadedLocale["submit"])
submit.ConnectClicked(func() {
localPart := localPartEntry.Text()
domain := domainEntry.Text()
resource := resourceEntry.Text()
at := "@"
slash := "/"
if localPart == "" {
at = ""
}
if resource == "" {
slash = ""
}
jid := localPart + at + domain + slash + resource
en.SetText(jid)
win.SetVisible(false)
})
box.Append(submit)
win.SetChild(box)
win.SetVisible(true)
} }
+159 -57
View File
@@ -6,22 +6,24 @@ import (
"context" "context"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"github.com/diamondburned/gotk4/pkg/gtk/v4"
"github.com/diamondburned/gotk4/pkg/gdk/v4" "github.com/diamondburned/gotk4/pkg/gdk/v4"
"github.com/diamondburned/gotk4/pkg/glib/v2"
"github.com/diamondburned/gotk4/pkg/gtk/v4"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/jacoblockett/sanitizefilename"
"github.com/jasonlovesdoggo/gopen" "github.com/jasonlovesdoggo/gopen"
"gosrc.io/xmpp/stanza" "gosrc.io/xmpp/stanza"
"mellium.im/xmpp/jid" "mellium.im/xmpp/jid"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings"
) )
func generatePresenceWidget(p stanza.Packet) gtk.Widgetter { func generatePresenceWidget(p stanza.Packet) gtk.Widgetter {
b := gtk.NewBox(gtk.OrientationHorizontal, 0)
presence, ok := p.(stanza.Presence) presence, ok := p.(stanza.Presence)
if !ok { if !ok {
return gtk.NewLabel("Unsupported message.") return gtk.NewLabel(loadedLocale["unsupportedMessage"])
} }
if presence.Type == stanza.PresenceTypeUnavailable { if presence.Type == stanza.PresenceTypeUnavailable {
@@ -29,14 +31,21 @@ func generatePresenceWidget(p stanza.Packet) gtk.Widgetter {
ok := presence.Get(&mu) ok := presence.Get(&mu)
if ok { if ok {
if mu.MucUserItem.Affiliation == "outcast" { 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 + loadedLocale["bannedWidget"] + 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 { } 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 { func generateMessageWidget(p stanza.Packet) gtk.Widgetter {
@@ -49,18 +58,37 @@ func generateMessageWidget(p stanza.Packet) gtk.Widgetter {
readmarker := Marker{} readmarker := Marker{}
ok = m.Get(&readmarker) ok = m.Get(&readmarker)
if ok { 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%s", JidMustParse(m.From).Resource, loadedLocale["readWidget"])))
return b
} }
composing := stanza.StateComposing{}
ok = m.Get(&composing)
if ok {
b := gtk.NewBox(gtk.OrientationHorizontal, 0)
b.Append(gtk.NewLabel(fmt.Sprintf("%s%s", JidMustParse(m.From).Resource, loadedLocale["isTyping"])))
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{} sid := StanzaID{}
m.Get(&sid) m.Get(&sid)
mainBox := gtk.NewBox(gtk.OrientationVertical, 0) mainBox := gtk.NewBox(gtk.OrientationVertical, 10)
gesture := gtk.NewGestureClick() gesture := gtk.NewGestureClick()
gesture.SetButton(3) // Right click gesture.SetButton(3) // Right click
popover := gtk.NewPopover() popover := gtk.NewPopover()
popover.SetParent(mainBox) popover.SetParent(mainBox)
popover.SetHasArrow(false) popover.SetHasArrow(false)
@@ -68,7 +96,7 @@ func generateMessageWidget(p stanza.Packet) gtk.Widgetter {
rc_box := gtk.NewBox(gtk.OrientationVertical, 0) rc_box := gtk.NewBox(gtk.OrientationVertical, 0)
reactions := gtk.NewBox(gtk.OrientationHorizontal, 0) reactions := gtk.NewBox(gtk.OrientationHorizontal, 0)
reaction := []string{"👍", "👎", "♥️", "🤣", "😭"} reaction := []string{"👍", "👎", "♥️", "🤣", "💀"}
for _, v := range reaction { for _, v := range reaction {
like := gtk.NewButton() like := gtk.NewButton()
like.SetLabel(v) like.SetLabel(v)
@@ -88,21 +116,19 @@ func generateMessageWidget(p stanza.Packet) gtk.Widgetter {
rc_box.Append(reactions) rc_box.Append(reactions)
if m.Type == stanza.MessageTypeGroupchat { quote := gtk.NewButtonWithLabel("Quote")
moderate := gtk.NewButtonWithLabel("Moderate") // TODO: Implement proper support for moderations via extension quote.ConnectClicked(func() {
moderate.ConnectClicked(func() { lines := strings.Split(m.Body, "\n")
client.SendRaw(fmt.Sprintf(` for i, line := range lines {
<iq type='set' to='%s' id='%s'> quoteline := "> " + line
<moderate id='%s' xmlns='urn:xmpp:message-moderate:1'> lines[i] = quoteline
<retract xmlns='urn:xmpp-message-retract:1'/>
<reason>Retracted</reason>
</moderate>
</iq>
`, jid.MustParse(m.From).Bare().String(), uuid.New().String(), sid.ID))
})
rc_box.Append(moderate)
} }
newstr := strings.Join(lines, "\n") + "\n\n"
message_en.SetText(newstr)
})
rc_box.Append(quote)
popover.SetChild(rc_box) popover.SetChild(rc_box)
@@ -114,65 +140,89 @@ func generateMessageWidget(p stanza.Packet) gtk.Widgetter {
mainBox.AddController(gesture) 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{} ocu := OccupantID{}
m.Get(&ocu) m.Get(&ocu)
//id := ocu.ID
// if id == "" {
id := JidMustParse(m.From).Resource
// }
authorBox := gtk.NewBox(gtk.OrientationHorizontal, 10) authorBox := gtk.NewBox(gtk.OrientationHorizontal, 10)
contentBox := gtk.NewBox(gtk.OrientationHorizontal, 0) contentBox := gtk.NewBox(gtk.OrientationHorizontal, 0)
// im := newImageFromPath("debug.png") // im := newImageFromPath("debug.png")
// authorBox.Append(im) // authorBox.Append(im)
al := gtk.NewLabel(jid.MustParse(m.From).Resourcepart()) n := JidMustParse(m.From).Resource
if n == "" {
n = JidMustParse(m.From).Resource
}
al := gtk.NewLabel(n)
al.AddCSSClass("author") al.AddCSSClass("author")
al.SetSelectable(true)
if m.Type == stanza.MessageTypeGroupchat { if m.Type == stanza.MessageTypeGroupchat {
mo, _ := mucmembers.Load(jid.MustParse(m.From).Bare().String()) mo, _ := mucmembers.Load(JidMustParse(m.From).Bare())
mm := mo.(mucUnit) mm := mo.(mucUnit)
mmm := mm.Members mmm := mm.Members
mmmm, ok := mmm.Load(ocu.ID) mmmm, ok := mmm.Load(id)
if ok { if ok {
pres := mmmm.(stanza.Presence) pres := mmmm.(stanza.Presence)
var vu VCardUpdate var vu VCardUpdate
pres.Get(&vu) pres.Get(&vu)
im := createIdenticon(m.From)
im.SetPixelSize(40)
im.AddCSSClass("author_img")
authorBox.Append(im)
if vu.Photo != "" { if vu.Photo != "" {
im := getAvatar(m.From, vu.Photo) go func() {
im.SetPixelSize(40) new_im := getAvatar(m.From, vu.Photo)
im.AddCSSClass("author_img") glib.IdleAdd(func() {
authorBox.Append(im) new_im.SetPixelSize(40)
} else { new_im.AddCSSClass("author_img")
im := gtk.NewImageFromPaintable(clientAssets["DefaultAvatar"]) authorBox.Remove(im)
im.SetPixelSize(40) authorBox.Prepend(new_im)
im.AddCSSClass("author_img") })
authorBox.Append(im) }()
} }
} else { } else {
im := gtk.NewImageFromPaintable(clientAssets["DefaultAvatar"]) im := createIdenticon(m.From)
im.SetPixelSize(40) im.SetPixelSize(40)
im.AddCSSClass("author_img") im.AddCSSClass("author_img")
authorBox.Append(im) authorBox.Append(im)
} }
} else if m.Type == stanza.MessageTypeChat { } else if m.Type == stanza.MessageTypeChat {
al.SetText(al.Text() + " whispers") al.SetText(al.Text() + loadedLocale["whispers"])
} }
authorBox.Append(al) authorBox.Append(al)
wxdc := XDCEl{}
ok = m.Get(&wxdc)
if ok {
authorBox.Append(gtk.NewLabel("🎮"))
}
mlabel := gtk.NewLabel(m.Body) mlabel := gtk.NewLabel(m.Body)
if m.Body == "" {
mlabel.SetText(loadedLocale["noBodySet"])
mlabel.AddCSSClass("visitor")
}
mlabel.SetWrap(true) mlabel.SetWrap(true)
mlabel.SetSelectable(true) mlabel.SetSelectable(true)
mlabel.SetHAlign(gtk.AlignFill) mlabel.SetHAlign(gtk.AlignFill)
/*
mum := MucUser{}
ok = m.Get(&mum)
if ok {
mlabel.SetText(fmt.Sprintf("%s%s%s", mum.MucUserItem.JID, loadedLocale["affilChange"], mum.MucUserItem.Affiliation))
}
*/
contentBox.Append(mlabel) contentBox.Append(mlabel)
mainBox.Append(authorBox) mainBox.Append(authorBox)
@@ -195,6 +245,39 @@ func generateMessageWidget(p stanza.Packet) gtk.Widgetter {
authorBox.Append(mbtn) 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)
}
link_preview := LinkPreview{}
ok = m.Get(&link_preview)
if ok {
lp_box := gtk.NewBox(gtk.OrientationVertical, 10)
lp_box.AddCSSClass("link_preview")
lp_title := gtk.NewLabel(link_preview.Title)
lp_title.SetSelectable(true)
lp_title.SetWrap(true)
lp_title.SetHAlign(gtk.AlignFill)
lp_desc := gtk.NewLabel(link_preview.URL + "\n" + link_preview.Description)
lp_desc.SetSelectable(true)
lp_desc.SetWrap(true)
lp_desc.SetHAlign(gtk.AlignFill)
lp_box.Append(lp_title)
lp_box.Append(lp_desc)
warning := gtk.NewLabel("⚠️")
warning.SetTooltipText(loadedLocale["linkPreviewWarning"])
lp_box.Append(warning)
mainBox.Append(lp_box)
}
return mainBox return mainBox
} }
@@ -204,28 +287,39 @@ 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. 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() p, err := ensureCache()
if err != nil { if err != nil {
return gtk.NewImageFromPaintable(clientAssets["DefaultAvatar"]) return createIdenticon(j)
} }
if hash == "" { if hash == "" {
fmt.Println("Hash is nil!") return createIdenticon(j)
return gtk.NewImageFromPaintable(clientAssets["DefaultAvatar"])
} }
hash = filepath.Join(p, sanitizefilename.Sanitize(hash)) _, ok := invalidImages[hash]
if ok {
return createIdenticon(j)
}
hash = filepath.Join(p, hash)
_, err = os.ReadFile(hash) _, err = os.ReadFile(hash)
if err == nil { if err == nil {
return newImageFromPath(hash) i, err := newImageFromPath(hash)
if err != nil {
invalidImages[oghash] = true
return createIdenticon(j)
}
i.AddCSSClass(loadedConfig.CVD.String() + "_CVD")
return i
} }
iqResp, err := stanza.NewIQ(stanza.Attrs{ iqResp, err := stanza.NewIQ(stanza.Attrs{
Type: "get", Type: "get",
From: clientroot.Session.BindJid, From: clientroot.Session.BindJid,
To: j, To: j,
Id: "vc2", Id: uuid.New().String(),
Lang: "en", Lang: "en",
}) })
@@ -243,12 +337,14 @@ func getAvatar(j, hash string) *gtk.Image { // TODO: This function probably shou
result := <-mychan result := <-mychan
card, ok := result.Payload.(*VCard) card, ok := result.Payload.(*VCard)
if !ok { if !ok {
return gtk.NewImageFromPaintable(clientAssets["DefaultAvatar"]) return createIdenticon(j)
} }
base64_data := card.Photo.Binval base64_data := card.Photo.Binval
if card.Photo.Binval == "" || (card.Photo.Type == "image/svg+xml" && runtime.GOOS == "windows") { if card.Photo.Binval == "" || ((card.Photo.Type == "image/svg+xml" || card.Photo.Type == "image/webp") && (runtime.GOOS == "windows" || runtime.GOOS == "netbsd")) {
return gtk.NewImageFromPaintable(clientAssets["DefaultAvatar"]) fmt.Println("Blocking image")
invalidImages[oghash] = true
return createIdenticon(j)
} }
data, err := base64.StdEncoding.DecodeString(base64_data) data, err := base64.StdEncoding.DecodeString(base64_data)
@@ -261,5 +357,11 @@ func getAvatar(j, hash string) *gtk.Image { // TODO: This function probably shou
panic(err) panic(err)
} }
return newImageFromPath(hash) i, err := newImageFromPath(hash)
if err != nil {
invalidImages[oghash] = true
return createIdenticon(j)
}
i.AddCSSClass(loadedConfig.CVD.String() + "_CVD")
return i
} }
+21 -11
View File
@@ -1,19 +1,29 @@
package main package main
import ( import (
"os"
"bytes" "bytes"
"os"
"github.com/diamondburned/gotk4/pkg/gio/v2" "github.com/diamondburned/gotk4/pkg/gio/v2"
"github.com/diamondburned/gotk4/pkg/gtk/v4" "github.com/diamondburned/gotk4/pkg/gtk/v4"
"path/filepath" "path/filepath"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"math/rand/v2"
_ "embed" _ "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) { func dropToSignInPage(err error) {
app := gtk.NewApplication("net.sunglocto.lambda.login", gio.ApplicationFlagsNone) app := gtk.NewApplication("net.sunglocto.lambda.login", gio.ApplicationFlagsNone)
app.ConnectActivate(func() { app.ConnectActivate(func() {
@@ -25,12 +35,12 @@ func dropToSignInPage(err error) {
nickname_box := gtk.NewBox(gtk.OrientationHorizontal, 0) nickname_box := gtk.NewBox(gtk.OrientationHorizontal, 0)
insecure_box := gtk.NewBox(gtk.OrientationHorizontal, 0) insecure_box := gtk.NewBox(gtk.OrientationHorizontal, 0)
server_label := gtk.NewLabel("Server: ") server_label := gtk.NewLabel(loadedLocale["SIServerLabel"])
username_label := gtk.NewLabel("JID: ") username_label := gtk.NewLabel(loadedLocale["SIUsernameLabel"])
password_label := gtk.NewLabel("Password: ") password_label := gtk.NewLabel(loadedLocale["SIPasswordLabel"])
nickname_label := gtk.NewLabel("Nickname: ") nickname_label := gtk.NewLabel(loadedLocale["SINicknameLabel"])
insecure_label := gtk.NewLabel("Insecure: (?)") insecure_label := gtk.NewLabel(loadedLocale["SIInsecureLabel"])
insecure_label.SetTooltipText("Tick this if you need to connect without TLS, usually for connecting to Tor XMPP servers") insecure_label.SetTooltipText(loadedLocale["SIInsecureLabelTooltip"])
server_entry := gtk.NewEntry() server_entry := gtk.NewEntry()
server_entry.SetHAlign(gtk.AlignEnd) server_entry.SetHAlign(gtk.AlignEnd)
@@ -52,7 +62,6 @@ func dropToSignInPage(err error) {
insecure_check.SetHAlign(gtk.AlignEnd) insecure_check.SetHAlign(gtk.AlignEnd)
insecure_check.SetHExpand(true) insecure_check.SetHExpand(true)
server_box.Append(server_label) server_box.Append(server_label)
server_box.Append(server_entry) server_box.Append(server_entry)
@@ -74,7 +83,7 @@ func dropToSignInPage(err error) {
form_box.Append(nickname_box) form_box.Append(nickname_box)
form_box.Append(insecure_box) form_box.Append(insecure_box)
sumbit_btn := gtk.NewButtonWithLabel("Submit") sumbit_btn := gtk.NewButtonWithLabel(loadedLocale["submit"])
sumbit_btn.ConnectClicked(func() { sumbit_btn.ConnectClicked(func() {
conf := new(lambdaConfig) conf := new(lambdaConfig)
conf.Server = server_entry.Text() conf.Server = server_entry.Text()
@@ -82,6 +91,8 @@ func dropToSignInPage(err error) {
conf.Password = password_entry.Text() conf.Password = password_entry.Text()
conf.Nick = nickname_entry.Text() conf.Nick = nickname_entry.Text()
conf.Insecure = insecure_check.Active() conf.Insecure = insecure_check.Active()
conf.JoinBookmarks = true
conf.Resource = randomClientResource()
var b bytes.Buffer var b bytes.Buffer
e := toml.NewEncoder(&b) e := toml.NewEncoder(&b)
@@ -93,7 +104,6 @@ func dropToSignInPage(err error) {
} }
os.WriteFile(filepath.Join(p, "lambda.toml"), b.Bytes(), 0644) os.WriteFile(filepath.Join(p, "lambda.toml"), b.Bytes(), 0644)
window.SetVisible(false) window.SetVisible(false)
main() main()
os.Exit(0) os.Exit(0)
+25
View File
@@ -0,0 +1,25 @@
// Generic helpers
package main
import (
"sort"
"sync"
)
func rangeOrdered(m *sync.Map, fn func(k, v any) bool) {
var keys []string
m.Range(func(k, v any) bool {
keys = append(keys, k.(string))
return true
})
sort.Strings(keys)
for _, k := range keys {
v, _ := m.Load(k)
if !fn(k, v) {
break
}
}
}
+139
View File
@@ -0,0 +1,139 @@
package main
// Default language is en_GB
var loadedLocale = make(map[string]string)
var enGB = map[string]string{ // British English
// main.go
"appName": "Lambda",
"cancel": "Cancel",
"submit": "Submit",
"join": "Join",
"send": "Send",
"error": "Error",
"close": "Close",
"userRequested": "User requested",
"configResourceEmptyWarning": "Config resource is empty! Generating a random one",
"attention": "Attention",
"disconnected": "Disconnected: ",
"connecting": "Connecting...",
"milliseconds": "ms",
"KBPerSecond": "KB/s",
"connectedAs": "Connected as ",
"bindedJid": "Binded JID: ",
"usingTLS": "Using TLS: ",
"joinMUCMenu": "Join MUC",
"joinMUCJIDEntry": "MUC JID:",
"joinMUCNickEntry": "Nick:",
"joinMUCDiscoCheck": "Check MUC features before joining",
"joinMUCDiscoCheckTooltip": "If you are creating a MUC through this window then turn this off",
"joinPreviewTitle": "Joining ",
"joinPasswordRequired": "Password required",
"muc_passwordprotected_description": "This MUC is password-protected",
"muc_unsecured_description": "This MUC does not require a password",
"muc_membersonly_description": "Only members can join this MUC",
"muc_open_description": "Anyone can join this MUC",
"muc_moderated_description": "Only members can speak in this MUC",
"muc_unmoderated_description": "Anyone can speak in this MUC",
"muc_nonanonymous_description": "This MUC is non-anonymous, your JID will be visible to other users",
"muc_semianonymous_description": "This MUC is semi-anonymous, only moderators will see your full JID",
"muc_persistent_description": "This MUC is persistent, it will not be deleted when the last user leaves",
"muc_temporary_description": "This MUC is temporary, it will be deleted when the last user leaves",
"muc_public_description": "This MUC can be found in directories and search engines",
"muc_hidden_description": "This MUC is hidden and cannot be found in directories or search engines",
"urn:xmpp:mam_description": "This MUC supports archiving via MAM",
"urn:xmpp:message-moderate_description": "This MUC supports message moderation",
"discoFail": "Failed to get Disco info",
"startDMMenu": "Start DM",
"destroyMUCMenu": "Destroy MUC",
"aboutMenu": "About",
"destroyMUCWarningOne": "Are you sure? This MUC will be gone forever! (a very long time)",
"destroyMUCWarningTwo": "If you wish to continue, type 'I understand'",
"destroyMUCPassword": "I understand",
"destroyMUCActionButton": "Destroy",
"destroyMUCNotOwnerWarning": "You are not an owner of this MUC and thus will most likely not be able to delete it",
"pingBarTooltip": "Ping between you and your XMPP server\nRight-click to see graph",
"pingGraphTitle": "Server latency",
"pingGraphYAxis": "Ping (ms)",
"throughputTooltip": "Throughput of your XMPP connection in KB/s",
"messageEntryPlaceholder": "Say something, what else are you going to do here?",
// gtk-message.go
"unsupportedMessage": "Unsupported message.",
"bannedWidget": " has been banned by ",
"readWidget": " has read to this point",
"isTyping": " is typing...",
"whispers": " whispers",
"noBodySet": "No body set",
"affilChange": "'s affiliation has been changed to ",
"linkPreviewWarning": "This link preview was generated by the client sending it and may not be accurate of the actual website content",
// gtk-helpers.go
"getPastMessages": "Get past messages...",
"clickForMoreInfo": "Click for more information",
"ban": "Ban",
"kick": "Kick",
"setAffil": "Set affiliation",
"setAffilDescPartOne": "Set ",
"setAffilDescPartTwo": "'s affiliation",
"setRole": "Set role",
"setRoleDescPartOne": "Set ",
"setRoleDescPartTwo": "'s role",
"setRoleWarning": "Important: if you want this to be permanent, set their affiliation instead",
"gettingVersion": "Getting version...",
"connectedWithRole": "Connected with role ",
"affiliatedAs": "Affiliated as ",
"participants": "participant(s)",
"versionQueryEmpty": "Client responded with empty version",
"versionQueryError": "Got error trying to get version",
// gtk-signin.go
"SIServerLabel": "Server: ",
"SIUsernameLabel": "Username: ",
"SIPasswordLabel": "Password: ",
"SINicknameLabel": "Nickname: ",
"SIInsecureLabel": "Insecure: (?)",
"SIInsecureLabelTooltip": "Tick this if you need to connect without TLS, usually for connecting to Tor XMPP servers",
}
var kaGE = map[string]string{ // Georgian (Georgia)
}
var roRo = map[string]string{ // Romanian (Romania)
"appName": "Lambda",
"cancel": "Canselează",
"submit": "A preda",
"join": "Intră",
"send": "Trimite",
"error": "Eroare",
"close": "închide",
"userRequested": "Uzator cerut",
"configResourceEmptyWarning": "Resursa configurată este goala! Creiez unu aleatoriu",
"attention": "Atenție",
"disconnected": "Deconectat",
"connecting": "Conectat",
"bindedJid": "Lipit JID",
"joinMUCMenu": "Intră pe MUC",
"joinMUCJIDEntry": "MUC JID:",
"joinMUCNickEntry": "Poreclă:",
"joinMUCDiscoCheck": "Verifica detalile de MUC înainte sa intri",
"joinMUCDiscoCheckTooltip": "Dacă creiezi un MUC prin această oglindă închido",
}
var enUS = enGB // American English
var locales = map[string]map[string]string{
"en_GB": enGB,
"ka_GE": kaGE,
"en_US": enUS,
}
// TODO: Load locale according to user configuration
func init() {
loadedLocale = locales["en_GB"]
}
+856 -177
View File
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.
+47
View File
@@ -33,3 +33,50 @@
.visitor { .visitor {
color: grey; 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;
}
.RedGreen_CVD {
filter: hue-rotate(30deg) saturate(120%) contrast(110%);
}
.Blue_CVD {
filter: hue-rotate(30deg);
}
.None_CVD {
}
.link_preview {
color: white;
background-color: grey;
border-radius: 5px;
padding: 5px;
}
+40
View File
@@ -0,0 +1,40 @@
package main
import (
"bytes"
"fmt"
"github.com/srwiley/oksvg"
"github.com/srwiley/rasterx"
"image"
"image/png"
)
func SVGToPNG(svgData []byte) ([]byte, error) {
// Parse SVG
icon, err := oksvg.ReadIconStream(bytes.NewReader(svgData))
if err != nil {
return nil, fmt.Errorf("failed to parse SVG: %w", err)
}
w := int(icon.ViewBox.W)
h := int(icon.ViewBox.H)
if w == 0 || h == 0 {
w, h = 100, 100
}
// Rasterize into an RGBA image
rgba := image.NewRGBA(image.Rect(0, 0, w, h))
scanner := rasterx.NewScannerGV(w, h, rgba, rgba.Bounds())
dasher := rasterx.NewDasher(w, h, scanner)
icon.SetTarget(0, 0, float64(w), float64(h))
icon.Draw(dasher, 1.0)
// Encode to PNG bytes
var buf bytes.Buffer
if err := png.Encode(&buf, rgba); err != nil {
return nil, fmt.Errorf("failed to encode PNG: %w", err)
}
return buf.Bytes(), nil
}
+8 -1
View File
@@ -2,24 +2,31 @@ package main
import ( import (
"github.com/diamondburned/gotk4/pkg/gtk/v4" "github.com/diamondburned/gotk4/pkg/gtk/v4"
"mellium.im/xmpp/color"
"sync" "sync"
) )
type chatTab struct { type chatTab struct {
isMuc bool isMuc bool
msgs *gtk.ListBox msgs *gtk.ListBox
name string
} }
type lambdaConfig struct { type lambdaConfig struct {
Server string Server string
Username string Username string
Resource string
Password string Password string
Insecure bool Insecure bool
Nick string Nick string
JoinBookmarks bool
CVD color.CVD
Identicons bool
Debug bool
} }
type mucUnit struct { type mucUnit struct {
// key: OccupantID // key: Resource
// value: last user presence // value: last user presence
Members sync.Map Members sync.Map
} }
+1 -1
View File
@@ -1,3 +1,3 @@
package main package main
var lambda_version string = "0.1.0" var lambda_version string = "26w17a"
+17
View 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
View File
@@ -0,0 +1,22 @@
package main
import (
"encoding/xml"
"gosrc.io/xmpp/stanza"
)
// Implementation of XEP-0402: PEP Native Bookmarks
// https://xmpp.org/extensions/xep-0402.html
type Bookmark struct {
XMLName xml.Name `xml:"urn:xmpp:bookmarks:1 conference"`
Name string `xml:"name,attr,omitempty"`
Autojoin bool `xml:"autojoin,attr,omitempty"`
Nick string `xml:"nick,omitempty"`
Password string `xml:"password,omitempty"`
Extensions []any `xml:"extensions,omitempty"`
}
func init() {
stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{Space: "urn:xmpp:bookmarks:1", Local: "conference"}, Bookmark{})
}
+26
View 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{})
}
+65 -6
View File
@@ -1,7 +1,6 @@
package main package main
import ( import (
"fmt"
"gosrc.io/xmpp" "gosrc.io/xmpp"
"gosrc.io/xmpp/stanza" "gosrc.io/xmpp/stanza"
) )
@@ -9,7 +8,7 @@ import (
// This file has small functions that can be used to do XMPP stuff without writing tons of boilerplate // 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 // 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{ m := stanza.Message{
Attrs: stanza.Attrs{ Attrs: stanza.Attrs{
To: sendTo, To: sendTo,
@@ -18,6 +17,7 @@ func sendMessage(c xmpp.Sender, sendTo string, msgType stanza.StanzaType, body s
Body: body, Body: body,
Subject: subject, Subject: subject,
Thread: thread, Thread: thread,
Extensions: exts,
} }
err := c.Send(m) err := c.Send(m)
if err != nil { if err != nil {
@@ -27,10 +27,11 @@ func sendMessage(c xmpp.Sender, sendTo string, msgType stanza.StanzaType, body s
} }
// Joins a MUC // Joins a MUC
func joinMuc(c xmpp.Sender, jid string, muc string, nick string) error { func joinMuc(c xmpp.Sender, jid string, muc string, nick string, password string) error {
var joinPresence stanza.Presence
addr := muc + "/" + nick addr := muc + "/" + nick
fmt.Println(addr) if password == "" {
joinPresence := stanza.Presence{ joinPresence = stanza.Presence{
Attrs: stanza.Attrs{ Attrs: stanza.Attrs{
From: jid, From: jid,
To: addr, To: addr,
@@ -39,6 +40,64 @@ func joinMuc(c xmpp.Sender, jid string, muc string, nick string) error {
&stanza.MucPresence{}, &stanza.MucPresence{},
}, },
} }
} else {
joinPresence = stanza.Presence{
Attrs: stanza.Attrs{
From: jid,
To: addr,
},
Extensions: []stanza.PresExtension{
&stanza.MucPresence{
Password: password,
},
},
}
}
err := client.Send(joinPresence)
if err != nil {
return err
}
return nil
}
func joinMucWithoutHistory(c xmpp.Sender, jid string, muc string, nick string, password string) error {
var joinPresence stanza.Presence
addr := muc + "/" + nick
if password == "" {
joinPresence = stanza.Presence{
Attrs: stanza.Attrs{
From: jid,
To: addr,
},
Extensions: []stanza.PresExtension{
&stanza.MucPresence{
History: stanza.History{
MaxChars: stanza.NewNullableInt(0),
MaxStanzas: stanza.NewNullableInt(0),
Seconds: stanza.NewNullableInt(0),
},
},
},
}
} else {
joinPresence = stanza.Presence{
Attrs: stanza.Attrs{
From: jid,
To: addr,
},
Extensions: []stanza.PresExtension{
&stanza.MucPresence{
Password: password,
History: stanza.History{
MaxChars: stanza.NewNullableInt(0),
MaxStanzas: stanza.NewNullableInt(0),
Seconds: stanza.NewNullableInt(0),
},
},
},
}
}
err := client.Send(joinPresence) err := client.Send(joinPresence)
if err != nil { if err != nil {
@@ -50,7 +109,7 @@ func joinMuc(c xmpp.Sender, jid string, muc string, nick string) error {
// jid MustParse but using gosrc's instead of mellium // jid MustParse but using gosrc's instead of mellium
// This function will panic if its an invalid JID // 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) j, err := stanza.NewJid(s)
if err != nil { if err != nil {
panic(err) panic(err)
+20
View File
@@ -0,0 +1,20 @@
package main
import (
"encoding/xml"
"gosrc.io/xmpp/stanza"
)
type LinkPreview struct {
stanza.MsgExtension
XMLName xml.Name `xml:"http://www.w3.org/1999/02/22-rdf-syntax-ns# Description"`
About string `xml:"https://ogp.me/ns#,attr"`
Title string `xml:"https://ogp.me/ns# title"`
Description string `xml:"https://ogp.me/ns# description"`
Image string `xml:"https://ogp.me/ns# image"`
URL string `xml:"https://ogp.me/ns# url"`
}
func init() {
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{Space: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", Local: "Description"}, LinkPreview{})
}
+28
View File
@@ -0,0 +1,28 @@
package main
import (
"encoding/xml"
"gosrc.io/xmpp/stanza"
)
// Implementation of XEP-0513: Explicit Mentions
// https://xmpp.org/extensions/xep-0513.html
type NoPing struct{}
type Active struct{}
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"`
Mentions string `xml:"mentions,attr"`
OccupantID string `xml:"occupantid,attr,omitempty"`
NoPing NoPing `xml:"noping,omitempty"`
Active Active `xml:"active,omitempty"`
}
func init() {
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{Space: "urn:xmpp:mentions:0", Local: "mention"}, Mention{})
}
+8
View File
@@ -10,6 +10,7 @@ import (
type MucUser struct { type MucUser struct {
stanza.PresExtension stanza.PresExtension
stanza.MsgExtension
XMLName xml.Name `xml:"http://jabber.org/protocol/muc#user x"` XMLName xml.Name `xml:"http://jabber.org/protocol/muc#user x"`
MucUserItem MucUserItem `xml:"item,omitempty"` MucUserItem MucUserItem `xml:"item,omitempty"`
} }
@@ -20,8 +21,15 @@ type MucUserItem struct {
Role string `xml:"role,attr,omitempty"` // TODO: Use enum Role string `xml:"role,attr,omitempty"` // TODO: Use enum
JID string `xml:"jid,attr,omitempty"` JID string `xml:"jid,attr,omitempty"`
Reason string `xml:"reason,omitempty"` Reason string `xml:"reason,omitempty"`
Actor Actor `xml:"actor,omitempty"`
}
type Actor struct {
JID string `xml:"jid,attr"`
Nick string `xml:"nick,attr"`
} }
func init() { func init() {
stanza.TypeRegistry.MapExtension(stanza.PKTPresence, xml.Name{Space: "http://jabber.org/protocol/muc#user", Local: "x"}, MucUser{}) 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{})
} }
-20
View File
@@ -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{})
}
+14
View File
@@ -10,7 +10,21 @@ import (
type VCard struct { type VCard struct {
XMLName xml.Name `xml:"vcard-temp vCard"` XMLName xml.Name `xml:"vcard-temp vCard"`
FirstName string `xml:"FN"`
LastName string `xml:"N>FAMILY"`
GivenName string `xml:"N>GIVEN"`
MiddleName string `xml:"N>MIDDLE"`
Nickname string `xml:"NICKNAME"`
URI string `xml:"URL"`
Birthday string `xml:"BDAY"`
OrgName string `xml:"ORG>ORGNAME"`
OrgUnit string `xml:"ORG>ORGUNIT"`
Title string `xml:"TITLE"`
Role string `xml:"ROLE"`
Description string `xml:"DESC"`
Jid string `xml:"JABBERID"`
Photo Photo `xml:"PHOTO"` Photo Photo `xml:"PHOTO"`
Email string `xml:"EMAIL>USERID"`
ResultSet *stanza.ResultSet `xml:"set,omitempty"` ResultSet *stanza.ResultSet `xml:"set,omitempty"`
} }
+21
View 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{})
}