Compare commits
54 Commits
f4eb50c741
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 82aa2abfbd | |||
| 58c7165ce5 | |||
| 5bcf0eda0b | |||
| 0bfb140dc7 | |||
| 7b63799f0b | |||
| a97c42323c | |||
| fc0ed5ac2c | |||
| 69994d9856 | |||
| 843687ff2b | |||
| 7416aa37e0 | |||
| ce83000e0c | |||
| 359e8ed63e | |||
| 3b9df94cf6 | |||
| 634f451595 | |||
| 00d7945f70 | |||
| 60d6a287e5 | |||
| d5981b7475 | |||
| 523722bae6 | |||
| 71e6a58fbd | |||
| a7e90e4ae5 | |||
| 654ab8b618 | |||
| 7a201808e3 | |||
| 77e4e444d4 | |||
| bf1685a382 | |||
| 0ac43946b1 | |||
| 15ff7a20db | |||
| 1dd3f09fed | |||
| 87bdbc440a | |||
| 62b5a9db72 | |||
| 39156af48a | |||
| 3f40d3da29 | |||
| a7a49f7441 | |||
| e026e777f6 | |||
| 09a809c102 | |||
| 29ef37e237 | |||
| 713cb24508 | |||
| 589101c292 | |||
| f807565cb2 | |||
| 6626d35920 | |||
| 5c76729a6b | |||
| c260b8b231 | |||
| 971147dcb8 | |||
| 777df725b6 | |||
| 6cb8771994 | |||
| ac013e7969 | |||
| 067a74e157 | |||
| 1dcd55d5ff | |||
| e87369912d | |||
| 63bb323ac3 | |||
| e7054741a0 | |||
| 70d51aef47 | |||
| 519e7bcf25 | |||
| 4d69d95c88 | |||
| c0e330ea22 |
@@ -0,0 +1,15 @@
|
||||
# Contributing
|
||||
|
||||
As you might have gathered, this Gitea instance does not allow registrations. If you want to have an account on this instance in order to contribute to this project, you must contact me for an account. I am available on:
|
||||
|
||||
- XMPP: `x@sunglocto.net`
|
||||
- Matrix: `@x:sunglocto.net`
|
||||
- E-Mail: sunglocto <img width=16px height=16px src="https://sunglocto.net/icons/email.gif"/> protonmail.com
|
||||
|
||||
For actually submitting your patches, you can just fork this repo and submit a pull request. I currently have no contribution policy that you have to follow however if you just use common sense you should be good.
|
||||
|
||||
## AI contributions
|
||||
|
||||
Obviously vibe-coded or fully AI-generated contributions will be immediately rejected. This is in order to ensure the stability of the project and decrease the chance of significant bugs or vulnerabilities.
|
||||
|
||||
AI contributions that you have reviewed, edited and **ensured that they compile, run and work successfully** are acceptable. AI is a tool, and like any other tools, can be used for good if used in moderation.
|
||||
@@ -1,12 +1,7 @@
|
||||
# lambda
|
||||
|
||||
In order to run this program you must have a file named `lambda.toml` that specifies your config.
|
||||
Here is an example:
|
||||
an XMPP client
|
||||
|
||||
```toml
|
||||
Server = "example.com:5222"
|
||||
Username = "user@example.com"
|
||||
Password = "123456789"
|
||||
Insecure = false
|
||||
Nick = "User"
|
||||
```
|
||||
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)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# TODO
|
||||
- XEP-0393: Message Styling 0%
|
||||
- XEP-0402: PEP Native Bookmarks 0%
|
||||
- XEP-0066: Out of Band Data partial
|
||||
- XEP-0461: Message Replies partial
|
||||
- XEP-0402: PEP Native Bookmarks 50% (often lags out client if enabled)
|
||||
- XEP-0461: Message Replies 0%
|
||||
- XEP-0444: Message Reactions partial
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 600 B |
|
After Width: | Height: | Size: 762 B |
|
After Width: | Height: | Size: 607 B |
|
After Width: | Height: | Size: 671 B |
|
After Width: | Height: | Size: 656 B |
|
After Width: | Height: | Size: 456 B |
|
After Width: | Height: | Size: 641 B |
|
After Width: | Height: | Size: 541 B |
|
After Width: | Height: | Size: 434 B |
@@ -0,0 +1 @@
|
||||
All client assets are owned by their respective owners
|
||||
|
After Width: | Height: | Size: 413 B |
|
After Width: | Height: | Size: 721 B |
|
After Width: | Height: | Size: 796 B |
|
After Width: | Height: | Size: 693 B |
|
After Width: | Height: | Size: 643 B |
|
After Width: | Height: | Size: 753 B |
|
After Width: | Height: | Size: 744 B |
|
After Width: | Height: | Size: 264 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 778 B |
|
After Width: | Height: | Size: 730 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 747 B |
|
After Width: | Height: | Size: 597 B |
|
After Width: | Height: | Size: 793 B |
|
After Width: | Height: | Size: 593 B |
|
After Width: | Height: | Size: 935 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 970 B |
|
After Width: | Height: | Size: 508 B |
|
After Width: | Height: | Size: 612 B |
|
After Width: | Height: | Size: 806 B |
|
After Width: | Height: | Size: 622 B |
|
After Width: | Height: | Size: 924 B |
|
After Width: | Height: | Size: 882 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 903 B |
|
After Width: | Height: | Size: 591 B |
|
After Width: | Height: | Size: 460 B |
|
After Width: | Height: | Size: 538 B |
|
After Width: | Height: | Size: 584 B |
|
After Width: | Height: | Size: 794 B |
|
After Width: | Height: | Size: 751 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 722 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 910 B |
|
After Width: | Height: | Size: 362 B |
|
After Width: | Height: | Size: 923 B |
@@ -13,10 +13,15 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// 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) {
|
||||
cachePath := configdir.LocalCache("lambda-im")
|
||||
@@ -28,49 +33,57 @@ func ensureCache() (string, error) {
|
||||
return cachePath, nil
|
||||
}
|
||||
|
||||
func getTexture(path string) gdk.Paintabler {
|
||||
if tex, exists := textureCache[path]; exists {
|
||||
return tex
|
||||
func getTexture(path string) (gdk.Paintabler, error) {
|
||||
tex, exists := textureCache.Load(path)
|
||||
if exists {
|
||||
return tex.(gdk.Paintabler), nil
|
||||
}
|
||||
|
||||
tex, err := gdk.NewTextureFromFilename(path) // load once
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
textureCache[path] = tex
|
||||
return tex
|
||||
textureCache.Store(path, tex)
|
||||
return tex.(gdk.Paintabler), nil
|
||||
}
|
||||
|
||||
func newPictureFromPath(path string) *gtk.Picture {
|
||||
tex := getTexture(path)
|
||||
func newPictureFromPath(path string) (*gtk.Picture, error) {
|
||||
tex, err := getTexture(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
img := gtk.NewPictureForPaintable(tex)
|
||||
return img
|
||||
return img, nil
|
||||
}
|
||||
|
||||
func newImageFromPath(path string) *gtk.Image {
|
||||
tex := getTexture(path)
|
||||
func newImageFromPath(path string) (*gtk.Image, error) {
|
||||
tex, err := getTexture(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
img := gtk.NewImageFromPaintable(tex)
|
||||
return img
|
||||
return img, nil
|
||||
}
|
||||
|
||||
func newPictureFromWeb(url string) *gtk.Picture {
|
||||
func newPictureFromWeb(url string) (*gtk.Picture, error) {
|
||||
pa, _ := ensureCache()
|
||||
// step 1: get a sha256 sum of the URL
|
||||
sum := fmt.Sprintf("%x", sha256.Sum256([]byte(url)))
|
||||
|
||||
p, ok := textureCache[sum]
|
||||
p, ok := textureCache.Load(sum)
|
||||
if ok {
|
||||
return gtk.NewPictureForPaintable(p)
|
||||
return gtk.NewPictureForPaintable(p.(gdk.Paintabler)), nil
|
||||
}
|
||||
|
||||
// step 2: download it
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fullpath := filepath.Join(pa, sum)
|
||||
@@ -78,31 +91,31 @@ func newPictureFromWeb(url string) *gtk.Picture {
|
||||
// step 3: save it
|
||||
err = os.WriteFile(fullpath, b, 0644)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newPictureFromPath(fullpath)
|
||||
}
|
||||
|
||||
func newImageFromWeb(url string) *gtk.Image {
|
||||
func newImageFromWeb(url string) (*gtk.Image, error) {
|
||||
pa, _ := ensureCache()
|
||||
// step 1: get a sha256 sum of the URL
|
||||
sum := fmt.Sprintf("%x", sha256.Sum256([]byte(url)))
|
||||
|
||||
p, ok := textureCache[sum]
|
||||
p, ok := textureCache.Load(sum)
|
||||
if ok {
|
||||
return gtk.NewImageFromPaintable(p)
|
||||
return gtk.NewImageFromPaintable(p.(gdk.Paintabler)), nil
|
||||
}
|
||||
|
||||
// step 2: download it
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fullpath := filepath.Join(pa, sum)
|
||||
@@ -110,7 +123,7 @@ func newImageFromWeb(url string) *gtk.Image {
|
||||
// step 3: save it
|
||||
err = os.WriteFile(fullpath, b, 0644)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newImageFromPath(fullpath)
|
||||
|
||||
|
After Width: | Height: | Size: 5.5 KiB |
@@ -4,23 +4,45 @@ go 1.25.5
|
||||
|
||||
require (
|
||||
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/gen2brain/beeep v0.11.2
|
||||
github.com/go-analyze/charts v0.5.24
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/jacoblockett/sanitizefilename v1.0.1
|
||||
github.com/jasonlovesdoggo/gopen v0.0.0-20250130105607-39c98c645030
|
||||
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f
|
||||
github.com/kr/pretty v0.1.0
|
||||
github.com/kr/pretty v0.2.0
|
||||
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
|
||||
mellium.im/xmpp v0.22.0
|
||||
)
|
||||
|
||||
require (
|
||||
git.sr.ht/~jackmordaunt/go-toast v1.1.2 // indirect
|
||||
github.com/KarpelesLab/weak v0.1.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/esiqveland/notify v0.13.3 // indirect
|
||||
github.com/go-analyze/bulk v0.1.3 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/jackmordaunt/icns/v3 v3.0.1 // indirect
|
||||
github.com/kr/text v0.1.0 // indirect
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||
github.com/sergeymakinen/go-bmp v1.0.0 // indirect
|
||||
github.com/sergeymakinen/go-ico v1.0.0-beta.0 // indirect
|
||||
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 // indirect
|
||||
golang.org/x/net v0.29.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
golang.org/x/image v0.36.0 // indirect
|
||||
golang.org/x/sync v0.19.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
|
||||
mellium.im/reader v0.1.0 // indirect
|
||||
mellium.im/xmlstream v0.15.4 // indirect
|
||||
|
||||
@@ -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/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/KarpelesLab/weak v0.1.1 h1:fNnlPo3aypS9tBzoEQluY13XyUfd/eWaSE/vMvo9s4g=
|
||||
github.com/KarpelesLab/weak v0.1.1/go.mod h1:pzXsWs5f2bf+fpgHayTlBE1qJpO3MpJKo5sRaLu1XNw=
|
||||
github.com/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-20190621002710-8cbd498dd7a0/go.mod h1:S8mB5wY3vV+vRIzf39xDXsw3XKYewW9X6rW2aEmkrSw=
|
||||
github.com/chromedp/cdproto v0.0.0-20190812224334-39ef923dcb8d/go.mod h1:0YChpVzuLJC5CPr+x3xkHN6Z8KOSXjNbL7qV8Wc4GW0=
|
||||
github.com/chromedp/cdproto v0.0.0-20190926234355-1b4886c6fad6/go.mod h1:0YChpVzuLJC5CPr+x3xkHN6Z8KOSXjNbL7qV8Wc4GW0=
|
||||
github.com/chromedp/chromedp v0.3.1-0.20190619195644-fd957a4d2901/go.mod h1:mJdvfrVn594N9tfiPecUidF6W5jPRKHymqHfzbobPsM=
|
||||
github.com/chromedp/chromedp v0.4.0/go.mod h1:DC3QUn4mJ24dwjcaGQLoZrhm4X/uPHZ6spDbS2uFhm4=
|
||||
github.com/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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/diamondburned/gotk4/pkg v0.3.1 h1:uhkXSUPUsCyz3yujdvl7DSN8jiLS2BgNTQE95hk6ygg=
|
||||
github.com/diamondburned/gotk4/pkg v0.3.1/go.mod h1:DqeOW+MxSZFg9OO+esk4JgQk0TiUJJUBfMltKhG+ub4=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/esiqveland/notify v0.13.3 h1:QCMw6o1n+6rl+oLUfg8P1IIDSFsDEb2WlXvVvIJbI/o=
|
||||
github.com/esiqveland/notify v0.13.3/go.mod h1:hesw/IRYTO0x99u1JPweAl4+5mwXJibQVUcP0Iu5ORE=
|
||||
github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gen2brain/beeep v0.11.2 h1:+KfiKQBbQCuhfJFPANZuJ+oxsSKAYNe88hIpJuyKWDA=
|
||||
github.com/gen2brain/beeep v0.11.2/go.mod h1:jQVvuwnLuwOcdctHn/uyh8horSBNJ8uGb9Cn2W4tvoc=
|
||||
github.com/go-analyze/bulk v0.1.3 h1:pzRdBqzHDAT9PyROt0SlWE0YqPtdmTcEpIJY0C3vF0c=
|
||||
github.com/go-analyze/bulk v0.1.3/go.mod h1:afon/KtFJYnekIyN20H/+XUvcLFjE8sKR1CfpqfClgM=
|
||||
github.com/go-analyze/charts v0.5.24 h1:HamvYWEgbANbLb/P6uijygtG9qeSYev34ki1RelA1Lw=
|
||||
github.com/go-analyze/charts v0.5.24/go.mod h1:s1YvQhjiSwtLx1f2dOKfiV9x2TT49nVSL6v2rlRpTbY=
|
||||
github.com/go-interpreter/wagon v0.5.1-0.20190713202023-55a163980b6c/go.mod h1:5+b/MBYkclRZngKF5s6qrgWxSLgE9F5dFdO1hAueZLc=
|
||||
github.com/go-interpreter/wagon v0.6.0/go.mod h1:5+b/MBYkclRZngKF5s6qrgWxSLgE9F5dFdO1hAueZLc=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@@ -33,6 +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/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jackmordaunt/icns/v3 v3.0.1 h1:xxot6aNuGrU+lNgxz5I5H0qSeCjNKp8uTXB1j8D4S3o=
|
||||
github.com/jackmordaunt/icns/v3 v3.0.1/go.mod h1:5sHL59nqTd2ynTnowxB/MDQFhKNqkK8X687uKNygaSQ=
|
||||
github.com/jacoblockett/sanitizefilename v1.0.1 h1:HhPMoPp05U5aKjhht+d7lsqhyF4trKSBme0FE6S/1C4=
|
||||
github.com/jacoblockett/sanitizefilename v1.0.1/go.mod h1:/cQHSz2fHeR1iDKpHTSW/MIyONa+Uqj0eszbvPuIxNs=
|
||||
github.com/jasonlovesdoggo/gopen v0.0.0-20250130105607-39c98c645030 h1:NFCJG3BerP/5ZLXwu08x9xDs+9p7AYFMeo5IXjGANxw=
|
||||
@@ -43,8 +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/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
@@ -58,21 +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.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
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.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/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.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/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.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af h1:6yITBqGTE2lEeTPG04SN9W+iWHCRyHqlVYILiSXziwk=
|
||||
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af/go.mod h1:4F09kP5F+am0jAwlQLddpoMDM+iewkxxt6nxUQ5nq5o=
|
||||
github.com/twitchyliquid64/golang-asm v0.0.0-20190126203739-365674df15fc/go.mod h1:NoCfSFWosfqMqmmD7hApkirIK9ozpHjxRnRxs1l413A=
|
||||
go.coder.com/go-tools v0.0.0-20190317003359-0c6a35b74a16/go.mod h1:iKV5yK9t+J5nG9O3uF6KYdPEz3dyfMyB15MN1rbQ8Qw=
|
||||
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
|
||||
@@ -85,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.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
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/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||
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-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
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-20190215142949-d0b11bdaac8a/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-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190927073244-c990c680b611/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
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/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
|
||||
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
|
||||
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||
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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
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/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gosrc.io/xmpp v0.5.1 h1:Rgrm5s2rt+npGggJH3HakQxQXR8ZZz3+QRzakRQqaq4=
|
||||
gosrc.io/xmpp v0.5.1/go.mod h1:L3NFMqYOxyLz3JGmgFyWf7r9htE91zVGiK40oW4RwdY=
|
||||
gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"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/gtk/v4"
|
||||
"github.com/diamondburned/gotk4/pkg/pango"
|
||||
"github.com/rrivera/identicon"
|
||||
"gosrc.io/xmpp/stanza"
|
||||
Jid "mellium.im/xmpp/jid"
|
||||
"image"
|
||||
"image/png"
|
||||
xmpp_color "mellium.im/xmpp/color"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func scrollToBottomAfterUpdate(scrolledWindow *gtk.ScrolledWindow) {
|
||||
@@ -17,19 +27,27 @@ func scrollToBottomAfterUpdate(scrolledWindow *gtk.ScrolledWindow) {
|
||||
})
|
||||
}
|
||||
|
||||
func createTab(jid string, isMuc bool) {
|
||||
fmt.Println("Creating tab", jid, "isMuc:", isMuc)
|
||||
func createTab(jid string, isMuc bool, name string) bool {
|
||||
if name == "" {
|
||||
name = jid
|
||||
}
|
||||
_, ok := tabs.Load(jid)
|
||||
if !ok {
|
||||
_, uok := userdevices.Load(jid)
|
||||
_, mok := mucmembers.Load(jid)
|
||||
if !ok && !uok && !mok {
|
||||
newTab := new(chatTab)
|
||||
newTab.isMuc = isMuc
|
||||
newTab.msgs = gtk.NewListBox()
|
||||
newTab.msgs.SetVExpand(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)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func switchToTab(jid string, w *gtk.Window) {
|
||||
@@ -40,38 +58,279 @@ func switchToTab(jid string, w *gtk.Window) {
|
||||
}
|
||||
|
||||
typed_tab := tab.(*chatTab)
|
||||
|
||||
scroller.SetChild(typed_tab.msgs)
|
||||
typingStatus.SetText("")
|
||||
if typed_tab.isMuc {
|
||||
m, _ := mucmembers.Load(jid)
|
||||
ma := m.(mucUnit)
|
||||
m, ok := mucmembers.Load(jid)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ma, ok := m.(mucUnit)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
mm := ma.Members
|
||||
gen := gtk.NewBox(gtk.OrientationVertical, 0)
|
||||
gen := gtk.NewBox(gtk.OrientationVertical, 10)
|
||||
|
||||
mm.Range(func(k, v any) bool {
|
||||
userbox := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||||
i := 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 ocu OccupantID
|
||||
u.Get(&mu)
|
||||
u.Get(&ocu)
|
||||
|
||||
nick_label := gtk.NewLabel(Jid.MustParse(u.From).Resourcepart())
|
||||
if mu.MucUserItem.Role == "moderator" {
|
||||
gen.Prepend(userbox)
|
||||
} else {
|
||||
gen.Append(userbox)
|
||||
}
|
||||
|
||||
//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)
|
||||
if mu.MucUserItem.Role == "visitor" {
|
||||
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"]))
|
||||
userbox.Append(nick_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.SetTooltipText(mu.MucUserItem.Affiliation)
|
||||
|
||||
medal.SetHAlign(gtk.AlignEnd)
|
||||
medal.SetHExpand(true)
|
||||
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.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) {
|
||||
win := gtk.NewWindow()
|
||||
win.SetDefaultSize(400, 400)
|
||||
profile_box := gtk.NewBox(gtk.OrientationVertical, 0)
|
||||
nick := gtk.NewLabel(Jid.MustParse(u.From).Resourcepart())
|
||||
nick := gtk.NewLabel(JidMustParse(u.From).Resource)
|
||||
ver_text := gtk.NewLabel(loadedLocale["gettingVersion"])
|
||||
ver_text.AddCSSClass("visitor")
|
||||
|
||||
win.SetTitle(JidMustParse(u.From).Resource)
|
||||
nick.AddCSSClass("author")
|
||||
nick.SetSelectable(true)
|
||||
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{
|
||||
Type: "get",
|
||||
@@ -92,10 +351,71 @@ func switchToTab(jid string, w *gtk.Window) {
|
||||
ok := u.Get(&hats)
|
||||
if ok {
|
||||
for _, hat := range hats.Hats {
|
||||
profile_box.Append(gtk.NewLabel(hat.Title))
|
||||
l := gtk.NewLabel(hat.Title)
|
||||
l.AddCSSClass("hat")
|
||||
profile_box.Append(l)
|
||||
}
|
||||
}
|
||||
|
||||
var mu MucUser
|
||||
ok = u.Get(&mu)
|
||||
if ok {
|
||||
if mu.MucUserItem.JID != "" {
|
||||
ji := (gtk.NewLabel(mu.MucUserItem.JID))
|
||||
ji.AddCSSClass("jid")
|
||||
ji.SetSelectable(true)
|
||||
profile_box.Append(ji)
|
||||
}
|
||||
profile_box.Append(gtk.NewLabel(loadedLocale["connectedWithRole"] + mu.MucUserItem.Role))
|
||||
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() {
|
||||
ctx := context.TODO()
|
||||
mychan, err := client.SendIQ(ctx, iqResp)
|
||||
@@ -108,18 +428,27 @@ func switchToTab(jid string, w *gtk.Window) {
|
||||
version := ver.Version
|
||||
os := ver.OS
|
||||
|
||||
profile_box.Append(gtk.NewLabel(name))
|
||||
profile_box.Append(gtk.NewLabel(version))
|
||||
profile_box.Append(gtk.NewLabel(os))
|
||||
vr := fmt.Sprintf("%s %s %s", name, version, os)
|
||||
if name == "" && version == "" && os == "" {
|
||||
ver_text.SetText(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")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
mo, _ := mucmembers.Load(Jid.MustParse(u.From).Bare().String())
|
||||
mo, _ := mucmembers.Load(JidMustParse(u.From).Bare())
|
||||
mm := mo.(mucUnit)
|
||||
mmm := mm.Members
|
||||
mmmm, ok := mmm.Load(ocu.ID)
|
||||
mmmm, ok := mmm.Load(id)
|
||||
if ok {
|
||||
pres := mmmm.(stanza.Presence)
|
||||
|
||||
@@ -129,18 +458,18 @@ func switchToTab(jid string, w *gtk.Window) {
|
||||
im := getAvatar(u.From, vu.Photo)
|
||||
im.SetPixelSize(80)
|
||||
im.AddCSSClass("author_img")
|
||||
profile_box.Append(im)
|
||||
profile_box.Prepend(im)
|
||||
} else {
|
||||
im := newImageFromPath("debug.png")
|
||||
im := createIdenticon(u.From)
|
||||
im.SetPixelSize(80)
|
||||
im.AddCSSClass("author_img")
|
||||
profile_box.Append(im)
|
||||
profile_box.Prepend(im)
|
||||
}
|
||||
} else {
|
||||
im := newImageFromPath("debug.png")
|
||||
im := createIdenticon(u.From)
|
||||
im.SetPixelSize(80)
|
||||
im.AddCSSClass("author_img")
|
||||
profile_box.Append(im)
|
||||
profile_box.Prepend(im)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -148,12 +477,30 @@ func switchToTab(jid string, w *gtk.Window) {
|
||||
win.SetTransientFor(win)
|
||||
win.Present()
|
||||
})
|
||||
|
||||
userbox.AddController(gesture)
|
||||
userbox.AddController(mod_gesture)
|
||||
|
||||
gen.Append(userbox)
|
||||
return true
|
||||
})
|
||||
}))
|
||||
|
||||
headerBox := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||||
if i >= 500 {
|
||||
headerBox.Append(gtk.NewImageFromPaintable(clientAssets["world"]))
|
||||
} else if i >= 50 {
|
||||
headerBox.Append(gtk.NewImageFromPaintable(clientAssets["large_group"]))
|
||||
} else {
|
||||
headerBox.Append(gtk.NewImageFromPaintable(clientAssets["group"]))
|
||||
}
|
||||
headerBox.Append(gtk.NewLabel(fmt.Sprintf("%d %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)
|
||||
} else {
|
||||
memberList.SetChild(gtk.NewLabel(jid))
|
||||
@@ -162,5 +509,114 @@ func switchToTab(jid string, w *gtk.Window) {
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -6,21 +6,24 @@ import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"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/jacoblockett/sanitizefilename"
|
||||
"github.com/jasonlovesdoggo/gopen"
|
||||
"gosrc.io/xmpp/stanza"
|
||||
"mellium.im/xmpp/jid"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func generatePresenceWidget(p stanza.Packet) gtk.Widgetter {
|
||||
b := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||||
presence, ok := p.(stanza.Presence)
|
||||
if !ok {
|
||||
return gtk.NewLabel("Unsupported message.")
|
||||
return gtk.NewLabel(loadedLocale["unsupportedMessage"])
|
||||
}
|
||||
|
||||
if presence.Type == stanza.PresenceTypeUnavailable {
|
||||
@@ -28,14 +31,21 @@ func generatePresenceWidget(p stanza.Packet) gtk.Widgetter {
|
||||
ok := presence.Get(&mu)
|
||||
if ok {
|
||||
if mu.MucUserItem.Affiliation == "outcast" {
|
||||
return gtk.NewLabel(jid.MustParse(presence.From).Resourcepart() + " has been banned!")
|
||||
b.Append(gtk.NewImageFromPaintable(clientAssets["outcast"]))
|
||||
b.Append(gtk.NewLabel(JidMustParse(presence.From).Resource + loadedLocale["bannedWidget"] + mu.MucUserItem.Actor.Nick + "!"))
|
||||
return b
|
||||
}
|
||||
}
|
||||
|
||||
return gtk.NewLabel(jid.MustParse(presence.From).Resourcepart() + " left the room")
|
||||
b.Append(gtk.NewImageFromPaintable(clientAssets["door_out"]))
|
||||
b.Append(gtk.NewLabel(JidMustParse(presence.From).Resource))
|
||||
} else {
|
||||
return gtk.NewLabel(jid.MustParse(presence.From).Resourcepart() + " joined the room")
|
||||
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 {
|
||||
@@ -43,24 +53,56 @@ func generateMessageWidget(p stanza.Packet) gtk.Widgetter {
|
||||
if !ok {
|
||||
return gtk.NewLabel("Unsupported message.")
|
||||
}
|
||||
fmt.Println(m.Body)
|
||||
|
||||
readmarker := Marker{}
|
||||
ok = m.Get(&readmarker)
|
||||
if ok {
|
||||
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{}
|
||||
m.Get(&sid)
|
||||
|
||||
mainBox := gtk.NewBox(gtk.OrientationVertical, 0)
|
||||
mainBox := gtk.NewBox(gtk.OrientationVertical, 10)
|
||||
gesture := gtk.NewGestureClick()
|
||||
gesture.SetButton(3) // Right click
|
||||
|
||||
vis := false
|
||||
reactions := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||||
reactions.SetVisible(false)
|
||||
popover := gtk.NewPopover()
|
||||
popover.SetParent(mainBox)
|
||||
popover.SetHasArrow(false)
|
||||
|
||||
reaction := []string{"👍", "👎", "♥️", "🤣", "😭"}
|
||||
rc_box := gtk.NewBox(gtk.OrientationVertical, 0)
|
||||
|
||||
reactions := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||||
reaction := []string{"👍", "👎", "♥️", "🤣", "💀"}
|
||||
for _, v := range reaction {
|
||||
like := gtk.NewButton()
|
||||
like.SetLabel(v)
|
||||
like.SetHExpand(true)
|
||||
like.ConnectClicked(func() {
|
||||
fmt.Println("licked")
|
||||
fmt.Println("licked") // TODO: Implement proper support for reactions via extension
|
||||
client.SendRaw(fmt.Sprintf(`
|
||||
<message from='%s' to='%s' id='%s' type='%s'>
|
||||
<reactions id='%s' xmlns='urn:xmpp:reactions:0'>
|
||||
@@ -72,75 +114,115 @@ func generateMessageWidget(p stanza.Packet) gtk.Widgetter {
|
||||
reactions.Append(like)
|
||||
}
|
||||
|
||||
gesture.Connect("pressed", func(n_press, x, y int) {
|
||||
if !vis {
|
||||
vis = true
|
||||
reactions.SetVisible(true)
|
||||
} else {
|
||||
vis = false
|
||||
reactions.SetVisible(false)
|
||||
rc_box.Append(reactions)
|
||||
|
||||
quote := gtk.NewButtonWithLabel("Quote")
|
||||
quote.ConnectClicked(func() {
|
||||
lines := strings.Split(m.Body, "\n")
|
||||
for i, line := range lines {
|
||||
quoteline := "> " + line
|
||||
lines[i] = quoteline
|
||||
}
|
||||
|
||||
newstr := strings.Join(lines, "\n") + "\n\n"
|
||||
|
||||
message_en.SetText(newstr)
|
||||
})
|
||||
rc_box.Append(quote)
|
||||
|
||||
popover.SetChild(rc_box)
|
||||
|
||||
gesture.Connect("pressed", func(n_press, x, y int) {
|
||||
rect := gdk.NewRectangle(x, y, 1, 1)
|
||||
popover.SetPointingTo(&rect)
|
||||
popover.Popup()
|
||||
})
|
||||
|
||||
mainBox.AddController(gesture)
|
||||
|
||||
reply := Reply{}
|
||||
ok = m.Get(&reply)
|
||||
if ok {
|
||||
replyBox := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||||
replyBox.Append(gtk.NewLabel("↱ " + jid.MustParse(reply.To).Resourcepart()))
|
||||
mainBox.Append(replyBox)
|
||||
}
|
||||
|
||||
ocu := OccupantID{}
|
||||
|
||||
m.Get(&ocu)
|
||||
//id := ocu.ID
|
||||
// if id == "" {
|
||||
id := JidMustParse(m.From).Resource
|
||||
// }
|
||||
|
||||
authorBox := gtk.NewBox(gtk.OrientationHorizontal, 10)
|
||||
|
||||
contentBox := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||||
// im := newImageFromPath("debug.png")
|
||||
|
||||
// authorBox.Append(im)
|
||||
|
||||
n := JidMustParse(m.From).Resource
|
||||
if n == "" {
|
||||
n = JidMustParse(m.From).Resource
|
||||
}
|
||||
al := gtk.NewLabel(n)
|
||||
al.AddCSSClass("author")
|
||||
al.SetSelectable(true)
|
||||
|
||||
if m.Type == stanza.MessageTypeGroupchat {
|
||||
mo, _ := mucmembers.Load(jid.MustParse(m.From).Bare().String())
|
||||
mo, _ := mucmembers.Load(JidMustParse(m.From).Bare())
|
||||
mm := mo.(mucUnit)
|
||||
mmm := mm.Members
|
||||
mmmm, ok := mmm.Load(ocu.ID)
|
||||
mmmm, ok := mmm.Load(id)
|
||||
if ok {
|
||||
pres := mmmm.(stanza.Presence)
|
||||
|
||||
var vu VCardUpdate
|
||||
pres.Get(&vu)
|
||||
im := createIdenticon(m.From)
|
||||
im.SetPixelSize(40)
|
||||
im.AddCSSClass("author_img")
|
||||
authorBox.Append(im)
|
||||
if vu.Photo != "" {
|
||||
im := getAvatar(m.From, vu.Photo)
|
||||
im.SetPixelSize(40)
|
||||
im.AddCSSClass("author_img")
|
||||
authorBox.Append(im)
|
||||
} else {
|
||||
im := newImageFromPath("debug.png")
|
||||
im.SetPixelSize(40)
|
||||
im.AddCSSClass("author_img")
|
||||
authorBox.Append(im)
|
||||
go func() {
|
||||
new_im := getAvatar(m.From, vu.Photo)
|
||||
glib.IdleAdd(func() {
|
||||
new_im.SetPixelSize(40)
|
||||
new_im.AddCSSClass("author_img")
|
||||
authorBox.Remove(im)
|
||||
authorBox.Prepend(new_im)
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
} else {
|
||||
im := newImageFromPath("debug.png")
|
||||
im := createIdenticon(m.From)
|
||||
im.SetPixelSize(40)
|
||||
im.AddCSSClass("author_img")
|
||||
authorBox.Append(im)
|
||||
}
|
||||
} else if m.Type == stanza.MessageTypeChat {
|
||||
al.SetText(al.Text() + loadedLocale["whispers"])
|
||||
}
|
||||
|
||||
al := gtk.NewLabel(jid.MustParse(m.From).Resourcepart())
|
||||
al.AddCSSClass("author")
|
||||
|
||||
authorBox.Append(al)
|
||||
|
||||
wxdc := XDCEl{}
|
||||
ok = m.Get(&wxdc)
|
||||
if ok {
|
||||
authorBox.Append(gtk.NewLabel("🎮"))
|
||||
}
|
||||
|
||||
mlabel := gtk.NewLabel(m.Body)
|
||||
// mlabel.SetMarkup(convertXEPToPango(m.Body))
|
||||
if m.Body == "" {
|
||||
mlabel.SetText(loadedLocale["noBodySet"])
|
||||
mlabel.AddCSSClass("visitor")
|
||||
}
|
||||
mlabel.SetWrap(true)
|
||||
mlabel.SetSelectable(true)
|
||||
mlabel.SetHAlign(gtk.AlignFill)
|
||||
|
||||
/*
|
||||
mum := MucUser{}
|
||||
ok = m.Get(&mum)
|
||||
if ok {
|
||||
mlabel.SetText(fmt.Sprintf("%s%s%s", mum.MucUserItem.JID, loadedLocale["affilChange"], mum.MucUserItem.Affiliation))
|
||||
}
|
||||
*/
|
||||
|
||||
contentBox.Append(mlabel)
|
||||
|
||||
mainBox.Append(authorBox)
|
||||
@@ -163,6 +245,39 @@ func generateMessageWidget(p stanza.Packet) gtk.Widgetter {
|
||||
authorBox.Append(mbtn)
|
||||
}
|
||||
|
||||
if m.Subject != "" {
|
||||
subjectlabel := gtk.NewLabel(m.Subject)
|
||||
subjectlabel.SetWrap(true)
|
||||
subjectlabel.SetSelectable(true)
|
||||
subjectlabel.SetHAlign(gtk.AlignFill)
|
||||
subjectlabel.AddCSSClass("subject")
|
||||
mainBox.Append(subjectlabel)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -172,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.
|
||||
oghash := hash
|
||||
p, err := ensureCache()
|
||||
if err != nil {
|
||||
return newImageFromPath("debug.png")
|
||||
return createIdenticon(j)
|
||||
}
|
||||
|
||||
if hash == "" {
|
||||
fmt.Println("Hash is nil!")
|
||||
return newImageFromPath("debug.png")
|
||||
return createIdenticon(j)
|
||||
}
|
||||
|
||||
hash = filepath.Join(p, sanitizefilename.Sanitize(hash))
|
||||
_, ok := invalidImages[hash]
|
||||
if ok {
|
||||
return createIdenticon(j)
|
||||
}
|
||||
|
||||
hash = filepath.Join(p, hash)
|
||||
|
||||
_, err = os.ReadFile(hash)
|
||||
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{
|
||||
Type: "get",
|
||||
From: clientroot.Session.BindJid,
|
||||
To: j,
|
||||
Id: "vc2",
|
||||
Id: uuid.New().String(),
|
||||
Lang: "en",
|
||||
})
|
||||
|
||||
@@ -211,12 +337,14 @@ func getAvatar(j, hash string) *gtk.Image { // TODO: This function probably shou
|
||||
result := <-mychan
|
||||
card, ok := result.Payload.(*VCard)
|
||||
if !ok {
|
||||
return newImageFromPath("debug.png")
|
||||
return createIdenticon(j)
|
||||
}
|
||||
|
||||
base64_data := card.Photo.Binval
|
||||
if card.Photo.Binval == "" || (card.Photo.Type == "image/svg+xml" && runtime.GOOS == "windows") {
|
||||
return newImageFromPath("debug.png")
|
||||
if card.Photo.Binval == "" || ((card.Photo.Type == "image/svg+xml" || card.Photo.Type == "image/webp") && (runtime.GOOS == "windows" || runtime.GOOS == "netbsd")) {
|
||||
fmt.Println("Blocking image")
|
||||
invalidImages[oghash] = true
|
||||
return createIdenticon(j)
|
||||
}
|
||||
|
||||
data, err := base64.StdEncoding.DecodeString(base64_data)
|
||||
@@ -229,5 +357,11 @@ func getAvatar(j, hash string) *gtk.Image { // TODO: This function probably shou
|
||||
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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
|
||||
"github.com/diamondburned/gotk4/pkg/gio/v2"
|
||||
"github.com/diamondburned/gotk4/pkg/gtk/v4"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"math/rand/v2"
|
||||
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
func randomClientResource() string {
|
||||
chars := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZλ"
|
||||
str := ""
|
||||
for range 4 {
|
||||
str = str + string(chars[rand.IntN(len(chars))])
|
||||
}
|
||||
|
||||
return "lambda." + str
|
||||
}
|
||||
|
||||
func dropToSignInPage(err error) {
|
||||
app := gtk.NewApplication("net.sunglocto.lambda.login", gio.ApplicationFlagsNone)
|
||||
app.ConnectActivate(func() {
|
||||
form_box := gtk.NewBox(gtk.OrientationVertical, 0)
|
||||
|
||||
server_box := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||||
username_box := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||||
password_box := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||||
nickname_box := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||||
insecure_box := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||||
|
||||
server_label := gtk.NewLabel(loadedLocale["SIServerLabel"])
|
||||
username_label := gtk.NewLabel(loadedLocale["SIUsernameLabel"])
|
||||
password_label := gtk.NewLabel(loadedLocale["SIPasswordLabel"])
|
||||
nickname_label := gtk.NewLabel(loadedLocale["SINicknameLabel"])
|
||||
insecure_label := gtk.NewLabel(loadedLocale["SIInsecureLabel"])
|
||||
insecure_label.SetTooltipText(loadedLocale["SIInsecureLabelTooltip"])
|
||||
|
||||
server_entry := gtk.NewEntry()
|
||||
server_entry.SetHAlign(gtk.AlignEnd)
|
||||
server_entry.SetHExpand(true)
|
||||
|
||||
username_entry := gtk.NewEntry()
|
||||
username_entry.SetHAlign(gtk.AlignEnd)
|
||||
username_entry.SetHExpand(true)
|
||||
|
||||
password_entry := gtk.NewPasswordEntry()
|
||||
password_entry.SetHAlign(gtk.AlignEnd)
|
||||
password_entry.SetHExpand(true)
|
||||
|
||||
nickname_entry := gtk.NewEntry()
|
||||
nickname_entry.SetHAlign(gtk.AlignEnd)
|
||||
nickname_entry.SetHExpand(true)
|
||||
|
||||
insecure_check := gtk.NewCheckButton()
|
||||
insecure_check.SetHAlign(gtk.AlignEnd)
|
||||
insecure_check.SetHExpand(true)
|
||||
|
||||
server_box.Append(server_label)
|
||||
server_box.Append(server_entry)
|
||||
|
||||
username_box.Append(username_label)
|
||||
username_box.Append(username_entry)
|
||||
|
||||
password_box.Append(password_label)
|
||||
password_box.Append(password_entry)
|
||||
|
||||
nickname_box.Append(nickname_label)
|
||||
nickname_box.Append(nickname_entry)
|
||||
|
||||
insecure_box.Append(insecure_label)
|
||||
insecure_box.Append(insecure_check)
|
||||
|
||||
form_box.Append(server_box)
|
||||
form_box.Append(username_box)
|
||||
form_box.Append(password_box)
|
||||
form_box.Append(nickname_box)
|
||||
form_box.Append(insecure_box)
|
||||
|
||||
sumbit_btn := gtk.NewButtonWithLabel(loadedLocale["submit"])
|
||||
sumbit_btn.ConnectClicked(func() {
|
||||
conf := new(lambdaConfig)
|
||||
conf.Server = server_entry.Text()
|
||||
conf.Username = username_entry.Text()
|
||||
conf.Password = password_entry.Text()
|
||||
conf.Nick = nickname_entry.Text()
|
||||
conf.Insecure = insecure_check.Active()
|
||||
conf.JoinBookmarks = true
|
||||
conf.Resource = randomClientResource()
|
||||
|
||||
var b bytes.Buffer
|
||||
e := toml.NewEncoder(&b)
|
||||
e.Encode(conf)
|
||||
|
||||
p, err := ensureConfig()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
os.WriteFile(filepath.Join(p, "lambda.toml"), b.Bytes(), 0644)
|
||||
|
||||
window.SetVisible(false)
|
||||
main()
|
||||
os.Exit(0)
|
||||
})
|
||||
|
||||
form_box.Append(sumbit_btn)
|
||||
|
||||
window = gtk.NewApplicationWindow(app)
|
||||
window.SetChild(form_box)
|
||||
window.SetResizable(false)
|
||||
window.SetVisible(true)
|
||||
})
|
||||
|
||||
if code := app.Run(os.Args); code == 0 {
|
||||
os.Exit(code)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
|
After Width: | Height: | Size: 5.5 KiB |
@@ -10,3 +10,73 @@
|
||||
.author_img {
|
||||
border-radius 100%;
|
||||
}
|
||||
|
||||
.owner {
|
||||
background-color: red;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.admin {
|
||||
background-color: orange;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.member {
|
||||
background-color: lime;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.moderator {
|
||||
color: magenta;
|
||||
}
|
||||
|
||||
.visitor {
|
||||
color: grey;
|
||||
}
|
||||
|
||||
.pending {
|
||||
color: grey;
|
||||
transition-duration: 0.5s;
|
||||
}
|
||||
|
||||
.hat {
|
||||
background-color: orange;
|
||||
color: black;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.subject {
|
||||
color: lime;
|
||||
}
|
||||
|
||||
.icon {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.jid {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: white;
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -2,24 +2,31 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/diamondburned/gotk4/pkg/gtk/v4"
|
||||
"mellium.im/xmpp/color"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type chatTab struct {
|
||||
isMuc bool
|
||||
msgs *gtk.ListBox
|
||||
name string
|
||||
}
|
||||
|
||||
type lambdaConfig struct {
|
||||
Server string
|
||||
Username string
|
||||
Password string
|
||||
Insecure bool
|
||||
Nick string
|
||||
Server string
|
||||
Username string
|
||||
Resource string
|
||||
Password string
|
||||
Insecure bool
|
||||
Nick string
|
||||
JoinBookmarks bool
|
||||
CVD color.CVD
|
||||
Identicons bool
|
||||
Debug bool
|
||||
}
|
||||
|
||||
type mucUnit struct {
|
||||
// key: OccupantID
|
||||
// key: Resource
|
||||
// value: last user presence
|
||||
Members sync.Map
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package main
|
||||
|
||||
var lambda_version string = "0.1.0"
|
||||
var lambda_version string = "26w17a"
|
||||
|
||||
@@ -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{})
|
||||
}
|
||||
@@ -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{})
|
||||
}
|
||||
@@ -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{})
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package main
|
||||
|
||||
// Partial implementation of XEP-0333: Displayed Markers
|
||||
// https://xmpp.org/extensions/xep-0333.html
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"gosrc.io/xmpp/stanza"
|
||||
)
|
||||
|
||||
type Marker struct {
|
||||
stanza.MsgExtension
|
||||
XMLName xml.Name `xml:"urn:xmpp:chat-markers:0 displayed"`
|
||||
ID string `xml:"id,attr"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{Space: "urn:xmpp:chat-markers:0", Local: "displayed"}, Marker{})
|
||||
}
|
||||
@@ -9,21 +9,21 @@ import (
|
||||
// https://xmpp.org/extensions/xep-0317.html
|
||||
|
||||
type Hats struct {
|
||||
XMLName xml.Name `xml:"urn:xmpp:hats:0 hats"`
|
||||
Hats []Hat `xml:"hat"`
|
||||
XMLName xml.Name `xml:"urn:xmpp:hats:0 hats"`
|
||||
Hats []Hat `xml:"hat"`
|
||||
}
|
||||
|
||||
type Hat struct {
|
||||
Title string `xml:"title,attr"`
|
||||
URI string `xml:"uri,attr"`
|
||||
Hue string `xml:"hue,attr"`
|
||||
Lang string `xml:"xml:lang,attr"`
|
||||
Badge Badge `xml:"badge"`
|
||||
Title string `xml:"title,attr"`
|
||||
URI string `xml:"uri,attr"`
|
||||
Hue string `xml:"hue,attr"`
|
||||
Lang string `xml:"xml:lang,attr"`
|
||||
Badge Badge `xml:"badge"`
|
||||
}
|
||||
|
||||
type Badge struct {
|
||||
XMLName xml.Name `xml:"urn:example:badges badge"`
|
||||
Level int `xml:"level,attr"`
|
||||
XMLName xml.Name `xml:"urn:example:badges badge"`
|
||||
Level int `xml:"level,attr"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gosrc.io/xmpp"
|
||||
"gosrc.io/xmpp/stanza"
|
||||
)
|
||||
@@ -9,15 +8,16 @@ import (
|
||||
// This file has small functions that can be used to do XMPP stuff without writing tons of boilerplate
|
||||
|
||||
// Basic message sender. Anything more complex should be written by hand
|
||||
func sendMessage(c xmpp.Sender, sendTo string, msgType stanza.StanzaType, body string, subject string, thread string) error {
|
||||
func sendMessage(c xmpp.Sender, sendTo string, msgType stanza.StanzaType, body string, subject string, thread string, exts []stanza.MsgExtension) error {
|
||||
m := stanza.Message{
|
||||
Attrs: stanza.Attrs{
|
||||
To: sendTo,
|
||||
Type: msgType,
|
||||
},
|
||||
Body: body,
|
||||
Subject: subject,
|
||||
Thread: thread,
|
||||
Body: body,
|
||||
Subject: subject,
|
||||
Thread: thread,
|
||||
Extensions: exts,
|
||||
}
|
||||
err := c.Send(m)
|
||||
if err != nil {
|
||||
@@ -27,17 +27,31 @@ func sendMessage(c xmpp.Sender, sendTo string, msgType stanza.StanzaType, body s
|
||||
}
|
||||
|
||||
// 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
|
||||
fmt.Println(addr)
|
||||
joinPresence := stanza.Presence{
|
||||
Attrs: stanza.Attrs{
|
||||
From: jid,
|
||||
To: addr,
|
||||
},
|
||||
Extensions: []stanza.PresExtension{
|
||||
&stanza.MucPresence{},
|
||||
},
|
||||
if password == "" {
|
||||
joinPresence = stanza.Presence{
|
||||
Attrs: stanza.Attrs{
|
||||
From: jid,
|
||||
To: addr,
|
||||
},
|
||||
Extensions: []stanza.PresExtension{
|
||||
&stanza.MucPresence{},
|
||||
},
|
||||
}
|
||||
} else {
|
||||
joinPresence = stanza.Presence{
|
||||
Attrs: stanza.Attrs{
|
||||
From: jid,
|
||||
To: addr,
|
||||
},
|
||||
Extensions: []stanza.PresExtension{
|
||||
&stanza.MucPresence{
|
||||
Password: password,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
err := client.Send(joinPresence)
|
||||
@@ -46,3 +60,59 @@ func joinMuc(c xmpp.Sender, jid string, muc string, nick string) error {
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// jid MustParse but using gosrc's instead of mellium
|
||||
// This function will panic if its an invalid JID
|
||||
|
||||
func JidMustParse(s string) *stanza.Jid {
|
||||
j, err := stanza.NewJid(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return j
|
||||
}
|
||||
|
||||
@@ -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{})
|
||||
}
|
||||
@@ -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{})
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
type MucUser struct {
|
||||
stanza.PresExtension
|
||||
stanza.MsgExtension
|
||||
XMLName xml.Name `xml:"http://jabber.org/protocol/muc#user x"`
|
||||
MucUserItem MucUserItem `xml:"item,omitempty"`
|
||||
}
|
||||
@@ -19,9 +20,16 @@ type MucUserItem struct {
|
||||
Affiliation string `xml:"affiliation,attr,omitempty"` // TODO: Use enum
|
||||
Role string `xml:"role,attr,omitempty"` // TODO: Use enum
|
||||
JID string `xml:"jid,attr,omitempty"`
|
||||
Reason string `xml:"reason,omitempty"`
|
||||
Reason string `xml:"reason,omitempty"`
|
||||
Actor Actor `xml:"actor,omitempty"`
|
||||
}
|
||||
|
||||
type Actor struct {
|
||||
JID string `xml:"jid,attr"`
|
||||
Nick string `xml:"nick,attr"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
stanza.TypeRegistry.MapExtension(stanza.PKTPresence, xml.Name{Space: "http://jabber.org/protocol/muc#user", Local: "x"}, MucUser{})
|
||||
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{Space: "http://jabber.org/protocol/muc#user", Local: "x"}, MucUser{})
|
||||
}
|
||||
|
||||
@@ -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{})
|
||||
}
|
||||
@@ -9,9 +9,23 @@ import (
|
||||
)
|
||||
|
||||
type VCard struct {
|
||||
XMLName xml.Name `xml:"vcard-temp vCard"`
|
||||
Photo Photo `xml:"PHOTO"`
|
||||
ResultSet *stanza.ResultSet `xml:"set,omitempty"`
|
||||
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"`
|
||||
Email string `xml:"EMAIL>USERID"`
|
||||
ResultSet *stanza.ResultSet `xml:"set,omitempty"`
|
||||
}
|
||||
|
||||
type VCardUpdate struct {
|
||||
|
||||
@@ -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{})
|
||||
}
|
||||