Attention and experimental mentions impl
This commit is contained in:
14
assets.go
14
assets.go
@@ -87,6 +87,11 @@ var connectB64 string = base64.StdEncoding.EncodeToString(connectBytes)
|
||||
var commentBytes []byte
|
||||
var commentB64 string = base64.StdEncoding.EncodeToString(commentBytes)
|
||||
|
||||
|
||||
//go:embed assets/information.png
|
||||
var informationBytes []byte
|
||||
var informationB64 string = base64.StdEncoding.EncodeToString(informationBytes)
|
||||
|
||||
func init() {
|
||||
|
||||
loader := gdkpixbuf.NewPixbufLoader()
|
||||
@@ -248,4 +253,13 @@ func init() {
|
||||
loader.Close()
|
||||
|
||||
clientAssets["comment"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||
|
||||
|
||||
loader = gdkpixbuf.NewPixbufLoader()
|
||||
|
||||
informationData, _ := base64.StdEncoding.DecodeString(informationB64)
|
||||
loader.Write(informationData)
|
||||
loader.Close()
|
||||
|
||||
clientAssets["information"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||
}
|
||||
|
||||
2
go.mod
2
go.mod
@@ -12,6 +12,7 @@ require (
|
||||
github.com/jasonlovesdoggo/gopen v0.0.0-20250130105607-39c98c645030
|
||||
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f
|
||||
github.com/kr/pretty v0.2.0
|
||||
golang.org/x/net v0.29.0
|
||||
gosrc.io/xmpp v0.5.1
|
||||
mellium.im/xmpp v0.22.0
|
||||
)
|
||||
@@ -33,7 +34,6 @@ require (
|
||||
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/image v0.24.0 // indirect
|
||||
golang.org/x/net v0.29.0 // indirect
|
||||
golang.org/x/sync v0.11.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
|
||||
@@ -2,8 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/diamondburned/gotk4/pkg/glib/v2"
|
||||
"github.com/diamondburned/gotk4/pkg/gtk/v4"
|
||||
@@ -112,11 +110,17 @@ func switchToTab(jid string, w *gtk.Window) {
|
||||
win.SetDefaultSize(400, 400)
|
||||
profile_box := gtk.NewBox(gtk.OrientationVertical, 0)
|
||||
nick := gtk.NewLabel(JidMustParse(u.From).Resource)
|
||||
ver_text := gtk.NewLabel("Getting version...")
|
||||
ver_text.AddCSSClass("visitor")
|
||||
|
||||
win.SetTitle(JidMustParse(u.From).Resource)
|
||||
nick.AddCSSClass("author")
|
||||
profile_box.Append(nick)
|
||||
profile_box.Append(gtk.NewLabel(u.From))
|
||||
profile_box.Append(ver_text)
|
||||
fr := gtk.NewLabel(u.From)
|
||||
fr.AddCSSClass("jid")
|
||||
profile_box.Append(fr)
|
||||
profile_box.Append(ver_text)
|
||||
|
||||
iqResp, err := stanza.NewIQ(stanza.Attrs{
|
||||
Type: "get",
|
||||
@@ -147,7 +151,9 @@ func switchToTab(jid string, w *gtk.Window) {
|
||||
ok = u.Get(&mu)
|
||||
if ok {
|
||||
if mu.MucUserItem.JID != "" {
|
||||
profile_box.Append(gtk.NewLabel(mu.MucUserItem.JID))
|
||||
ji := (gtk.NewLabel(mu.MucUserItem.JID))
|
||||
ji.AddCSSClass("jid")
|
||||
profile_box.Append(ji)
|
||||
}
|
||||
profile_box.Append(gtk.NewLabel("Connected with role " + mu.MucUserItem.Role))
|
||||
profile_box.Append(gtk.NewLabel("Affiliated as " + mu.MucUserItem.Affiliation))
|
||||
@@ -178,14 +184,24 @@ func switchToTab(jid string, w *gtk.Window) {
|
||||
if ok {
|
||||
idents := res.Identity
|
||||
for i, ident := range idents {
|
||||
profile_box.Append(gtk.NewLabel(fmt.Sprintf("Identity %d: Name: %s, Category: %s, Type: %s", i+1, ident.Name, ident.Category, ident.Type)))
|
||||
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))
|
||||
profile_box.Append(gtk.NewLabel(fmt.Sprintf("The hash of this user's Disco features is:\n%s\nUse the disco feature to view them", sha1_hash)))
|
||||
*/
|
||||
|
||||
sw := gtk.NewScrolledWindow()
|
||||
s := ""
|
||||
for _, feature := range res.Features {
|
||||
s = s + feature.Var + "\n"
|
||||
}
|
||||
sw.SetChild(gtk.NewLabel(s))
|
||||
|
||||
profile_box.Append(sw)
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -202,8 +218,14 @@ func switchToTab(jid string, w *gtk.Window) {
|
||||
version := ver.Version
|
||||
os := ver.OS
|
||||
|
||||
profile_box.Append(gtk.NewLabel(fmt.Sprintf("%s %s %s", name, version, os)))
|
||||
}
|
||||
ver_text.SetText(fmt.Sprintf("%s %s %s", name, version, os))
|
||||
ver_text.RemoveCSSClass("visitor")
|
||||
} else if result.Error != nil && result.Error.Type != "" {
|
||||
ver_text.SetText("Got error trying to get version")
|
||||
ver_text.SetTooltipText(result.Error.Reason + ": "+result.Error.Text)
|
||||
ver_text.RemoveCSSClass("visitor")
|
||||
ver_text.AddCSSClass("error")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -221,18 +243,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.SetPixelSize(80)
|
||||
im.AddCSSClass("author_img")
|
||||
profile_box.Append(im)
|
||||
profile_box.Prepend(im)
|
||||
}
|
||||
} else {
|
||||
im := newImageFromPath("debug.png")
|
||||
im.SetPixelSize(80)
|
||||
im.AddCSSClass("author_img")
|
||||
profile_box.Append(im)
|
||||
profile_box.Prepend(im)
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
@@ -115,6 +115,7 @@ func generateMessageWidget(p stanza.Packet) gtk.Widgetter {
|
||||
|
||||
rc_box.Append(reactions)
|
||||
|
||||
/*
|
||||
if m.Type == stanza.MessageTypeGroupchat {
|
||||
moderate := gtk.NewButtonWithLabel("Moderate") // TODO: Implement proper support for moderations via extension
|
||||
moderate.ConnectClicked(func() {
|
||||
@@ -129,6 +130,13 @@ func generateMessageWidget(p stanza.Packet) gtk.Widgetter {
|
||||
})
|
||||
rc_box.Append(moderate)
|
||||
}
|
||||
*/
|
||||
|
||||
quote := gtk.NewButtonWithLabel("Quote")
|
||||
quote.ConnectClicked(func() {
|
||||
message_en.SetText("> " + m.Body + "\n")
|
||||
})
|
||||
rc_box.Append(quote)
|
||||
|
||||
popover.SetChild(rc_box)
|
||||
|
||||
@@ -203,6 +211,10 @@ func generateMessageWidget(p stanza.Packet) gtk.Widgetter {
|
||||
}
|
||||
|
||||
mlabel := gtk.NewLabel(m.Body)
|
||||
if m.Body == "" {
|
||||
mlabel.SetText("No body set")
|
||||
mlabel.AddCSSClass("visitor")
|
||||
}
|
||||
mlabel.SetWrap(true)
|
||||
mlabel.SetSelectable(true)
|
||||
mlabel.SetHAlign(gtk.AlignFill)
|
||||
|
||||
88
main.go
88
main.go
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/diamondburned/gotk4/pkg/gtk/v4"
|
||||
"github.com/gen2brain/beeep"
|
||||
"github.com/go-analyze/charts"
|
||||
"golang.org/x/net/html/charset"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
@@ -25,6 +26,7 @@ import (
|
||||
"encoding/xml"
|
||||
"github.com/kr/pretty"
|
||||
"runtime"
|
||||
"io"
|
||||
)
|
||||
|
||||
var loadedConfig lambdaConfig
|
||||
@@ -49,6 +51,7 @@ var current string
|
||||
var scroller *gtk.ScrolledWindow
|
||||
var memberList *gtk.ScrolledWindow
|
||||
var menu *gtk.Box
|
||||
var message_en *gtk.Entry
|
||||
|
||||
//go:embed style.css
|
||||
var styleCSS string
|
||||
@@ -111,10 +114,13 @@ func main() {
|
||||
config := xmpp.Config{
|
||||
TransportConfiguration: xmpp.TransportConfiguration{
|
||||
Address: loadedConfig.Server,
|
||||
CharsetReader: func(c string, input io.Reader) (io.Reader, error) {
|
||||
return charset.NewReaderLabel(c, input)
|
||||
},
|
||||
},
|
||||
Jid: loadedConfig.Username + "/" + loadedConfig.Resource,
|
||||
Credential: xmpp.Password(loadedConfig.Password),
|
||||
Insecure: loadedConfig.Insecure,
|
||||
Jid: loadedConfig.Username + "/" + loadedConfig.Resource,
|
||||
Credential: xmpp.Password(loadedConfig.Password),
|
||||
Insecure: loadedConfig.Insecure,
|
||||
// StreamLogger: os.Stdout,
|
||||
StreamManagementEnable: true,
|
||||
}
|
||||
@@ -149,6 +155,7 @@ func main() {
|
||||
{Var: "urn:xmpp:delegation:1"},
|
||||
{Var: "http://jabber.org/protocol/muc"},
|
||||
{Var: "λ"},
|
||||
{Var: "urn:xmpp:attention:0"},
|
||||
},
|
||||
}
|
||||
iqResp.Payload = &payload
|
||||
@@ -197,11 +204,49 @@ func main() {
|
||||
originator := JidMustParse(m.From).Bare()
|
||||
mStatus.SetText(originator)
|
||||
|
||||
at := new(Attention)
|
||||
ok = m.Get(at)
|
||||
if ok {
|
||||
beeep.Notify("Attention", fmt.Sprintf("%s: %s", JidMustParse(m.From).Resource, m.Body), commentBytes) // TODO: Use localpart if DM
|
||||
}
|
||||
|
||||
sc := new(SentCarbon)
|
||||
ok = m.Get(sc)
|
||||
if ok {
|
||||
fm, ok := sc.Forwarded.Stanza.(stanza.Message)
|
||||
if ok {
|
||||
if JidMustParse(fm.From).Bare() == JidMustParse(m.From).Bare() {
|
||||
p = sc.Forwarded.Stanza
|
||||
m = sc.Forwarded.Stanza.(stanza.Message)
|
||||
} else {
|
||||
panic(fmt.Sprintln("Impersonation: ", fm.From, m.From))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rc := new(SentCarbon)
|
||||
ok = m.Get(rc)
|
||||
if ok {
|
||||
fm, ok := rc.Forwarded.Stanza.(stanza.Message)
|
||||
if ok {
|
||||
if JidMustParse(fm.From).Bare() == JidMustParse(m.From).Bare() {
|
||||
p = rc.Forwarded.Stanza
|
||||
m = rc.Forwarded.Stanza.(stanza.Message)
|
||||
} else {
|
||||
panic(fmt.Sprintln("Impersonation: ", fm.From, m.From))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glib.IdleAdd(func() {
|
||||
//uiQueue <- func() {
|
||||
b := gtk.NewBox(gtk.OrientationVertical, 0)
|
||||
|
||||
tab, ok := tabs.Load(originator)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
typed_tab := tab.(*chatTab)
|
||||
|
||||
if ok {
|
||||
@@ -383,6 +428,16 @@ func main() {
|
||||
connectionStatus.SetText(fmt.Sprintf("Connected as %s", JidMustParse(clientroot.Session.BindJid).Bare()))
|
||||
connectionStatus.SetTooltipText(fmt.Sprintf("Binded JID: %s\nUsing TLS: %t", clientroot.Session.BindJid, clientroot.Session.TlsEnabled))
|
||||
connectionIcon.SetFromPaintable(clientAssets["connect"])
|
||||
// Enable carbons
|
||||
client.SendRaw(fmt.Sprintf(
|
||||
`<iq xmlns='jabber:client'
|
||||
from='%s'
|
||||
id='enable1'
|
||||
type='set'>
|
||||
<enable xmlns='urn:xmpp:carbons:2'/>
|
||||
</iq>
|
||||
`, clientroot.Session.BindJid))
|
||||
|
||||
// Join rooms in bookmarks
|
||||
if loadedConfig.JoinBookmarks {
|
||||
books, err := stanza.NewItemsRequest("", "urn:xmpp:bookmarks:1", 0)
|
||||
@@ -778,12 +833,15 @@ func activate(app *gtk.Application) {
|
||||
|
||||
entry_box := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
||||
|
||||
en := gtk.NewEntry()
|
||||
en.SetPlaceholderText("Say something, what else are you gonna do here?")
|
||||
oob_en := gtk.NewEntry()
|
||||
oob_en.SetPlaceholderText("Embed URL")
|
||||
|
||||
message_en = gtk.NewEntry()
|
||||
message_en.SetPlaceholderText("Say something, what else are you gonna do here?")
|
||||
b := gtk.NewButtonWithLabel("Send")
|
||||
|
||||
sendtxt := func() {
|
||||
t := en.Text()
|
||||
t := message_en.Text()
|
||||
if t == "" {
|
||||
dialog := >k.AlertDialog{}
|
||||
dialog.SetDetail("detail")
|
||||
@@ -803,21 +861,29 @@ func activate(app *gtk.Application) {
|
||||
message_type = stanza.MessageTypeGroupchat
|
||||
}
|
||||
|
||||
err := sendMessage(client, current, message_type, t, "", "")
|
||||
exts := []stanza.MsgExtension{}
|
||||
if oob_en.Text() != "" {
|
||||
new_oob := new(stanza.OOB)
|
||||
new_oob.URL = oob_en.Text()
|
||||
exts = append(exts, new_oob)
|
||||
}
|
||||
|
||||
err := sendMessage(client, current, message_type, t, "", "", exts)
|
||||
if err != nil {
|
||||
panic(err) // TODO: Show error message via GTK
|
||||
}
|
||||
en.SetText("")
|
||||
message_en.SetText("")
|
||||
scrollToBottomAfterUpdate(scroller)
|
||||
}
|
||||
|
||||
en.Connect("activate", sendtxt)
|
||||
message_en.Connect("activate", sendtxt)
|
||||
|
||||
b.ConnectClicked(sendtxt)
|
||||
|
||||
en.SetHExpand(true)
|
||||
message_en.SetHExpand(true)
|
||||
|
||||
entry_box.Append(en)
|
||||
entry_box.Append(oob_en)
|
||||
entry_box.Append(message_en)
|
||||
entry_box.Append(b)
|
||||
|
||||
box.Append(entry_box)
|
||||
|
||||
@@ -52,3 +52,12 @@
|
||||
.icon {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.jid {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: white;
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
17
xmpp-attention.go
Normal file
17
xmpp-attention.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"gosrc.io/xmpp/stanza"
|
||||
)
|
||||
|
||||
// Implementation of XEP-0224: Attention
|
||||
|
||||
type Attention struct {
|
||||
stanza.MsgExtension
|
||||
XMLName xml.Name `xml:"urn:xmpp:attention:0 attention"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{Space: "urn:xmpp:attention:0", Local: "attention"}, Attention{})
|
||||
}
|
||||
27
xmpp-carbons.go
Normal file
27
xmpp-carbons.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"gosrc.io/xmpp/stanza"
|
||||
)
|
||||
|
||||
// Implementation of XEP-0280: Message Carbons
|
||||
// https://xmpp.org/extensions/xep-0280.html
|
||||
|
||||
type ReceivedCarbon struct {
|
||||
stanza.MsgExtension
|
||||
XMLName xml.Name `xml:"urn:xmpp:carbons:2 received"`
|
||||
Forwarded stanza.Forwarded
|
||||
}
|
||||
|
||||
|
||||
type SentCarbon struct {
|
||||
stanza.MsgExtension
|
||||
XMLName xml.Name `xml:"urn:xmpp:carbons:2 sent"`
|
||||
Forwarded stanza.Forwarded
|
||||
}
|
||||
|
||||
func init() {
|
||||
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{Space: "urn:xmpp:carbons:2", Local: "received"}, ReceivedCarbon{})
|
||||
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{Space: "urn:xmpp:carbons:2", Local: "sent"}, SentCarbon{})
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
// This file has small functions that can be used to do XMPP stuff without writing tons of boilerplate
|
||||
|
||||
// Basic message sender. Anything more complex should be written by hand
|
||||
func sendMessage(c xmpp.Sender, sendTo string, msgType stanza.StanzaType, body string, subject string, thread string) error {
|
||||
func sendMessage(c xmpp.Sender, sendTo string, msgType stanza.StanzaType, body string, subject string, thread string, exts []stanza.MsgExtension) error {
|
||||
m := stanza.Message{
|
||||
Attrs: stanza.Attrs{
|
||||
To: sendTo,
|
||||
@@ -18,6 +18,7 @@ func sendMessage(c xmpp.Sender, sendTo string, msgType stanza.StanzaType, body s
|
||||
Body: body,
|
||||
Subject: subject,
|
||||
Thread: thread,
|
||||
Extensions: exts,
|
||||
}
|
||||
err := c.Send(m)
|
||||
if err != nil {
|
||||
|
||||
23
xmpp-mentions.go
Normal file
23
xmpp-mentions.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"gosrc.io/xmpp/stanza"
|
||||
)
|
||||
|
||||
// Experimental implementation of XEP-XXXX: Explicit Mentions
|
||||
// https://git.isekai.rocks/snit/protoxeps/tree/explicit-mentions.xml
|
||||
|
||||
type Mention struct {
|
||||
stanza.MsgExtension
|
||||
XMLName xml.Name `xml:"urn:xmpp:mentions:0 mention"`
|
||||
Mentions string `xml:"mentions,attr,omitempty"`
|
||||
URI string `xml:"uri,attr,omitempty"`
|
||||
Begin int `xml:"begin,attr,omitempty"`
|
||||
End int `xml:"end,attr,omitempty"`
|
||||
OccupantID string `xml:"occupantid,attr,omitempty"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{Space: "urn:xmpp:mentions:0", Local: "mention"}, Mention{})
|
||||
}
|
||||
Reference in New Issue
Block a user