telegabber/xmpp/omemo_command.go
2026-07-30 08:52:59 -04:00

204 lines
6.9 KiB
Go

package xmpp
import (
"fmt"
"dev.narayana.im/narayana/telegabber/telegram"
"dev.narayana.im/narayana/telegabber/xmpp/gateway"
"gosrc.io/xmpp/stanza"
)
// buildOmemoTransportFormFields builds the initial (no form submitted yet)
// field list for the transport-level "/omemo" ad-hoc command: a single
// list-single field for the account-wide mode, pre-selected to the current
// value. Plugs into handleSetQueryCommand's shared per-command
// field-then-form-then-answer construction, the same way "config"'s own
// field-building does.
func buildOmemoTransportFormFields(session *telegram.Client) []*stanza.Field {
mode, _ := session.Session.Get("omemo")
return []*stanza.Field{
{
Var: "mode",
Label: "OMEMO mode",
Type: stanza.FieldTypeListSingle,
Options: []stanza.Option{
{Label: "Automatic (default)", ValuesList: []string{"auto"}},
{Label: "Always on", ValuesList: []string{"on"}},
{Label: "Always off", ValuesList: []string{"off"}},
},
ValuesList: []string{mode},
},
}
}
// buildOmemoChatFormFields builds the initial field list for the
// chat-scoped "/omemo" ad-hoc command: a read-only effective-status field,
// a list-single override control, and two list-single "block"/"accept"
// fields - each an empty-by-default choice (no selection = no action)
// among the CURRENT opposite-state devices (block's options are the
// currently-trusted devices, accept's are the currently-blocked ones).
// This mirrors handleSetQueryCommand's existing mute/kick/ban/unmute/unban
// field-building exactly (a list-single of opposite-state members with a
// blank leading option), rather than a separate reported/item table plus
// list-multi fields - one form field per action, same as those commands.
func buildOmemoChatFormFields(session *telegram.Client, chatID int64) []*stanza.Field {
status, err := session.GetOmemoChatStatus(chatID)
if err != nil {
return []*stanza.Field{
{Type: stanza.FieldTypeFixed, Label: "Error", ValuesList: []string{err.Error()}},
}
}
overrideValue := ""
if status.OverrideSet {
overrideValue = omemoOnOff(status.Override)
}
fields := []*stanza.Field{
{
Var: "effective",
Label: "Effective right now",
Type: stanza.FieldTypeFixed,
ValuesList: []string{fmt.Sprintf("%s (account-wide mode: %s)", omemoOnOff(status.Effective), status.Mode)},
},
{
Var: "override",
Label: "Override for this chat",
Type: stanza.FieldTypeListSingle,
Options: []stanza.Option{
{Label: "Follow account-wide mode", ValuesList: []string{""}},
{Label: "Always on", ValuesList: []string{"on"}},
{Label: "Always off", ValuesList: []string{"off"}},
},
ValuesList: []string{overrideValue},
},
}
// allow an empty selection ("no action"), same as mute/unmute's own
// blank leading option
blockOptions := []stanza.Option{{ValuesList: []string{""}}}
acceptOptions := []stanza.Option{{ValuesList: []string{""}}}
for _, d := range status.Devices {
option := stanza.Option{Label: d.ID, ValuesList: []string{d.ID}}
if d.Trusted {
blockOptions = append(blockOptions, option)
} else {
acceptOptions = append(acceptOptions, option)
}
}
return append(fields,
&stanza.Field{Var: "block", Label: "Block device", Type: stanza.FieldTypeListSingle, Options: blockOptions},
&stanza.Field{Var: "accept", Label: "Accept (unblock) device", Type: stanza.FieldTypeListSingle, Options: acceptOptions},
)
}
// submitOmemoTransportForm applies a submitted transport-level "/omemo"
// form.
func submitOmemoTransportForm(session *telegram.Client, form *stanza.Form) *stanza.Command {
var mode string
for _, field := range form.Fields {
if field != nil && field.Var == "mode" && len(field.ValuesList) > 0 {
mode = field.ValuesList[0]
}
}
var note stanza.Note
if value, err := session.Session.Set("omemo", mode); err != nil {
note = stanza.Note{Text: err.Error(), Type: stanza.CommandNoteTypeErr}
} else {
gateway.DirtySessions = true
note = stanza.Note{Text: fmt.Sprintf("omemo mode set to %s", value), Type: stanza.CommandNoteTypeInfo}
}
return &stanza.Command{
SessionId: "omemo",
Node: "omemo",
Status: stanza.CommandStatusCompleted,
CommandElements: []stanza.CommandElement{&note},
}
}
// submitOmemoChatForm applies a submitted chat-scoped "/omemo" form: the
// override change (if submitted), then at most one block and one accept
// (each field is a single empty-or-one-device selection, not a multi-pick
// - see buildOmemoChatFormFields) - all in one round trip, since XEP-0050
// single-stage commands only get one.
func submitOmemoChatForm(session *telegram.Client, chatID int64, form *stanza.Form) *stanza.Command {
var overrideValue string
var overrideSubmitted bool
var blockID, acceptID string
for _, field := range form.Fields {
if field == nil {
continue
}
switch field.Var {
case "override":
overrideSubmitted = true
if len(field.ValuesList) > 0 {
overrideValue = field.ValuesList[0]
}
case "block":
if len(field.ValuesList) > 0 {
blockID = field.ValuesList[0]
}
case "accept":
if len(field.ValuesList) > 0 {
acceptID = field.ValuesList[0]
}
}
}
var notes []stanza.CommandElement
if overrideSubmitted {
switch overrideValue {
case "":
if err := session.SetOmemoChatOverride(chatID, nil); err != nil {
notes = append(notes, &stanza.Note{Text: err.Error(), Type: stanza.CommandNoteTypeErr})
} else {
notes = append(notes, &stanza.Note{Text: "OMEMO override cleared for this chat", Type: stanza.CommandNoteTypeInfo})
}
case "on", "off":
on := overrideValue == "on"
if err := session.SetOmemoChatOverride(chatID, &on); err != nil {
notes = append(notes, &stanza.Note{Text: err.Error(), Type: stanza.CommandNoteTypeErr})
} else {
notes = append(notes, &stanza.Note{Text: fmt.Sprintf("OMEMO forced %s for this chat", overrideValue), Type: stanza.CommandNoteTypeInfo})
}
}
}
if blockID != "" {
if err := session.SetOmemoDeviceBlocked(chatID, blockID, true); err != nil {
notes = append(notes, &stanza.Note{Text: fmt.Sprintf("Failed to block device %s: %v", blockID, err), Type: stanza.CommandNoteTypeErr})
} else {
notes = append(notes, &stanza.Note{Text: fmt.Sprintf("Device %s blocked", blockID), Type: stanza.CommandNoteTypeInfo})
}
}
if acceptID != "" {
if err := session.SetOmemoDeviceBlocked(chatID, acceptID, false); err != nil {
notes = append(notes, &stanza.Note{Text: fmt.Sprintf("Failed to accept device %s: %v", acceptID, err), Type: stanza.CommandNoteTypeErr})
} else {
notes = append(notes, &stanza.Note{Text: fmt.Sprintf("Device %s accepted", acceptID), Type: stanza.CommandNoteTypeInfo})
}
}
if len(notes) == 0 {
notes = append(notes, &stanza.Note{Text: "No changes", Type: stanza.CommandNoteTypeInfo})
}
return &stanza.Command{
SessionId: "omemo",
Node: "omemo",
Status: stanza.CommandStatusCompleted,
CommandElements: notes,
}
}
func omemoOnOff(b bool) string {
if b {
return "on"
}
return "off"
}