diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..d380d49 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +im gonna make my own xmpp client with blackjack and hookers diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..f41d112 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +i farted diff --git a/TODO.md b/TODO.md index c8baaeb..6c7fb73 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,4 @@ # TODO -- XEP-0393: Message Styling 0% -- XEP-0402: PEP Native Bookmarks 50% (often lags out client if enabled) - XEP-0461: Message Replies 0% - XEP-0444: Message Reactions partial diff --git a/cache.go b/cache.go index 4de8f29..d1210fd 100644 --- a/cache.go +++ b/cache.go @@ -71,10 +71,10 @@ func newImageFromPath(path string) (*gtk.Image, error) { return gtk.NewImageFromPaintable(tex), nil } -func setImageFromPath(path string, i *gtk.Image) (error) { +func setImageFromPath(path string, i *gtk.Image) error { tex, err := getTexture(path) if err != nil { - return err + return err } i.SetFromPaintable(tex) diff --git a/gtk-helpers.go b/gtk-helpers.go index 2733833..28056b7 100644 --- a/gtk-helpers.go +++ b/gtk-helpers.go @@ -718,9 +718,9 @@ func createIdenticon(word string, always_create bool, i *gtk.Image) { // This fu p := loader.Pixbuf() gt := gdk.NewTextureForPixbuf(p) /* - i := gtk.NewImageFromPaintable(gt) - i.AddCSSClass(loadedConfig.CVD.String() + "_CVD") - return i + i := gtk.NewImageFromPaintable(gt) + i.AddCSSClass(loadedConfig.CVD.String() + "_CVD") + return i */ i.SetFromPaintable(gt) diff --git a/gtk-message.go b/gtk-message.go index 0d46c20..a8adb72 100644 --- a/gtk-message.go +++ b/gtk-message.go @@ -307,9 +307,9 @@ func generateMessageWidget(p stanza.Packet, blocked bool) (gtk.Widgetter, bool) } else { createIdenticon(m.From, false, im) /* - im.SetPixelSize(40) - im.AddCSSClass("author_img") - authorBox.Append(im) + im.SetPixelSize(40) + im.AddCSSClass("author_img") + authorBox.Append(im) */ } } else if m.Type == stanza.MessageTypeChat { @@ -464,16 +464,18 @@ func getAvatar(j, hash string, i *gtk.Image) { // TODO: This function probably s _, err = os.ReadFile(hash) if err == nil { /* - i, err = newImageFromPath(hash) - if err != nil { - invalidImages.Store(oghash, true) - createIdenticon(j, false, i) - } + i, err = newImageFromPath(hash) + if err != nil { + invalidImages.Store(oghash, true) + createIdenticon(j, false, i) + } */ - setImageFromPath(hash, i) + glib.IdleAdd(func() { + setImageFromPath(hash, i) + }) return - } + } iqResp, err := stanza.NewIQ(stanza.Attrs{ Type: "get", @@ -519,12 +521,12 @@ func getAvatar(j, hash string, i *gtk.Image) { // TODO: This function probably s } glib.IdleAdd(func() { - err = setImageFromPath(hash, i) - if err != nil { - invalidImages.Store(oghash, true) - createIdenticon(j, false, i) - } + err = setImageFromPath(hash, i) + if err != nil { + invalidImages.Store(oghash, true) + createIdenticon(j, false, i) + } - i.AddCSSClass(loadedConfig.CVD.String() + "_CVD") + i.AddCSSClass(loadedConfig.CVD.String() + "_CVD") }) } diff --git a/gtk-xmpp-markup.go b/gtk-xmpp-markup.go index 2bf57f8..0f8acaf 100644 --- a/gtk-xmpp-markup.go +++ b/gtk-xmpp-markup.go @@ -15,15 +15,27 @@ var ( ) func XEPToPango(text string) string { - format := func(s string) string { + formatInline := func(s string) string { s = html.EscapeString(s) s = boldRE.ReplaceAllString(s, "$1") s = italicRE.ReplaceAllString(s, "$1") s = strikeRE.ReplaceAllString(s, "$1") - s = codeRE.ReplaceAllString(s, "$1") + s = codeRE.ReplaceAllString(s, `$1`) return s } + + format := func(s string) string { + lines := strings.Split(s, "\n") + + for i, line := range lines { + + lines[i] = formatInline(line) + } + + return strings.Join(lines, "\n") + } + var b strings.Builder last := 0 diff --git a/main.go b/main.go index 69d6cea..19c8186 100644 --- a/main.go +++ b/main.go @@ -82,6 +82,9 @@ var pingTimes = [][]float64{} var xmlLog *os.File +// Basic roster +var Roster = make(map[string]bool) + func init() { beeep.AppName = loadedLocale["appName"] @@ -98,7 +101,6 @@ func init() { } func main() { - pingTimes = append(pingTimes, []float64{}) p, err := ensureConfig() if err != nil { @@ -132,6 +134,11 @@ func main() { xmlLog = os.Stdout } + res := "/" + loadedConfig.Resource + if loadedConfig.RandomizeResource { + res = "" + } + config := xmpp.Config{ TransportConfiguration: xmpp.TransportConfiguration{ Address: loadedConfig.Server, @@ -141,7 +148,7 @@ func main() { ConnectTimeout: 300, TLSConfig: &tls.Config{InsecureSkipVerify: loadedConfig.Insecure}, }, - Jid: loadedConfig.Username + "/" + loadedConfig.Resource, + Jid: loadedConfig.Username + res, Credential: xmpp.Password(loadedConfig.Password), Insecure: loadedConfig.Insecure, StreamManagementEnable: true, @@ -161,6 +168,29 @@ func main() { panic(err) } + // All requests from either ourselves or our server will always be accepted regardless of user settings. + + + if !(loadedConfig.DiscoPrivacy == 0) && !((JidMustParse(iq.From).Bare() == JidMustParse(clientroot.Session.BindJid).Bare()) || (JidMustParse(iq.From).Domain == JidMustParse(clientroot.Session.BindJid).Domain)) { + if loadedConfig.DiscoPrivacy == None || (loadedConfig.DiscoPrivacy == OnlyRoster && !Roster[JidMustParse(iq.From).Bare()]) { + fmt.Println("blocking", loadedConfig.DiscoPrivacy) + if loadedConfig.IgnoreInsteadOfReturnErrorUponPrivacyViolation { + return + } + + iqResp.Type = "error" + + stanza_err := new(stanza.Err) + stanza_err.Type = stanza.ErrorTypeCancel + stanza_err.Text = "service-unavailable" + + iqResp.MakeError(*stanza_err) + s.Send(iqResp) + return + } + } + + identity := stanza.Identity{ Name: "Lambda", Category: "client", // TODO: Allow spoofing on user request @@ -182,6 +212,8 @@ func main() { {Var: "urn:xmpp:attention:0"}, {Var: "urn:xmpp:carbons:2"}, {Var: "urn:xmpp:ping"}, + {Var: "jabber:iq:oob"}, + {Var: "jabber:x:oob"}, }, } iqResp.Payload = &payload @@ -192,7 +224,6 @@ func main() { iq, ok := p.(*stanza.IQ) if !ok || iq.Type != stanza.IQTypeGet { return - } iqResp, err := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id}) @@ -200,6 +231,27 @@ func main() { panic(err) } + // All requests from either ourselves or our server will always be accepted regardless of user settings. + + if !(loadedConfig.VersionPrivacy == 0) && !((JidMustParse(iq.From).Bare() == JidMustParse(clientroot.Session.BindJid).Bare()) || (JidMustParse(iq.From).Domain == JidMustParse(clientroot.Session.BindJid).Domain)) { + if loadedConfig.VersionPrivacy == None || (loadedConfig.VersionPrivacy == OnlyRoster && !Roster[JidMustParse(iq.From).Bare()]) { + fmt.Println("blocking", loadedConfig.DiscoPrivacy) + if loadedConfig.IgnoreInsteadOfReturnErrorUponPrivacyViolation { + return + } + + iqResp.Type = "error" + + stanza_err := new(stanza.Err) + stanza_err.Type = stanza.ErrorTypeCancel + stanza_err.Text = "service-unavailable" + + iqResp.MakeError(*stanza_err) + s.Send(iqResp) + return + } + } + v := &stanza.Version{} v = v.SetInfo(loadedLocale["appName"], lambda_version, runtime.GOOS) // TODO: Allow spoofing on user request @@ -484,37 +536,6 @@ func main() { } }() - // Throughput - /* - var oldsize int64 - var newsize int64 - var diff float64 - go func() { - for { - time.Sleep(5 * time.Second) - stat, err := xmlLog.Stat() - if err != nil { - panic(err) - } - - newsize = stat.Size() - diff = float64(newsize-oldsize) / 1000 - - ic := clientAssets["car"] - if diff >= 25 { - ic = clientAssets["car_high"] - } - - glib.IdleAdd(func() { - sStatus.SetText(fmt.Sprintf("%.2f%s", diff, loadedLocale["KBPerSecond"])) - sIcon.SetFromPaintable(ic) - }) - - oldsize = newsize - } - }() - */ - glib.IdleAdd(func() { connectionStatus.SetText(fmt.Sprintf("%s%s", loadedLocale["connectedAs"], JidMustParse(clientroot.Session.BindJid).Bare())) connectionStatus.SetTooltipText(fmt.Sprintf("%s%s\n%s%t", loadedLocale["bindedJid"], clientroot.Session.BindJid, loadedLocale["usingTLS"], clientroot.Session.TlsEnabled)) @@ -583,6 +604,7 @@ func main() { for _, v := range items.Items { name := v.Name jid := v.Jid + Roster[jid] = true if name == "" { name = jid @@ -613,13 +635,13 @@ func main() { go getAvatar(jid, jid, im) /* - go func() { - new_im := getAvatar(jid, jid) // TODO: Use PEP avatar and do not use JID as hash - glib.IdleAdd(func() { - new_im.SetPixelSize(40) - box.Prepend(new_im) - }) - }() + go func() { + new_im := getAvatar(jid, jid) // TODO: Use PEP avatar and do not use JID as hash + glib.IdleAdd(func() { + new_im.SetPixelSize(40) + box.Prepend(new_im) + }) + }() */ box.AddController(gesture1) @@ -710,13 +732,13 @@ func main() { go getAvatar(jid, jid, im) /* - go func() { - new_im := getAvatar(jid, jid) - glib.IdleAdd(func() { - new_im.SetPixelSize(40) - box.Prepend(new_im) - }) - }() + go func() { + new_im := getAvatar(jid, jid) + glib.IdleAdd(func() { + new_im.SetPixelSize(40) + box.Prepend(new_im) + }) + }() */ box.AddController(gesture1) @@ -1014,13 +1036,13 @@ func activate(app *gtk.Application) { go getAvatar(t, t, im) /* - go func() { - new_im := getAvatar(t, t) - glib.IdleAdd(func() { - new_im.SetPixelSize(40) - box.Prepend(new_im) - }) - }() + go func() { + new_im := getAvatar(t, t) + glib.IdleAdd(func() { + new_im.SetPixelSize(40) + box.Prepend(new_im) + }) + }() */ b := gtk.NewLabel(n) @@ -1208,7 +1230,7 @@ func activate(app *gtk.Application) { the_menuBar := gtk.NewPopoverMenuBarFromModel(the_menu) app.SetMenubar(gio.NewMenu()) - window.SetTitle("Lambda") + window.SetTitle(loadedLocale["appName"]) window.Window.AddCSSClass("ssd") window.Window.SetDefaultSize(500, 500) menu = gtk.NewBox(gtk.OrientationVertical, 0) diff --git a/types.go b/types.go index d1d0b90..505ae1b 100644 --- a/types.go +++ b/types.go @@ -18,9 +18,17 @@ type chatTab struct { type BlockingMode int const ( - Drop BlockingMode = iota - Hide - Highlight + Drop BlockingMode = iota // Do not even process if the entity is blocked + Hide // Hide the message but allow the user to expand it if they wish (discord style) + Highlight // Just highlight the message in orange +) + +type DiscoveryMode int + +const ( + Everyone DiscoveryMode = iota // All entities can query + OnlyRoster // Only people on your roster can query + None // Nobody can query ) type lambdaConfig struct { @@ -48,6 +56,18 @@ type lambdaConfig struct { BlockingMode BlockingMode CheckUpdate string + + RandomizeResource bool // If this is set to true, then the resource will be completely randomized by the server on every single connect + + // Privacy settings + DiscoPrivacy DiscoveryMode // Whether or not Lambda will report its service discovery features and Identity. Reccomended mode is Everyone, Default is Everyone + VersionPrivacy DiscoveryMode // Whether or not Lambda will report its version information. Reccomended mode is Roster, Default is Everyone + + IgnoreInsteadOfReturnErrorUponPrivacyViolation bool // If set to true, Lambda will simply ignore Disco and Query Version requests from entities that are untrusted as per the user's discovery mode. If set to false, a service-unavailable error will be reported. + + // Spoofing + + // TODO } type mucUnit struct { diff --git a/version.go b/version.go index 3307953..feb7fed 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package main -var lambda_version string = "6" +var lambda_version string = "7"