Rest in peace Dolly Patron

This commit is contained in:
2026-08-25 20:17:23 +01:00
parent 2717137270
commit a6e74ec808
52 changed files with 675 additions and 462 deletions
+5 -1
View File
@@ -2,6 +2,10 @@
an XMPP client
Experimental XMPP client written in Go, with the GTK4 UI toolkit and the Fluux IO XMPP libraries.
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)
additional icons are by Mark James's Silk Icon Set [https://github.com/markjames/famfamfam-silk-icons](https://github.com/markjames/famfamfam-silk-icons) and the [Haiku project](https://www.haiku-os.org/)
pepper & carrot default avatar is from [Pepper&Carrot](https://peppercarrot.com) - created by David Revoy, licensed under Creative Commons Attribution 4.0. Change made: cropped and made black & white
+66 -1
View File
@@ -7,7 +7,9 @@ import (
"sync"
)
//go:embed debug.png
// If you want to change the default profile picture used when Identicons are disabled, you can change the below line to an image in the repository and recompile Lambda.
//go:embed debug_psi.png
var defaultAvatarBytes []byte
//go:embed failed_load.png
@@ -159,6 +161,53 @@ var solarBytes []byte
//go:embed assets/timeline_marker.png
var timelineMarkerBytes []byte
//go:embed assets/cap.png
var capIconBytes []byte
// client icons
//go:embed assets/client_icons/monocles.png
var monoclesCapBytes []byte
//go:embed assets/client_icons/gajim.png
var gajimCapBytes []byte
//go:embed assets/client_icons/conversations.png
var conversationsCapBytes []byte
//go:embed assets/client_icons/dino.png
var dinoCapBytes []byte
//go:embed assets/client_icons/blabber.png
var blabberCapBytes []byte
//go:embed assets/client_icons/profanity.png
var profanityCapBytes []byte
//go:embed assets/client_icons/poezio.png
var poezioCapBytes []byte
//go:embed assets/client_icons/mcabber.png
var mcabberCapBytes []byte
//go:embed assets/client_icons/converse.png
var converseJSCapBytes []byte
//go:embed assets/client_icons/pidgin.png
var pidginCapBytes []byte
//go:embed assets/client_icons/anotherim.png
var anotherImCapBytes []byte
//go:embed assets/client_icons/movim.png
var movimCapBytes []byte
//go:embed assets/client_icons/yaxim.png
var yaximCapBytes []byte
//go:embed assets/client_icons/psi.png
var psiCapBytes []byte
var clientAssets sync.Map
func loadAsset(key string, data []byte) {
@@ -234,6 +283,22 @@ func init() {
"update": updateBytes,
"solar": solarBytes,
"timeline_marker": timelineMarkerBytes,
"cap": capIconBytes,
"https://monocles.eu": monoclesCapBytes,
"https://gajim.org": gajimCapBytes,
"http://conversations.im": conversationsCapBytes,
"https://dino.im": dinoCapBytes,
"http://blabber.im": blabberCapBytes,
"http://profanity-im.github.io": profanityCapBytes,
"https://poez.io": poezioCapBytes,
"http://mcabber.com/caps": mcabberCapBytes,
"https://conversejs.org": converseJSCapBytes,
"http://pidgin.im/": pidginCapBytes,
"https://dev.narayana.im/narayana/Conversations-Classic": anotherImCapBytes,
"https://movim.eu/": movimCapBytes,
"https://yaxim.org/": yaximCapBytes,
"https://psi-im.org": psiCapBytes,
} {
loadAsset(key, data)
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 287 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 783 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 91 KiB

+26
View File
@@ -0,0 +1,26 @@
package stanza
import (
"encoding/xml"
)
// Capabilities
// Reference: https://xmpp.org/extensions/xep-0115.html#stream
// "A server MAY include its entity capabilities in a stream feature element so that connecting clients
// and peer servers do not need to send service discovery requests each time they connect."
// This is not a stream feature but a way to let client cache server disco info.
// Moved from stream_features.go and made it a presence extension as per spec
type Caps struct {
PresExtension
XMLName xml.Name `xml:"http://jabber.org/protocol/caps c"`
Hash string `xml:"hash,attr"`
Node string `xml:"node,attr"`
Ver string `xml:"ver,attr"`
Ext string `xml:"ext,attr,omitempty"`
}
func init() {
TypeRegistry.MapExtension(PKTPresence, xml.Name{Space: "http://jabber.org/protocol/caps", Local: "c"}, Caps{})
}
-13
View File
@@ -42,19 +42,6 @@ func (streamFeatureDecoder) decode(p *xml.Decoder, se xml.StartElement) (StreamF
return packet, err
}
// Capabilities
// Reference: https://xmpp.org/extensions/xep-0115.html#stream
// "A server MAY include its entity capabilities in a stream feature element so that connecting clients
// and peer servers do not need to send service discovery requests each time they connect."
// This is not a stream feature but a way to let client cache server disco info.
type Caps struct {
XMLName xml.Name `xml:"http://jabber.org/protocol/caps c"`
Hash string `xml:"hash,attr"`
Node string `xml:"node,attr"`
Ver string `xml:"ver,attr"`
Ext string `xml:"ext,attr,omitempty"`
}
// ============================================================================
// Supported Stream Features
+167 -79
View File
@@ -4,6 +4,11 @@ import (
"bytes"
"context"
"fmt"
"image"
"image/color"
"image/png"
"strconv"
"github.com/boxes-ltd/imaging"
"github.com/crazy3lf/colorconv"
"github.com/diamondburned/gotk4/pkg/gdk/v4"
@@ -14,20 +19,45 @@ import (
"github.com/google/uuid"
"github.com/rrivera/identicon"
"gosrc.io/xmpp/stanza"
"image"
"image/color"
"image/png"
xmpp_color "mellium.im/xmpp/color"
"strconv"
)
func init() {
func createTabBox(name, jid, av_hash string, removable bool) *gtk.Box {
box := gtk.NewBox(gtk.OrientationHorizontal, 10)
name_label := gtk.NewLabel(name)
left_click_gesture := gtk.NewGestureClick()
left_click_gesture.SetButton(1)
left_click_gesture.Connect("pressed", func() {
switchToTab(jid, &window.Window)
})
box.AddController(left_click_gesture)
if removable {
right_click_gesture := gtk.NewGestureClick()
right_click_gesture.SetButton(3)
right_click_gesture.Connect("pressed", func() {
removeTab(jid, &window.Window)
box.SetVisible(false)
})
box.AddController(right_click_gesture)
}
box.Append(name_label)
image := gtk.NewImage()
image.SetPixelSize(40)
go getAvatar(jid, av_hash, image)
box.Prepend(image)
return box
}
func showUserWindow(u stanza.Presence, custom_nick string) {
var ocu OccupantID
u.Get(&ocu)
var caps stanza.Caps
u.Get(&caps)
var id string
id = JidMustParse(u.From).Resource
@@ -67,7 +97,7 @@ func showUserWindow(u stanza.Presence, custom_nick string) {
}
if loadedConfig.BlockingOI[ocu.ID] {
blocked_note := gtk.NewLabel("You have blocked this user!")
blocked_note := gtk.NewLabel(loadedLocale["blockedUserNotice"])
blocked_note.AddCSSClass("blocked")
profile_box.Prepend(blocked_note)
}
@@ -82,7 +112,7 @@ func showUserWindow(u stanza.Presence, custom_nick string) {
Type: "get",
From: clientroot.Session.BindJid,
To: u.From,
Id: uuid.New().String(),
Id: uuid.NewString(),
Lang: "en",
})
@@ -135,7 +165,7 @@ func showUserWindow(u stanza.Presence, custom_nick string) {
Type: "get",
From: clientroot.Session.BindJid,
To: u.From,
Id: uuid.New().String(),
Id: uuid.NewString(),
Lang: "en",
})
@@ -210,30 +240,43 @@ func showUserWindow(u stanza.Presence, custom_nick string) {
mmmm, ok := mmm.Load(id)
im := gtk.NewImage()
im.SetPixelSize(160)
im.AddCSSClass("author_img")
profile_box.Prepend(im)
createIdenticon(u.From, false, im)
if ok {
pres := mmmm.(stanza.Presence)
var vu VCardUpdate
pres.Get(&vu)
if vu.Photo != "" {
getAvatar(u.From, vu.Photo, im)
im.SetPixelSize(80)
im.AddCSSClass("author_img")
profile_box.Prepend(im)
} else {
createIdenticon(u.From, false, im)
im.SetPixelSize(80)
im.AddCSSClass("author_img")
profile_box.Prepend(im)
}
} else {
createIdenticon(u.From, false, im)
im.SetPixelSize(80)
im.AddCSSClass("author_img")
profile_box.Prepend(im)
}
}()
if caps.Hash != "" {
cap_box := gtk.NewBox(gtk.OrientationHorizontal, 2)
cap_box.SetHAlign(gtk.AlignCenter)
icon := gtk.NewImageFromPaintable(clientAssetsLoad("cap"))
icon.SetCursor(gdk.NewCursorFromName("pointer", nil))
cap_box.Append(icon)
cap_box.Append(gtk.NewLabel(fmt.Sprintf("Entity capabilities: Node %s", caps.Node)))
hasher := gtk.NewLabel(fmt.Sprintf("Using %s hash: %s", caps.Hash, caps.Ver))
hasher.SetVisible(false)
cap_box.Append(hasher)
left_click_gesture := gtk.NewGestureClick()
left_click_gesture.SetButton(1)
left_click_gesture.Connect("pressed", func() {
hasher.SetVisible(!(hasher.Visible()))
})
icon.AddController(left_click_gesture)
profile_box.Append(cap_box)
}
win.SetChild(profile_box)
win.SetTransientFor(win)
win.Present()
@@ -311,37 +354,30 @@ func switchToTab(jid string, w *gtk.Window) {
typed_tab := tab.(*chatTab)
scroller.SetChild(typed_tab.msgs)
typingStatus.SetText("")
createFailBox := func() {
box := gtk.NewBox(gtk.OrientationVertical, 10)
failed_icon := gtk.NewImageFromPaintable(clientAssetsLoad("fail"))
failed_icon.SetPixelSize(100)
box.Append(failed_icon)
jid_label := gtk.NewLabel(jid)
jid_label.AddCSSClass("author")
jid_label.AddCSSClass("jid")
box.Append(jid_label)
label := gtk.NewLabel(loadedLocale["mucLoadError"])
label.SetWrap(true)
box.Append(label)
memberList.SetChild(box)
}
if typed_tab.isMuc {
m, ok := mucmembers.Load(jid)
if !ok {
box := gtk.NewBox(gtk.OrientationVertical, 10)
failed_icon := gtk.NewImageFromPaintable(clientAssetsLoad("fail"))
failed_icon.SetPixelSize(100)
box.Append(failed_icon)
jid_label := gtk.NewLabel(jid)
jid_label.AddCSSClass("author")
jid_label.AddCSSClass("jid")
box.Append(jid_label)
label := gtk.NewLabel("There was an error loading the members of this room. Maybe the MUC does not give user presence, it does not exist, you have been banned from it, or you have to fill a CAPTCHA to access it. Try joining the room again or check if the room exists.")
label.SetWrap(true)
box.Append(label)
memberList.SetChild(box)
createFailBox()
return
}
ma, ok := m.(mucUnit)
if !ok {
box := gtk.NewBox(gtk.OrientationVertical, 10)
failed_icon := gtk.NewImageFromPaintable(clientAssetsLoad("fail"))
failed_icon.SetPixelSize(100)
box.Append(failed_icon)
jid_label := gtk.NewLabel(jid)
jid_label.AddCSSClass("author")
jid_label.AddCSSClass("jid")
box.Append(jid_label)
label := gtk.NewLabel("There was an error loading the members of this room. Maybe the MUC does not give user presence, it does not exist, you have been banned from it, or you have to fill a CAPTCHA to access it. Try joining the room again or check if the room exists.")
label.SetWrap(true)
box.Append(label)
memberList.SetChild(box)
createFailBox()
return
}
mm := ma.Members
@@ -370,11 +406,6 @@ func switchToTab(jid string, w *gtk.Window) {
gen.Append(gtk.NewSeparator(gtk.OrientationHorizontal))
}
//id := ocu.ID
//if id == "" {
// id := JidMustParse(u.From).Resource
//}
nick_label := gtk.NewLabel(JidMustParse(u.From).Resource)
var custom_nick string
@@ -403,7 +434,7 @@ func switchToTab(jid string, w *gtk.Window) {
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.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
@@ -418,7 +449,6 @@ func switchToTab(jid string, w *gtk.Window) {
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))
@@ -452,12 +482,26 @@ func switchToTab(jid string, w *gtk.Window) {
userbox.Append(status_message)
}
end_box := gtk.NewBox(gtk.OrientationHorizontal, 0)
var caps stanza.Caps
ok = u.Get(&caps)
if ok {
client_icon := gtk.NewImageFromPaintable(clientAssetsLoad(caps.Node))
//client_icon.SetHAlign(gtk.AlignEnd)
//client_icon.SetHExpand(true)
end_box.Append(client_icon)
}
medal := gtk.NewImageFromPaintable(clientAssetsLoad(mu.MucUserItem.Affiliation))
medal.SetTooltipText(mu.MucUserItem.Affiliation)
medal.SetHAlign(gtk.AlignEnd)
medal.SetHExpand(true)
userbox.Append(medal)
//medal.SetHAlign(gtk.AlignEnd)
//medal.SetHExpand(true)
end_box.Append(medal)
end_box.SetHAlign(gtk.AlignEnd)
end_box.SetHExpand(true)
userbox.Append(end_box)
if loadedConfig.ShowAvatarsInMemberList {
default_av := gtk.NewImage()
@@ -479,6 +523,52 @@ func switchToTab(jid string, w *gtk.Window) {
}
}
userbox.SetHasTooltip(true)
userbox.Connect("query-tooltip", func(
x int,
y int,
keyboardMode bool,
tooltip *gtk.Tooltip,
) bool {
box := gtk.NewBox(gtk.OrientationHorizontal, 4)
av_box := gtk.NewBox(gtk.OrientationHorizontal, 0)
if loadedConfig.ShowAvatarsInMemberList {
default_av := gtk.NewImage()
default_av.SetPixelSize(100)
createIdenticon(u.From, false, default_av)
av_box.Prepend(default_av)
var vcu VCardUpdate
ok = u.Get(&vcu)
if ok {
photo := vcu.Photo
go func() {
glib.IdleAdd(func() {
new_im := gtk.NewImage()
new_im.SetPixelSize(100)
av_box.Remove(default_av)
av_box.Append(new_im)
go getAvatar(u.From, photo, new_im)
})
}()
}
}
box.Append(av_box)
content_box := gtk.NewBox(gtk.OrientationVertical, 4)
content_box.Append(gtk.NewLabel(u.From))
content_box.Append(gtk.NewLabel(mu.MucUserItem.Role))
content_box.Append(gtk.NewLabel(mu.MucUserItem.Affiliation))
content_box.Append(gtk.NewLabel(mu.MucUserItem.JID))
box.Append(content_box)
tooltip.SetCustom(box)
return true
})
gesture := gtk.NewGestureClick()
gesture.SetButton(1)
@@ -498,7 +588,7 @@ func switchToTab(jid string, w *gtk.Window) {
kb.ConnectClicked(func() {
client.SendRaw(fmt.Sprintf(`
<iq from='%s'
id='kick1'
id='%s'
to='%s'
type='set'>
<query xmlns='http://jabber.org/protocol/muc#admin'>
@@ -506,7 +596,7 @@ func switchToTab(jid string, w *gtk.Window) {
</item>
</query>
</iq>
`, clientroot.Session.BindJid, jid, JidMustParse(u.From).Resource))
`, clientroot.Session.BindJid, uuid.NewString(), jid, JidMustParse(u.From).Resource))
})
bb.ConnectClicked(func() {
@@ -516,14 +606,14 @@ func switchToTab(jid string, w *gtk.Window) {
if mu.MucUserItem.JID != "" {
client.SendRaw(fmt.Sprintf(`
<iq from='%s'
id='ban1'
id='%s'
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()))
`, clientroot.Session.BindJid, uuid.NewString(), jid, JidMustParse(mu.MucUserItem.JID).Bare()))
}
}
})
@@ -547,14 +637,14 @@ func switchToTab(jid string, w *gtk.Window) {
submit.ConnectClicked(func() {
client.SendRaw(fmt.Sprintf(`
<iq from='%s'
id='ba1'
id='%s'
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()))
`, clientroot.Session.BindJid, uuid.NewString(), jid, the_entry.Text(), JidMustParse(mu.MucUserItem.JID).Bare()))
win.Destroy()
})
@@ -588,7 +678,7 @@ func switchToTab(jid string, w *gtk.Window) {
client.SendRaw(fmt.Sprintf(`
<iq from='%s'
id='kick1'
id='%s'
to='%s'
type='set'>
<query xmlns='http://jabber.org/protocol/muc#admin'>
@@ -596,7 +686,7 @@ func switchToTab(jid string, w *gtk.Window) {
</item>
</query>
</iq>
`, clientroot.Session.BindJid, jid, JidMustParse(u.From).Resource, the_entry.Text()))
`, clientroot.Session.BindJid, uuid.NewString(), jid, JidMustParse(u.From).Resource, the_entry.Text()))
})
box.Append(the_entry)
@@ -642,7 +732,13 @@ func switchToTab(jid string, w *gtk.Window) {
} else {
headerBox.Append(gtk.NewImageFromPaintable(clientAssetsLoad("group")))
}
headerBox.Append(gtk.NewLabel(fmt.Sprintf("%d %s", i, loadedLocale["participants"])))
pword := loadedLocale["participants"]
if i == 1 {
pword = loadedLocale["participant"]
}
headerBox.Append(gtk.NewLabel(fmt.Sprintf("%d %s", i, pword)))
gen.Prepend(headerBox)
muc_presence := typed_tab.muc_presence
@@ -717,37 +813,29 @@ func createIdenticon(word string, always_create bool, i *gtk.Image) { // This fu
loader.Close()
p := loader.Pixbuf()
gt := gdk.NewTextureForPixbuf(p)
/*
i := gtk.NewImageFromPaintable(gt)
i.AddCSSClass(loadedConfig.CVD.String() + "_CVD")
return i
*/
glib.IdleAdd(func() {
i.SetFromPaintable(gt)
i.AddCSSClass(loadedConfig.CVD.String() + "_CVD")
})
}
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.SetTitle(loadedLocale["buildAJID"])
win.SetDefaultSize(400, 1)
win.SetResizable(false)
box := gtk.NewBox(gtk.OrientationVertical, 2)
header := gtk.NewLabel("Build-A-JID")
header := gtk.NewLabel(loadedLocale["buildAJID"])
header.AddCSSClass("author")
box.Append(header)
box.Append(gtk.NewLabel("All fields except for domain are optional"))
box.Append(gtk.NewLabel(loadedLocale["buildAJIDOptionalFieldsNote"]))
jid_builder := gtk.NewBox(gtk.OrientationHorizontal, 2)
localPartEntry := gtk.NewEntry()
localPartEntry.SetPlaceholderText("localpart")
localPartEntry.SetPlaceholderText(loadedLocale["BAJlocalpart"])
jid_builder.Append(localPartEntry)
at_sign := gtk.NewLabel("@")
@@ -755,7 +843,7 @@ func jidBuilder(en *gtk.Entry) { // This function spawns a window that allows th
jid_builder.Append(at_sign)
domainEntry := gtk.NewEntry()
domainEntry.SetPlaceholderText("domain")
domainEntry.SetPlaceholderText(loadedLocale["BAJdomain"])
jid_builder.Append(domainEntry)
resource_sign := gtk.NewLabel("/")
@@ -763,7 +851,7 @@ func jidBuilder(en *gtk.Entry) { // This function spawns a window that allows th
jid_builder.Append(resource_sign)
resourceEntry := gtk.NewEntry()
resourceEntry.SetPlaceholderText("resource")
resourceEntry.SetPlaceholderText(loadedLocale["BAJresource"])
jid_builder.Append(resourceEntry)
box.Append(jid_builder)
+105 -62
View File
@@ -81,12 +81,29 @@ func generateMessageWidget(p stanza.Packet, blocked bool, target string) (gtk.Wi
return gtk.NewLabel("Unsupported message."), true
}
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, true
sid := StanzaID{}
m.Get(&sid)
ocu := OccupantID{}
m.Get(&ocu)
store_label := false
var msg *abMessage
tab, ok := tabs.Load(target)
typed_tab, ok2 := tab.(*chatTab)
if ok && ok2 {
store_label = true
msg, ok = typed_tab.msg_refsID[m.Id]
if !ok {
msg = new(abMessage)
typed_tab.msg_refsID[m.Id] = msg
}
msg.ocu_id = ocu.ID
if sid.ID != "" {
typed_tab.msg_refsOSID[sid.ID] = msg
}
}
composing := stanza.StateComposing{}
@@ -108,19 +125,6 @@ func generateMessageWidget(p stanza.Packet, blocked bool, target string) (gtk.Wi
return error_box, true
}
store_label := false
tab, ok := tabs.Load(target)
if ok {
store_label = true
}
typed_tab := tab.(*chatTab)
sid := StanzaID{}
m.Get(&sid)
ocu := OccupantID{}
m.Get(&ocu)
id := JidMustParse(m.From).Resource
name := id
@@ -179,7 +183,7 @@ func generateMessageWidget(p stanza.Packet, blocked bool, target string) (gtk.Wi
<reaction>%s</reaction>
</reactions>
</message>
`, m.To, jid.MustParse(m.From).Bare().String(), uuid.New().String(), m.Type, sid.ID, v))
`, m.To, jid.MustParse(m.From).Bare().String(), uuid.NewString(), m.Type, sid.ID, v))
})
reactions.Append(like)
}
@@ -198,7 +202,7 @@ func generateMessageWidget(p stanza.Packet, blocked bool, target string) (gtk.Wi
<reaction>%s</reaction>
</reactions>
</message>
`, m.To, jid.MustParse(m.From).Bare().String(), uuid.New().String(), m.Type, sid.ID, custom.Text()))
`, m.To, jid.MustParse(m.From).Bare().String(), uuid.NewString(), m.Type, sid.ID, custom.Text()))
})
rc_box.Append(custom)
rc_box.Append(enter_custom)
@@ -234,6 +238,26 @@ func generateMessageWidget(p stanza.Packet, blocked bool, target string) (gtk.Wi
rc_box.Append(vp)
live_edit := gtk.NewButtonWithLabel("Live edit")
live_edit.ConnectClicked(func() {
en := gtk.NewEntry()
en.ConnectChanged(func() {
client.SendRaw(fmt.Sprintf("<message id='%s' to='%s' type='%s'><body>%s</body><replace id='%s' xmlns='urn:xmpp:message-correct:0'/></message>",
uuid.NewString(),
target,
m.Type,
en.Text(),
m.Id,
))
})
win := gtk.NewWindow()
win.SetChild(en)
win.SetVisible(true)
})
rc_box.Append(live_edit)
popover.SetChild(rc_box)
rect := gdk.NewRectangle(x, y, 1, 1)
popover.SetPointingTo(&rect)
@@ -273,18 +297,8 @@ func generateMessageWidget(p stanza.Packet, blocked bool, target string) (gtk.Wi
// TODO: Decouple this from widget generation
if store_label {
msg, ok := typed_tab.msg_refsID[m.Id]
if !ok {
msg = new(abMessage)
typed_tab.msg_refsID[m.Id] = msg
}
msg.label_ref = mlabel
msg.ocu_id = ocu.ID
if sid.ID != "" {
typed_tab.msg_refsOSID[sid.ID] = msg
}
}
} else {
authorBox := gtk.NewBox(gtk.OrientationHorizontal, 10)
@@ -312,10 +326,11 @@ func generateMessageWidget(p stanza.Packet, blocked bool, target string) (gtk.Wi
}
im := gtk.NewImage()
im.SetFromPaintable(clientAssetsLoad("DefaultAvatar"))
// im := gtk.NewImageFromPaintable(clientAssetsLoad("DefaultAvatar"))
go createIdenticon(m.From, false, im)
// im.SetFromPaintable(clientAssetsLoad("DefaultAvatar"))
im.SetPixelSize(40)
im.AddCSSClass("author_img")
// createIdenticon(m.From, false, im)
authorBox.Append(im)
if can_generate {
pres := mmmm.(stanza.Presence)
@@ -325,14 +340,38 @@ func generateMessageWidget(p stanza.Packet, blocked bool, target string) (gtk.Wi
go getAvatar(m.From, vu.Photo, im)
}
} else {
createIdenticon(m.From, false, im)
go createIdenticon(m.From, false, im)
}
} else if m.Type == stanza.MessageTypeChat {
al.SetText(al.Text() + loadedLocale["whispers"])
}
color_ind := gtk.NewLabel("")
ycbcr := xmpp_color.String(id, 255, loadedConfig.CVD)
r, g, b, _ := ycbcr.RGBA()
hexcolor := fmt.Sprintf("#%02x%02x%02x", r, g, b)
color_ind.SetMarkup(fmt.Sprintf("<span foreground='%s'>|</span>", hexcolor))
authorBox.Append(color_ind)
authorBox.Append(al)
oob := stanza.OOB{}
ok = m.Get(&oob)
if ok {
// media := newPictureFromWeb(oob.URL)
// media.SetCanShrink(false)
// mainBox.Append(media)
// media.AddCSSClass("chat_image")
mbtn := gtk.NewButtonWithLabel("🖼️")
// mbtn.SetChild(newImageFromWeb(oob.URL))
mbtn.ConnectClicked(func() {
gopen.Open(oob.URL)
})
authorBox.Append(mbtn)
}
if m.Thread != "" {
im := gtk.NewImage()
createIdenticon(m.Thread, true, im)
@@ -346,6 +385,33 @@ func generateMessageWidget(p stanza.Packet, blocked bool, target string) (gtk.Wi
authorBox.Append(gtk.NewLabel("🎮"))
}
stamp_label := gtk.NewLabel("")
stamp_label.SetHExpand(true)
stamp_label.SetHAlign(gtk.AlignEnd)
stamp_label.AddCSSClass("visitor")
stamp_label.SetText(time.Now().Format("03:04 PM"))
stamp_label.SetTooltipText(time.Now().Format("2006-01-02 15:04:05"))
stamp := new(Delayed_Stamp)
ok = m.Get(stamp)
if ok {
stamped_time, err := stamp.GetStamp()
if err == nil {
stamped_time := *stamped_time
loc := time.Now().Location()
stamped_time = stamped_time.In(loc)
str := stamped_time.Format("03:04 PM")
str_full := stamped_time.Format("2006-01-02 15:04:05")
stamp_label.SetText(str)
stamp_label.SetTooltipText(str_full)
}
}
authorBox.Append(stamp_label)
body := m.Body
body = XEPToPango(body, false)
@@ -361,40 +427,15 @@ func generateMessageWidget(p stanza.Packet, blocked bool, target string) (gtk.Wi
// TODO: Decouple this from widget generation
if store_label {
msg, ok := typed_tab.msg_refsID[m.Id]
if !ok {
msg = new(abMessage)
typed_tab.msg_refsID[m.Id] = msg
}
msg.label_ref = mlabel
msg.ocu_id = ocu.ID
if sid.ID != "" {
typed_tab.msg_refsOSID[sid.ID] = msg
}
msg.read_indicator = gtk.NewBox(gtk.OrientationHorizontal, padding)
authorBox.Append(msg.read_indicator)
}
contentBox.Append(mlabel)
mainBox.Append(authorBox)
mainBox.Append(contentBox)
oob := stanza.OOB{}
ok = m.Get(&oob)
if ok {
// media := newPictureFromWeb(oob.URL)
// media.SetCanShrink(false)
// mainBox.Append(media)
// media.AddCSSClass("chat_image")
mbtn := gtk.NewButtonWithLabel("🖼️")
// mbtn.SetChild(newImageFromWeb(oob.URL))
mbtn.ConnectClicked(func() {
gopen.Open(oob.URL)
})
authorBox.Append(mbtn)
}
if m.Subject != "" {
subjectlabel := gtk.NewLabel(m.Subject)
subjectlabel.SetWrap(true)
@@ -460,6 +501,8 @@ func getVAdjustment(scrolledWindow *gtk.ScrolledWindow) *gtk.Adjustment {
}
func getAvatar(j, hash string, i *gtk.Image) { // TODO: This function probably shouldn't be here, and should probably be in xmpp-helpers or somewhere similar.
time.Sleep(200 * time.Millisecond)
hash = strings.ReplaceAll(hash, "/", "-")
oghash := hash
p, err := ensureCache()
if err != nil {
@@ -492,7 +535,7 @@ func getAvatar(j, hash string, i *gtk.Image) { // TODO: This function probably s
Type: "get",
From: clientroot.Session.BindJid,
To: j,
Id: uuid.New().String(),
Id: uuid.NewString(),
Lang: "en",
})
+4 -1
View File
@@ -2,10 +2,10 @@ package main
import (
"fmt"
"github.com/diamondburned/gotk4/pkg/gtk/v4"
"html"
"regexp"
"strings"
"github.com/diamondburned/gotk4/pkg/gtk/v4"
)
var (
@@ -65,6 +65,9 @@ func XEPToPango(text string, correction bool) string {
}
func GenerateRetraction(lab *gtk.Label, retraction Retraction) {
if lab == nil {
return
}
if retraction.Moderated.OccupantID.ID != "" {
lab.AddCSSClass("moderated")
lab.SetTooltipMarkup(fmt.Sprintf("<span weight='bold'>This message was <span foreground='magenta'>retracted</span> by a moderator.</span>\nReason: '%s'", html.EscapeString(retraction.Reason)))
+27
View File
@@ -2,6 +2,8 @@
package main
import (
"errors"
"gosrc.io/xmpp/stanza"
"sort"
"sync"
)
@@ -23,3 +25,28 @@ func rangeOrdered(m *sync.Map, fn func(k, v any) bool) {
}
}
}
func getPresenceFromJIDMuc(jid *stanza.Jid) (*stanza.Presence, error) {
mo, ok := mucmembers.Load(jid.Bare())
if !ok {
return nil, errors.New("tab is not present in mucmembers")
}
mm, ok := mo.(mucUnit)
if !ok {
return nil, errors.New("mucUnit is nil")
}
mmm := mm.Members
mmmm, ok := mmm.Load(jid.Resource)
if !ok {
return nil, errors.New("user not in room")
}
pres, ok := mmmm.(stanza.Presence)
if !ok {
return nil, errors.New("presence is nil")
}
return &pres, nil
}
+26 -18
View File
@@ -76,24 +76,32 @@ var enGB = map[string]string{ // British English
// 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 ",
"unaffiliated": "Unaffiliated",
"participants": "participant(s)",
"versionQueryEmpty": "You do not have permission to view this client's version",
"versionQueryError": "Got error trying to get version",
"getPastMessages": "Get past messages...",
"mucLoadError": "There was an error loading the members of this room. Maybe the MUC does not give user presence, it does not exist, you have been banned from it, or you have to fill a CAPTCHA to access it. Try joining the room again or check if the room exists.",
"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 ",
"unaffiliated": "Unaffiliated",
"participants": "participants",
"participant": "participant",
"versionQueryEmpty": "You do not have permission to view this client's version",
"versionQueryError": "Got error trying to get version",
"blockedUserNotice": "You have blocked this user!",
"buildAJID": "Build-A-JID",
"buildAJIDOptionalFieldsNote": "All fields except for domain are optional",
"BAJlocalpart": "localpart",
"BAJdomain": "domain",
"BAJresource": "resource",
"away": "Away",
"chat": "Free for chat",
+206 -277
View File
@@ -49,11 +49,6 @@ var mIcon *gtk.Image
var gStatus *gtk.Label
var gIcon *gtk.Image
/*
var sStatus *gtk.Label
var sIcon *gtk.Image
*/
var typingStatus *gtk.Label
var pingStatus *gtk.Label
@@ -93,9 +88,6 @@ func init() {
}
func main() {
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
pingTimes = append(pingTimes, []float64{})
p, err := ensureConfig()
@@ -113,6 +105,12 @@ func main() {
if err != nil {
panic(err)
}
if loadedConfig.Debug {
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
log.Println("pprof debug server running at port 6060")
}
if loadedConfig.Resource == "" {
fmt.Println(loadedLocale["configResourceEmptyWarning"])
@@ -257,6 +255,31 @@ func main() {
return
}
originator := JidMustParse(m.From).Bare()
var typed_tab *chatTab
tab, ok := tabs.Load(originator)
if ok {
typed_tab = tab.(*chatTab)
} else {
if m.Type != stanza.MessageTypeGroupchat {
createTab(originator, false, originator)
tab, ok = tabs.Load(originator)
if ok {
typed_tab = tab.(*chatTab)
glib.IdleAdd(func() {
box := createTabBox(originator, originator, originator, true)
menu.Append(box)
menu.Append(gtk.NewSeparator(gtk.OrientationVertical))
})
} else {
return
}
} else {
return
}
}
oi := new(OccupantID)
ok = m.Get(oi)
if ok {
@@ -276,12 +299,6 @@ func main() {
sid := new(StanzaID)
m.Get(sid)
originator := JidMustParse(m.From).Bare()
glib.IdleAdd(func() {
mStatus.SetText(originator)
})
at := new(Attention)
ok = m.Get(at)
if ok {
@@ -340,6 +357,41 @@ func main() {
return
}
// Handle read indicators
marker := new(Marker)
ok = m.Get(marker)
if ok {
_, ok := typed_tab.msg_refsOSID[marker.ID]
if ok {
im := gtk.NewImage()
im.SetPixelSize(20)
im.SetTooltipText(JidMustParse(m.From).Resource)
glib.IdleAdd(func() {
createIdenticon(m.From, false, im)
})
pres, err := getPresenceFromJIDMuc(JidMustParse(m.From))
if err != nil {
return
}
var vu VCardUpdate
ok = pres.Get(&vu)
if ok {
go getAvatar(m.From, vu.Photo, im)
}
ind, ok := typed_tab.msg_refsOSID[marker.ID]
if ok {
if ind.read_indicator != nil {
ind.read_indicator.Append(im)
}
}
return
}
return
}
// Handle corrections BEFORE attempting to generate a message
correction := new(Correction)
ok = m.Get(correction)
@@ -352,9 +404,11 @@ func main() {
if ok {
if msg.ocu_id == oi.ID && msg.ocu_id != "" {
lab := msg.label_ref
glib.IdleAdd(func() {
lab.SetMarkup(XEPToPango(m.Body, true))
})
if lab != nil {
glib.IdleAdd(func() {
lab.SetMarkup(XEPToPango(m.Body, true))
})
}
}
return
}
@@ -370,15 +424,15 @@ func main() {
if ok {
typed_tab := tab.(*chatTab)
/*
to_retract := ""
refs := typed_tab.msg_refsID
to_retract := ""
refs := typed_tab.msg_refsID
// If it is a DM, then we use regular message ID. If it is a MUC, we use stanza id
to_retract = sid.ID
if typed_tab.isMuc && sid != nil {
// If it is a DM, then we use regular message ID. If it is a MUC, we use stanza id
to_retract = sid.ID
refs = typed_tab.msg_refsOSID
}
if typed_tab.isMuc && sid != nil {
to_retract = sid.ID
refs = typed_tab.msg_refsOSID
}
*/
msg, ok := typed_tab.msg_refsOSID[retraction.ID]
@@ -387,6 +441,7 @@ func main() {
glib.IdleAdd(func() {
GenerateRetraction(lab, *retraction)
})
return
}
}
}
@@ -399,7 +454,10 @@ func main() {
return
}
typed_tab := tab.(*chatTab)
typed_tab, ok := tab.(*chatTab)
if !ok {
return
}
if ok {
if current == JidMustParse(m.From).Bare() {
@@ -413,6 +471,7 @@ func main() {
ba, converted_successfully := untyped_box.(*gtk.Box)
if valid && converted_successfully {
mStatus.SetText(originator)
typed_tab.msgs.Append(b)
b.Append(ba)
b.Append(gtk.NewSeparator(gtk.OrientationVertical))
@@ -439,10 +498,7 @@ func main() {
if ok { // This is a presence stanza from a user in a MUC
presence.Get(&ocu)
// id := ocu.ID
// if id == "" {
id := JidMustParse(presence.From).Resource
// }
from, _ := stanza.NewJid(presence.From)
muc := from.Bare()
_, ok = mucmembers.Load(muc)
@@ -567,7 +623,7 @@ func main() {
pingStatus.AddCSSClass("pending")
before := time.Now()
iq := new(stanza.IQ)
iq.Id = uuid.New().String()
iq.Id = uuid.NewString()
iq.From = clientroot.Session.BindJid
iq.Type = "get"
iq.Payload = &Ping{}
@@ -621,22 +677,8 @@ func main() {
name := "Note To Self"
createTab(jid, false, name)
glib.IdleAdd(func() {
box := gtk.NewBox(gtk.OrientationHorizontal, 10)
b := gtk.NewLabel(name)
gesture1 := gtk.NewGestureClick()
gesture1.SetButton(1)
gesture1.Connect("pressed", func() {
switchToTab(jid, &window.Window)
})
box.Append(b)
im := gtk.NewImage()
im.SetPixelSize(40)
go getAvatar(jid, jid, im)
box.Prepend(im)
box.AddController(gesture1)
menu.Append(box)
b := createTabBox(name, jid, jid, false)
menu.Append(b)
menu.Append(gtk.NewSeparator(gtk.OrientationHorizontal))
})
@@ -666,30 +708,7 @@ func main() {
createTab(jid, false, name)
glib.IdleAdd(func() {
box := gtk.NewBox(gtk.OrientationHorizontal, 10)
b := gtk.NewLabel(name)
gesture1 := gtk.NewGestureClick()
gesture1.SetButton(1)
gesture1.Connect("pressed", func() {
switchToTab(jid, &window.Window)
})
gesture2 := gtk.NewGestureClick()
gesture2.SetButton(3)
gesture2.Connect("pressed", func() {
removeTab(jid, &window.Window)
box.SetVisible(false)
})
im := gtk.NewImage()
im.SetPixelSize(40)
box.Append(im)
box.Append(b)
// TODO: Use PEP avatar and do not use JID as hash
go getAvatar(jid, jid, im)
box.AddController(gesture1)
box := createTabBox(name, jid, jid, false)
menu.Append(box)
menu.Append(gtk.NewSeparator(gtk.OrientationHorizontal))
})
@@ -754,31 +773,7 @@ func main() {
joinMuc(client, clientroot.Session.BindJid, jid, nick, password)
createTab(jid, true, name)
glib.IdleAdd(func() {
box := gtk.NewBox(gtk.OrientationHorizontal, 10)
b := gtk.NewLabel(name)
gesture1 := gtk.NewGestureClick()
gesture1.SetButton(1)
gesture1.Connect("pressed", func() {
switchToTab(jid, &window.Window)
})
gesture2 := gtk.NewGestureClick()
gesture2.SetButton(3)
gesture2.Connect("pressed", func() {
removeTab(jid, &window.Window)
box.SetVisible(false)
})
im := gtk.NewImage()
im.SetPixelSize(40)
box.Append(b)
box.Prepend(im)
go getAvatar(jid, jid, im)
box.AddController(gesture1)
box.AddController(gesture2)
box := createTabBox(name, jid, jid, true)
menu.Append(box)
menu.Append(gtk.NewSeparator(gtk.OrientationHorizontal))
})
@@ -795,31 +790,7 @@ func main() {
joinMuc(client, clientroot.Session.BindJid, jid, nick, "")
createTab(jid, true, jid)
glib.IdleAdd(func() {
box := gtk.NewBox(gtk.OrientationHorizontal, 10)
b := gtk.NewLabel(jid)
gesture1 := gtk.NewGestureClick()
gesture1.SetButton(1)
gesture1.Connect("pressed", func() {
switchToTab(jid, &window.Window)
})
gesture2 := gtk.NewGestureClick()
gesture2.SetButton(3)
gesture2.Connect("pressed", func() {
removeTab(jid, &window.Window)
box.SetVisible(false)
})
im := gtk.NewImage()
im.SetPixelSize(40)
box.Append(b)
box.Prepend(im)
go getAvatar(jid, jid, im)
box.AddController(gesture1)
box.AddController(gesture2)
box := createTabBox(jid, jid, jid, true)
menu.Append(box)
menu.Append(gtk.NewSeparator(gtk.OrientationHorizontal))
})
@@ -891,26 +862,8 @@ func activate(app *gtk.Application) {
submit := gtk.NewButtonWithLabel(loadedLocale["submit"])
submit.ConnectClicked(func() {
t := jid_entry.Text()
n := t
createTab(t, false, n)
box := gtk.NewBox(gtk.OrientationHorizontal, 0)
b := gtk.NewLabel(n)
box.Append(b)
gesture1 := gtk.NewGestureClick()
gesture1.SetButton(1)
gesture1.Connect("pressed", func() {
switchToTab(t, &window.Window)
})
gesture2 := gtk.NewGestureClick()
gesture2.SetButton(3)
gesture2.Connect("pressed", func() {
removeTab(t, &window.Window)
box.SetVisible(false)
})
box.AddController(gesture1)
box.AddController(gesture2)
createTab(t, false, t)
box := createTabBox(t, t, t, true)
menu.Append(box)
menu.Append(gtk.NewSeparator(gtk.OrientationHorizontal))
dm_window.SetVisible(false)
@@ -1007,19 +960,14 @@ func activate(app *gtk.Application) {
if JidMustParse(mu.MucUserItem.JID).Bare() == JidMustParse(clientroot.Session.BindJid).Bare() {
if mu.MucUserItem.Affiliation != "owner" {
box.Append(gtk.NewLabel(loadedLocale["destroyMUCNotOwnerWarning"]))
return false
}
}
}
} else {
panic("not ok")
}
} else {
panic("not ok")
}
return true
})
} else {
panic("not ok")
}
win.SetChild(box)
@@ -1101,31 +1049,7 @@ func activate(app *gtk.Application) {
}
createTab(t, true, n)
box := gtk.NewBox(gtk.OrientationHorizontal, 10)
im := gtk.NewImage()
im.SetPixelSize(40)
go getAvatar(t, t, im)
b := gtk.NewLabel(n)
box.Append(im)
box.Append(b)
gesture1 := gtk.NewGestureClick()
gesture1.SetButton(1)
gesture1.Connect("pressed", func() {
switchToTab(t, &window.Window)
})
gesture2 := gtk.NewGestureClick()
gesture2.SetButton(3)
gesture2.Connect("pressed", func() {
removeTab(t, &window.Window)
box.SetVisible(false)
})
box.AddController(gesture1)
box.AddController(gesture2)
box := createTabBox(n, t, t, true)
menu.Append(box)
menu.Append(gtk.NewSeparator(gtk.OrientationHorizontal))
@@ -1137,7 +1061,6 @@ func activate(app *gtk.Application) {
}
}
if !ok {
if !disco_check.Active() {
jm(t, "")
win.SetVisible(false)
@@ -1152,7 +1075,7 @@ func activate(app *gtk.Application) {
Type: "get",
From: clientroot.Session.BindJid,
To: t,
Id: "dicks",
Id: uuid.NewString(),
Lang: "en",
})
@@ -1163,122 +1086,128 @@ func activate(app *gtk.Application) {
myIQ.Payload = &stanza.DiscoInfo{}
ctx := context.Background()
mychan, err := client.SendIQ(ctx, myIQ)
if err == nil {
result := <-mychan
res, ok = result.Payload.(*stanza.DiscoInfo)
if ok {
if len(res.Identity) >= 1 {
features := res.Features
allowed = false
password_protected := false
password := ""
warning_win := gtk.NewWindow()
warning_win.SetTitle(fmt.Sprintf("%s%s", loadedLocale["joinPreviewTitle"], res.Identity[0].Name))
warning_win.SetDefaultSize(400, 1)
warning_win.SetResizable(false)
buttons := gtk.NewBox(gtk.OrientationHorizontal, 10)
var mychan chan stanza.IQ
//go func() {
mychan, err = client.SendIQ(ctx, myIQ)
if err != nil {
showErrorDialog(err, &window.Window)
}
//}()
join_button := gtk.NewButtonWithLabel("Join")
join_button.ConnectClicked(func() {
warning_win.SetVisible(false)
if password_protected {
allowed = false
result := <-mychan
res, ok = result.Payload.(*stanza.DiscoInfo)
if ok {
if len(res.Identity) >= 1 {
features := res.Features
allowed = false
password_protected := false
password := ""
warning_win := gtk.NewWindow()
warning_win.SetTitle(fmt.Sprintf("%s%s", loadedLocale["joinPreviewTitle"], res.Identity[0].Name))
warning_win.SetDefaultSize(400, 1)
warning_win.SetResizable(false)
password_win := gtk.NewWindow()
password_win.SetTitle(loadedLocale["joinPasswordRequired"])
password_win.SetDefaultSize(400, 1)
password_win.SetResizable(false)
box := gtk.NewBox(gtk.OrientationVertical, 10)
en := gtk.NewPasswordEntry()
submit := gtk.NewButtonWithLabel(loadedLocale["submit"])
submit.ConnectClicked(func() {
password = en.Text()
jm(res.Identity[0].Name, password)
password_win.SetVisible(false)
})
box.Append(en)
box.Append(submit)
password_win.SetChild(box)
password_win.SetVisible(true)
}
buttons := gtk.NewBox(gtk.OrientationHorizontal, 10)
jm(res.Identity[0].Name, password)
})
join_button := gtk.NewButtonWithLabel("Join")
join_button.ConnectClicked(func() {
warning_win.SetVisible(false)
if password_protected {
allowed = false
cancel_button := gtk.NewButtonWithLabel(loadedLocale["cancel"])
cancel_button.ConnectClicked(func() {
warning_win.SetVisible(false)
})
join_button.SetHExpand(true)
cancel_button.SetHExpand(true)
buttons.Append(join_button)
buttons.Append(cancel_button)
warning_box := gtk.NewBox(gtk.OrientationVertical, 10)
header := gtk.NewLabel(res.Identity[0].Name)
warning_box.Append(header)
addFeature := func(icon string, description string) {
box := gtk.NewBox(gtk.OrientationHorizontal, 10)
box.Append(gtk.NewImageFromPaintable(clientAssetsLoad(icon)))
box.Append(gtk.NewLabel(description))
warning_box.Append(box)
password_win := gtk.NewWindow()
password_win.SetTitle(loadedLocale["joinPasswordRequired"])
password_win.SetDefaultSize(400, 1)
password_win.SetResizable(false)
box := gtk.NewBox(gtk.OrientationVertical, 10)
en := gtk.NewPasswordEntry()
submit := gtk.NewButtonWithLabel(loadedLocale["submit"])
submit.ConnectClicked(func() {
password = en.Text()
jm(res.Identity[0].Name, password)
password_win.SetVisible(false)
})
box.Append(en)
box.Append(submit)
password_win.SetChild(box)
password_win.SetVisible(true)
}
for _, feature := range features {
switch feature.Var {
case "muc_passwordprotected":
password_protected = true
addFeature("muc_passwordprotected", loadedLocale["muc_passwordprotected_description"])
case "muc_unsecured":
addFeature("muc_unsecured", loadedLocale["muc_unsecured_description"])
case "muc_membersonly":
addFeature("muc_membersonly", loadedLocale["muc_membersonly_description"])
case "muc_open":
addFeature("muc_open", loadedLocale["muc_open_description"])
case "muc_moderated":
addFeature("muc_moderated", loadedLocale["muc_moderated_description"])
case "muc_unmoderated":
addFeature("muc_unmoderated", loadedLocale["muc_unmoderated_description"])
case "muc_nonanonymous":
addFeature("muc_nonanonymous", loadedLocale["muc_nonanonymous_description"])
case "muc_semianonymous":
addFeature("muc_semianonymous", loadedLocale["muc_semianonymous_description"])
case "muc_persistent":
addFeature("muc_persistent", loadedLocale["muc_persistent_description"])
case "muc_temporary":
addFeature("muc_temporary", loadedLocale["muc_temporary_description"])
case "muc_public":
addFeature("muc_public", loadedLocale["muc_public_description"])
case "muc_hidden":
addFeature("muc_hidden", loadedLocale["muc_hidden_description"])
case "urn:xmpp:mam:0":
addFeature("ok", loadedLocale["urn:xmpp:mam_description"])
case "urn:xmpp:message-moderate:0":
addFeature("moderate", loadedLocale["urn:xmpp:message-moderate_description"])
/*
default:
addFeature("comment", feature.Var)
*/
}
}
jm(res.Identity[0].Name, password)
})
warning_box.Append(buttons)
warning_win.SetChild(warning_box)
warning_win.Present()
} else {
allowed = false
showErrorDialog(fmt.Errorf(loadedLocale["discoFail"]), win)
cancel_button := gtk.NewButtonWithLabel(loadedLocale["cancel"])
cancel_button.ConnectClicked(func() {
warning_win.SetVisible(false)
})
join_button.SetHExpand(true)
cancel_button.SetHExpand(true)
buttons.Append(join_button)
buttons.Append(cancel_button)
warning_box := gtk.NewBox(gtk.OrientationVertical, 10)
header := gtk.NewLabel(res.Identity[0].Name)
warning_box.Append(header)
addFeature := func(icon string, description string) {
box := gtk.NewBox(gtk.OrientationHorizontal, 10)
box.Append(gtk.NewImageFromPaintable(clientAssetsLoad(icon)))
box.Append(gtk.NewLabel(description))
warning_box.Append(box)
}
for _, feature := range features {
switch feature.Var {
case "muc_passwordprotected":
password_protected = true
addFeature("muc_passwordprotected", loadedLocale["muc_passwordprotected_description"])
case "muc_unsecured":
addFeature("muc_unsecured", loadedLocale["muc_unsecured_description"])
case "muc_membersonly":
addFeature("muc_membersonly", loadedLocale["muc_membersonly_description"])
case "muc_open":
addFeature("muc_open", loadedLocale["muc_open_description"])
case "muc_moderated":
addFeature("muc_moderated", loadedLocale["muc_moderated_description"])
case "muc_unmoderated":
addFeature("muc_unmoderated", loadedLocale["muc_unmoderated_description"])
case "muc_nonanonymous":
addFeature("muc_nonanonymous", loadedLocale["muc_nonanonymous_description"])
case "muc_semianonymous":
addFeature("muc_semianonymous", loadedLocale["muc_semianonymous_description"])
case "muc_persistent":
addFeature("muc_persistent", loadedLocale["muc_persistent_description"])
case "muc_temporary":
addFeature("muc_temporary", loadedLocale["muc_temporary_description"])
case "muc_public":
addFeature("muc_public", loadedLocale["muc_public_description"])
case "muc_hidden":
addFeature("muc_hidden", loadedLocale["muc_hidden_description"])
case "urn:xmpp:mam:0":
addFeature("ok", loadedLocale["urn:xmpp:mam_description"])
case "urn:xmpp:message-moderate:0":
addFeature("moderate", loadedLocale["urn:xmpp:message-moderate_description"])
/*
default:
addFeature("comment", feature.Var)
*/
}
}
warning_box.Append(buttons)
warning_win.SetChild(warning_box)
warning_win.Present()
} else {
allowed = false
if result.Error != nil {
showErrorDialog(fmt.Errorf("%s: %s - %s", loadedLocale["discoFail"], result.Error.Reason, result.Error.Text), win)
} else {
showErrorDialog(fmt.Errorf(loadedLocale["discoFail"]), win)
}
showErrorDialog(fmt.Errorf(loadedLocale["discoFail"]), win)
}
} else {
allowed = false
if result.Error != nil {
showErrorDialog(fmt.Errorf("%s: %s - %s", loadedLocale["discoFail"], result.Error.Reason, result.Error.Text), win)
} else {
showErrorDialog(fmt.Errorf(loadedLocale["discoFail"]), win)
}
}
+3
View File
@@ -72,6 +72,9 @@
background-color: red;
}
// a11y
.RedGreen_CVD {
filter: hue-rotate(30deg) saturate(120%) contrast(110%);
}
+3 -2
View File
@@ -8,8 +8,9 @@ import (
)
type abMessage struct {
label_ref *gtk.Label
ocu_id string
label_ref *gtk.Label
ocu_id string
read_indicator *gtk.Box
}
type chatTab struct {
+30
View File
@@ -0,0 +1,30 @@
package main
import (
"encoding/xml"
"gosrc.io/xmpp/stanza"
"time"
)
// Implementation of XEP-0203: Delayed Delivery
// https://xmpp.org/extensions/xep-0203.html
type Delayed_Stamp struct {
XMLName xml.Name `xml:"urn:xmpp:delay delay"`
From string `xml:"from,attr"`
Stamp string `xml:"stamp,attr"`
}
func (d *Delayed_Stamp) GetStamp() (*time.Time, error) {
stamp := d.Stamp
time, err := time.Parse(time.RFC3339, stamp)
if err != nil {
return nil, err
}
return &time, nil
}
func init() {
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{Space: "urn:xmpp:delay", Local: "delay"}, Delayed_Stamp{})
}
+5 -6
View File
@@ -9,17 +9,16 @@ import (
type Retraction struct {
stanza.MsgExtension
XMLName xml.Name `xml:"urn:xmpp:message-retract:1 retract"`
ID string `xml:"id,attr"` // Stanza ID is used in group chats.
Reason string `xml:"reason"`
XMLName xml.Name `xml:"urn:xmpp:message-retract:1 retract"`
ID string `xml:"id,attr"` // Stanza ID is used in group chats.
Reason string `xml:"reason"`
Moderated Moderated `xml:"urn:xmpp:message-moderate:1 moderated"`
}
type Moderated struct {
XMLName xml.Name `xml:"urn:xmpp:message-moderate:1 moderated"`
By string `xml:"by,attr"`
XMLName xml.Name `xml:"urn:xmpp:message-moderate:1 moderated"`
By string `xml:"by,attr"`
OccupantID OccupantID
}
// To whoever thought putting the same element twice in the same message, but in different parents, and not specifying this behaviour anywhere in the XEP was a good idea, I want to kill you. Seriously, I do. If I see you walking home at night I will kill you. Brutally. Slowly. I will relish in the act. I will do it.