ban, kick and change role&affil menu, status icons in muc, blocking avatars that are invalid so we dont fetch them over and over again
64
assets.go
@@ -39,6 +39,27 @@ var outcastMedalB64 string = base64.StdEncoding.EncodeToString(outcastMedalBytes
|
|||||||
var cancelBytes []byte
|
var cancelBytes []byte
|
||||||
var cancelB64 string = base64.StdEncoding.EncodeToString(cancelBytes)
|
var cancelB64 string = base64.StdEncoding.EncodeToString(cancelBytes)
|
||||||
|
|
||||||
|
|
||||||
|
//go:embed assets/status_away.png
|
||||||
|
var sABytes []byte
|
||||||
|
var sAB64 string = base64.StdEncoding.EncodeToString(sABytes)
|
||||||
|
|
||||||
|
//go:embed assets/status_busy.png
|
||||||
|
var sBBytes []byte
|
||||||
|
var sBB64 string = base64.StdEncoding.EncodeToString(sBBytes)
|
||||||
|
|
||||||
|
//go:embed assets/status_chatty.png
|
||||||
|
var sCBytes []byte
|
||||||
|
var sCB64 string = base64.StdEncoding.EncodeToString(sCBytes)
|
||||||
|
|
||||||
|
//go:embed assets/status_online.png
|
||||||
|
var sOBytes []byte
|
||||||
|
var sOB64 string = base64.StdEncoding.EncodeToString(sOBytes)
|
||||||
|
|
||||||
|
//go:embed assets/status_xa.png
|
||||||
|
var xaBytes []byte
|
||||||
|
var xaB64 string = base64.StdEncoding.EncodeToString(xaBytes)
|
||||||
|
|
||||||
//go:embed assets/tag.png
|
//go:embed assets/tag.png
|
||||||
var tagBytes []byte
|
var tagBytes []byte
|
||||||
var tagB64 string = base64.StdEncoding.EncodeToString(tagBytes)
|
var tagB64 string = base64.StdEncoding.EncodeToString(tagBytes)
|
||||||
@@ -104,7 +125,6 @@ func init() {
|
|||||||
loader.Close()
|
loader.Close()
|
||||||
clientAssets["DefaultAvatar"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
clientAssets["DefaultAvatar"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||||
|
|
||||||
|
|
||||||
loader = gdkpixbuf.NewPixbufLoader()
|
loader = gdkpixbuf.NewPixbufLoader()
|
||||||
|
|
||||||
failedData, _ := base64.StdEncoding.DecodeString(failedB64)
|
failedData, _ := base64.StdEncoding.DecodeString(failedB64)
|
||||||
@@ -272,4 +292,46 @@ func init() {
|
|||||||
loader.Close()
|
loader.Close()
|
||||||
|
|
||||||
clientAssets["information"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
clientAssets["information"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||||
|
|
||||||
|
loader = gdkpixbuf.NewPixbufLoader()
|
||||||
|
|
||||||
|
sAData, _ := base64.StdEncoding.DecodeString(sAB64)
|
||||||
|
loader.Write(sAData)
|
||||||
|
loader.Close()
|
||||||
|
|
||||||
|
clientAssets["status_away"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||||
|
|
||||||
|
|
||||||
|
loader = gdkpixbuf.NewPixbufLoader()
|
||||||
|
|
||||||
|
sBData, _ := base64.StdEncoding.DecodeString(sBB64)
|
||||||
|
loader.Write(sBData)
|
||||||
|
loader.Close()
|
||||||
|
|
||||||
|
clientAssets["status_dnd"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||||
|
|
||||||
|
loader = gdkpixbuf.NewPixbufLoader()
|
||||||
|
|
||||||
|
sCData, _ := base64.StdEncoding.DecodeString(sCB64)
|
||||||
|
loader.Write(sCData)
|
||||||
|
loader.Close()
|
||||||
|
|
||||||
|
clientAssets["status_chat"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||||
|
|
||||||
|
loader = gdkpixbuf.NewPixbufLoader()
|
||||||
|
|
||||||
|
xaData, _ := base64.StdEncoding.DecodeString(xaB64)
|
||||||
|
loader.Write(xaData)
|
||||||
|
loader.Close()
|
||||||
|
|
||||||
|
clientAssets["status_xa"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||||
|
|
||||||
|
|
||||||
|
loader = gdkpixbuf.NewPixbufLoader()
|
||||||
|
|
||||||
|
sOData, _ := base64.StdEncoding.DecodeString(sOB64)
|
||||||
|
loader.Write(sOData)
|
||||||
|
loader.Close()
|
||||||
|
|
||||||
|
clientAssets["status_"] = gdk.NewTextureForPixbuf(loader.Pixbuf())
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
assets/status_away.png
Normal file
|
After Width: | Height: | Size: 794 B |
BIN
assets/status_busy.png
Normal file
|
After Width: | Height: | Size: 751 B |
BIN
assets/status_chatty.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
assets/status_online.png
Normal file
|
After Width: | Height: | Size: 722 B |
BIN
assets/status_xa.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
failed_load.png
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.5 KiB |
160
gtk-helpers.go
@@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/diamondburned/gotk4/pkg/gdk/v4"
|
||||||
"github.com/diamondburned/gotk4/pkg/glib/v2"
|
"github.com/diamondburned/gotk4/pkg/glib/v2"
|
||||||
"github.com/diamondburned/gotk4/pkg/gtk/v4"
|
"github.com/diamondburned/gotk4/pkg/gtk/v4"
|
||||||
"github.com/diamondburned/gotk4/pkg/pango"
|
"github.com/diamondburned/gotk4/pkg/pango"
|
||||||
@@ -82,7 +83,7 @@ func switchToTab(jid string, w *gtk.Window) {
|
|||||||
nick_label.SetOpacity(0.5)
|
nick_label.SetOpacity(0.5)
|
||||||
}
|
}
|
||||||
|
|
||||||
userbox.SetTooltipText(fmt.Sprintf("%s\n%s\n%s\nRight-click for more information", u.From, mu.MucUserItem.Role, mu.MucUserItem.Affiliation))
|
userbox.SetTooltipText(fmt.Sprintf("%s\n%s\n%s\nClick for more information", u.From, mu.MucUserItem.Role, mu.MucUserItem.Affiliation))
|
||||||
userbox.Append(nick_label)
|
userbox.Append(nick_label)
|
||||||
|
|
||||||
var hats Hats
|
var hats Hats
|
||||||
@@ -95,6 +96,13 @@ func switchToTab(jid string, w *gtk.Window) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
status := gtk.NewImageFromPaintable(clientAssets["status_"+string(u.Show)])
|
||||||
|
status.SetTooltipText(string(u.Show))
|
||||||
|
|
||||||
|
status.SetHAlign(gtk.AlignEnd)
|
||||||
|
// medal.SetHExpand(true)
|
||||||
|
userbox.Prepend(status)
|
||||||
|
|
||||||
medal := gtk.NewImageFromPaintable(clientAssets[mu.MucUserItem.Affiliation])
|
medal := gtk.NewImageFromPaintable(clientAssets[mu.MucUserItem.Affiliation])
|
||||||
medal.SetTooltipText(mu.MucUserItem.Affiliation)
|
medal.SetTooltipText(mu.MucUserItem.Affiliation)
|
||||||
|
|
||||||
@@ -103,7 +111,145 @@ func switchToTab(jid string, w *gtk.Window) {
|
|||||||
userbox.Append(medal)
|
userbox.Append(medal)
|
||||||
|
|
||||||
gesture := gtk.NewGestureClick()
|
gesture := gtk.NewGestureClick()
|
||||||
gesture.SetButton(3) // Right click
|
gesture.SetButton(1)
|
||||||
|
|
||||||
|
mod_gesture := gtk.NewGestureClick()
|
||||||
|
mod_gesture.SetButton(3)
|
||||||
|
|
||||||
|
popover := gtk.NewPopover()
|
||||||
|
popover.SetHasArrow(false)
|
||||||
|
popover.SetParent(userbox)
|
||||||
|
|
||||||
|
rc_box := gtk.NewBox(gtk.OrientationVertical, 0)
|
||||||
|
bb := gtk.NewButtonWithLabel("Ban")
|
||||||
|
kb := gtk.NewButtonWithLabel("Kick")
|
||||||
|
ab := gtk.NewButtonWithLabel("Set affil")
|
||||||
|
rb := gtk.NewButtonWithLabel("Set role")
|
||||||
|
|
||||||
|
kb.ConnectClicked(func() {
|
||||||
|
client.SendRaw(fmt.Sprintf(`
|
||||||
|
<iq from='%s'
|
||||||
|
id='kick1'
|
||||||
|
to='%s'
|
||||||
|
type='set'>
|
||||||
|
<query xmlns='http://jabber.org/protocol/muc#admin'>
|
||||||
|
<item nick='%s' role='none'>
|
||||||
|
</item>
|
||||||
|
</query>
|
||||||
|
</iq>
|
||||||
|
`, clientroot.Session.BindJid, jid, JidMustParse(u.From).Resource))
|
||||||
|
})
|
||||||
|
|
||||||
|
bb.ConnectClicked(func() {
|
||||||
|
var mu MucUser
|
||||||
|
ok = u.Get(&mu)
|
||||||
|
if ok {
|
||||||
|
if mu.MucUserItem.JID != "" {
|
||||||
|
client.SendRaw(fmt.Sprintf(`
|
||||||
|
<iq from='%s'
|
||||||
|
id='ban1'
|
||||||
|
to='%s'
|
||||||
|
type='set'>
|
||||||
|
<query xmlns='http://jabber.org/protocol/muc#admin'>
|
||||||
|
<item affiliation='outcast' jid='%s'/>
|
||||||
|
</query>
|
||||||
|
</iq>
|
||||||
|
`, clientroot.Session.BindJid, jid, JidMustParse(mu.MucUserItem.JID).Bare()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ab.ConnectClicked(func() {
|
||||||
|
var mu MucUser
|
||||||
|
ok = u.Get(&mu)
|
||||||
|
if ok {
|
||||||
|
if mu.MucUserItem.JID != "" {
|
||||||
|
win := gtk.NewWindow()
|
||||||
|
win.SetDefaultSize(400, 1)
|
||||||
|
win.SetResizable(false)
|
||||||
|
|
||||||
|
box := gtk.NewBox(gtk.OrientationVertical, 0)
|
||||||
|
box.Append(gtk.NewLabel("Set "+JidMustParse(u.From).Resource+"'s affiliation"))
|
||||||
|
|
||||||
|
the_entry := gtk.NewEntry()
|
||||||
|
the_entry.SetText(mu.MucUserItem.Affiliation)
|
||||||
|
|
||||||
|
submit := gtk.NewButtonWithLabel("Submit")
|
||||||
|
submit.ConnectClicked(func() {
|
||||||
|
client.SendRaw(fmt.Sprintf(`
|
||||||
|
<iq from='%s'
|
||||||
|
id='ba1'
|
||||||
|
to='%s'
|
||||||
|
type='set'>
|
||||||
|
<query xmlns='http://jabber.org/protocol/muc#admin'>
|
||||||
|
<item affiliation='%s' jid='%s'/>
|
||||||
|
</query>
|
||||||
|
</iq>
|
||||||
|
`, clientroot.Session.BindJid, jid, the_entry.Text(), JidMustParse(mu.MucUserItem.JID).Bare()))
|
||||||
|
win.SetVisible(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
box.Append(the_entry)
|
||||||
|
box.Append(submit)
|
||||||
|
|
||||||
|
win.SetChild(box)
|
||||||
|
win.SetVisible(true)
|
||||||
|
}}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
rb.ConnectClicked(func() {
|
||||||
|
var mu MucUser
|
||||||
|
ok = u.Get(&mu)
|
||||||
|
if ok {
|
||||||
|
if mu.MucUserItem.JID != "" {
|
||||||
|
win := gtk.NewWindow()
|
||||||
|
win.SetDefaultSize(400, 1)
|
||||||
|
win.SetResizable(false)
|
||||||
|
|
||||||
|
box := gtk.NewBox(gtk.OrientationVertical, 0)
|
||||||
|
box.Append(gtk.NewLabel("Set "+JidMustParse(u.From).Resource+"'s role"))
|
||||||
|
box.Append(gtk.NewLabel("Important: if you want this to be permanent, set their affiliation instead"))
|
||||||
|
|
||||||
|
the_entry := gtk.NewEntry()
|
||||||
|
the_entry.SetText(mu.MucUserItem.Role)
|
||||||
|
|
||||||
|
submit := gtk.NewButtonWithLabel("Submit")
|
||||||
|
submit.ConnectClicked(func() {
|
||||||
|
|
||||||
|
client.SendRaw(fmt.Sprintf(`
|
||||||
|
<iq from='%s'
|
||||||
|
id='kick1'
|
||||||
|
to='%s'
|
||||||
|
type='set'>
|
||||||
|
<query xmlns='http://jabber.org/protocol/muc#admin'>
|
||||||
|
<item nick='%s' role='%s'>
|
||||||
|
</item>
|
||||||
|
</query>
|
||||||
|
</iq>
|
||||||
|
`, clientroot.Session.BindJid, jid, JidMustParse(u.From).Resource, the_entry.Text()))
|
||||||
|
})
|
||||||
|
|
||||||
|
box.Append(the_entry)
|
||||||
|
box.Append(submit)
|
||||||
|
|
||||||
|
win.SetChild(box)
|
||||||
|
win.SetVisible(true)
|
||||||
|
}}
|
||||||
|
})
|
||||||
|
|
||||||
|
rc_box.Append(bb)
|
||||||
|
rc_box.Append(kb)
|
||||||
|
rc_box.Append(ab)
|
||||||
|
rc_box.Append(rb)
|
||||||
|
|
||||||
|
popover.SetChild(rc_box)
|
||||||
|
|
||||||
|
mod_gesture.Connect("pressed", func(n_press, x, y int) {
|
||||||
|
rect := gdk.NewRectangle(x, y, 1, 1)
|
||||||
|
popover.SetPointingTo(&rect)
|
||||||
|
popover.Popup()
|
||||||
|
})
|
||||||
|
|
||||||
gesture.Connect("pressed", func(n_press, x, y int) {
|
gesture.Connect("pressed", func(n_press, x, y int) {
|
||||||
win := gtk.NewWindow()
|
win := gtk.NewWindow()
|
||||||
@@ -218,8 +364,13 @@ func switchToTab(jid string, w *gtk.Window) {
|
|||||||
version := ver.Version
|
version := ver.Version
|
||||||
os := ver.OS
|
os := ver.OS
|
||||||
|
|
||||||
ver_text.SetText(fmt.Sprintf("%s %s %s", name, version, os))
|
vr := fmt.Sprintf("%s %s %s", name, version, os)
|
||||||
|
if name == "" && version == "" && os == "" {
|
||||||
|
ver_text.SetText("Client responded with empty version")
|
||||||
|
} else {
|
||||||
|
ver_text.SetText(vr)
|
||||||
ver_text.RemoveCSSClass("visitor")
|
ver_text.RemoveCSSClass("visitor")
|
||||||
|
}
|
||||||
} else if result.Error != nil && result.Error.Type != "" {
|
} else if result.Error != nil && result.Error.Type != "" {
|
||||||
ver_text.SetText("Got error trying to get version")
|
ver_text.SetText("Got error trying to get version")
|
||||||
ver_text.SetTooltipText(result.Error.Reason + ": " + result.Error.Text)
|
ver_text.SetTooltipText(result.Error.Reason + ": " + result.Error.Text)
|
||||||
@@ -262,7 +413,10 @@ func switchToTab(jid string, w *gtk.Window) {
|
|||||||
win.SetTransientFor(win)
|
win.SetTransientFor(win)
|
||||||
win.Present()
|
win.Present()
|
||||||
})
|
})
|
||||||
|
|
||||||
userbox.AddController(gesture)
|
userbox.AddController(gesture)
|
||||||
|
userbox.AddController(mod_gesture)
|
||||||
|
|
||||||
if mu.MucUserItem.Role == "moderator" {
|
if mu.MucUserItem.Role == "moderator" {
|
||||||
gen.Prepend(userbox)
|
gen.Prepend(userbox)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
31
main.go
@@ -1,8 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"context"
|
"context"
|
||||||
@@ -188,8 +188,6 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pretty.Println(m)
|
|
||||||
|
|
||||||
e := stanza.PubSubEvent{}
|
e := stanza.PubSubEvent{}
|
||||||
ok = m.Get(&e)
|
ok = m.Get(&e)
|
||||||
if ok {
|
if ok {
|
||||||
@@ -211,6 +209,15 @@ func main() {
|
|||||||
beeep.Notify("Attention", fmt.Sprintf("%s: %s", JidMustParse(m.From).Resource, m.Body), commentBytes) // TODO: Use localpart if DM
|
beeep.Notify("Attention", fmt.Sprintf("%s: %s", JidMustParse(m.From).Resource, m.Body), commentBytes) // TODO: Use localpart if DM
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle mentions
|
||||||
|
for _, ext := range m.Extensions {
|
||||||
|
mention, ok := ext.(*Mention)
|
||||||
|
if ok {
|
||||||
|
pretty.Println(mention)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
sc := new(SentCarbon)
|
sc := new(SentCarbon)
|
||||||
ok = m.Get(sc)
|
ok = m.Get(sc)
|
||||||
if ok {
|
if ok {
|
||||||
@@ -492,7 +499,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
go func() {
|
conc := func() {
|
||||||
time.Sleep(3 * time.Second)
|
time.Sleep(3 * time.Second)
|
||||||
connectionStatus.SetText("Connecting...")
|
connectionStatus.SetText("Connecting...")
|
||||||
connectionIcon.SetFromPaintable(clientAssets["hourglass"])
|
connectionIcon.SetFromPaintable(clientAssets["hourglass"])
|
||||||
@@ -503,10 +510,13 @@ func main() {
|
|||||||
connectionStatus.SetText(fmt.Sprintf("Disconnected: %s", err.Error()))
|
connectionStatus.SetText(fmt.Sprintf("Disconnected: %s", err.Error()))
|
||||||
connectionIcon.SetFromPaintable(clientAssets["disconnect"])
|
connectionIcon.SetFromPaintable(clientAssets["disconnect"])
|
||||||
}
|
}
|
||||||
}()
|
}
|
||||||
|
|
||||||
app := gtk.NewApplication("net.sunglocto.lambda", gio.ApplicationFlagsNone)
|
app := gtk.NewApplication("net.sunglocto.lambda", gio.ApplicationFlagsNone)
|
||||||
app.ConnectActivate(func() { activate(app) })
|
app.ConnectActivate(func() {
|
||||||
|
go conc()
|
||||||
|
activate(app)
|
||||||
|
})
|
||||||
|
|
||||||
if code := app.Run(os.Args); code > 0 {
|
if code := app.Run(os.Args); code > 0 {
|
||||||
os.Exit(code)
|
os.Exit(code)
|
||||||
@@ -870,8 +880,15 @@ func activate(app *gtk.Application) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(t, "@everyone") {
|
if strings.Contains(t, "@everyone") {
|
||||||
|
start := strings.Index(t, "@everyone")
|
||||||
|
end := start + len("@everyone")
|
||||||
|
|
||||||
new_mention := new(Mention)
|
new_mention := new(Mention)
|
||||||
new_mention.Mentions = "urn:xmpp:mentions:0#channel"
|
new_mention.Type = "urn:xmpp:mentions:0#channel"
|
||||||
|
|
||||||
|
new_mention.Begin = start
|
||||||
|
new_mention.End = end
|
||||||
|
|
||||||
exts = append(exts, new_mention)
|
exts = append(exts, new_mention)
|
||||||
} else if strings.Contains(t, "@here") {
|
} else if strings.Contains(t, "@here") {
|
||||||
new_attention := new(Attention)
|
new_attention := new(Attention)
|
||||||
|
|||||||
4
types.go
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/diamondburned/gotk4/pkg/gtk/v4"
|
"github.com/diamondburned/gotk4/pkg/gtk/v4"
|
||||||
|
"mellium.im/xmpp/color"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,10 +19,11 @@ type lambdaConfig struct {
|
|||||||
Insecure bool
|
Insecure bool
|
||||||
Nick string
|
Nick string
|
||||||
JoinBookmarks bool
|
JoinBookmarks bool
|
||||||
|
CVD color.CVD
|
||||||
}
|
}
|
||||||
|
|
||||||
type mucUnit struct {
|
type mucUnit struct {
|
||||||
// key: OccupantID
|
// key: Resource
|
||||||
// value: last user presence
|
// value: last user presence
|
||||||
Members sync.Map
|
Members sync.Map
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
var lambda_version string = "0.1.0"
|
var lambda_version string = "26w11a"
|
||||||
|
|||||||
@@ -11,12 +11,11 @@ import (
|
|||||||
type Mention struct {
|
type Mention struct {
|
||||||
stanza.MsgExtension
|
stanza.MsgExtension
|
||||||
XMLName xml.Name `xml:"urn:xmpp:mentions:0 mention"`
|
XMLName xml.Name `xml:"urn:xmpp:mentions:0 mention"`
|
||||||
Mentions string `xml:"mentions,attr,omitempty"`
|
|
||||||
URI string `xml:"uri,attr,omitempty"`
|
URI string `xml:"uri,attr,omitempty"`
|
||||||
Begin int `xml:"begin,attr,omitempty"`
|
Begin int `xml:"begin,attr,omitempty"`
|
||||||
End int `xml:"end,attr,omitempty"`
|
End int `xml:"end,attr,omitempty"`
|
||||||
OccupantID string `xml:"occupantid,attr,omitempty"`
|
Type string `xml:"type,attr"`
|
||||||
JID string `xml:"ji,attr,omitempty"`
|
Target string `xml:"target,attr,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||