Support legacy MAM versions

This commit is contained in:
Bohdan Horbeshko 2025-08-25 10:51:26 -04:00
parent 0f10dbff8c
commit c4341433d6
3 changed files with 305 additions and 40 deletions

View file

@ -376,6 +376,30 @@ type MAM2Query struct {
FlipPage *FlipPage `xml:"flip-page"` FlipPage *FlipPage `xml:"flip-page"`
} }
// MAM1Query is from XEP-0313
type MAM1Query struct {
XMLName xml.Name `xml:"urn:xmpp:mam:1 query"`
Form *stanza.Form `xml:"jabber:x:data x"`
QueryId string `xml:"queryid,attr,omitempty"`
ResultSet *stanza.ResultSet `xml:"set,omitempty"`
}
// MAM0Query is from XEP-0313
type MAM0Query struct {
XMLName xml.Name `xml:"urn:xmpp:mam:0 query"`
Form *stanza.Form `xml:"jabber:x:data x"`
QueryId string `xml:"queryid,attr,omitempty"`
ResultSet *stanza.ResultSet `xml:"set,omitempty"`
}
type MAMQuery interface {
Namespace() string
GetForm() *stanza.Form
GetQueryId() string
GetSet() *stanza.ResultSet
GetFlipPage() *FlipPage
}
// FlipPage is an extended element from XEP-0313 // FlipPage is an extended element from XEP-0313
type FlipPage struct { type FlipPage struct {
XMLName xml.Name `xml:"flip-page"` XMLName xml.Name `xml:"flip-page"`
@ -396,6 +420,22 @@ type MAM2MessageResult struct {
Id string `xml:"id,attr,omitempty"` Id string `xml:"id,attr,omitempty"`
} }
// MAM1MessageResult is from XEP-0313
type MAM1MessageResult struct {
XMLName xml.Name `xml:"urn:xmpp:mam:1 result"`
Forwarded *ForwardedMessage `xml:"urn:xmpp:forward:0 forwarded,omitempty"`
QueryId string `xml:"queryid,attr,omitempty"`
Id string `xml:"id,attr,omitempty"`
}
// MAM0MessageResult is from XEP-0313
type MAM0MessageResult struct {
XMLName xml.Name `xml:"urn:xmpp:mam:0 result"`
Forwarded *ForwardedMessage `xml:"urn:xmpp:forward:0 forwarded,omitempty"`
QueryId string `xml:"queryid,attr,omitempty"`
Id string `xml:"id,attr,omitempty"`
}
// MAM2Fin is from XEP-0313 // MAM2Fin is from XEP-0313
type MAM2Fin struct { type MAM2Fin struct {
XMLName xml.Name `xml:"urn:xmpp:mam:2 fin"` XMLName xml.Name `xml:"urn:xmpp:mam:2 fin"`
@ -404,6 +444,22 @@ type MAM2Fin struct {
Stable bool `xml:"stable,attr"` Stable bool `xml:"stable,attr"`
} }
// MAM1Fin is from XEP-0313
type MAM1Fin struct {
XMLName xml.Name `xml:"urn:xmpp:mam:1 fin"`
ResultSet *stanza.ResultSet `xml:"set,omitempty"`
Complete bool `xml:"complete,attr,omitempty"`
Stable bool `xml:"stable,attr"`
}
// MAM0Fin is from XEP-0313
type MAM0Fin struct {
XMLName xml.Name `xml:"urn:xmpp:mam:0 fin"`
ResultSet *stanza.ResultSet `xml:"set,omitempty"`
Complete bool `xml:"complete,attr,omitempty"`
Stable bool `xml:"stable,attr"`
}
// MAM2Metadata is from XEP-0313 // MAM2Metadata is from XEP-0313
type MAM2Metadata struct { type MAM2Metadata struct {
XMLName xml.Name `xml:"urn:xmpp:mam:2 metadata"` XMLName xml.Name `xml:"urn:xmpp:mam:2 metadata"`
@ -536,11 +592,76 @@ func (c MAM2Query) Namespace() string {
return c.XMLName.Space return c.XMLName.Space
} }
// GetForm obtains the query form
func (c MAM2Query) GetForm() *stanza.Form {
return c.Form
}
// GetQueryId obtains the query id
func (c MAM2Query) GetQueryId() string {
return c.QueryId
}
// GetSet getsets! // GetSet getsets!
func (c MAM2Query) GetSet() *stanza.ResultSet { func (c MAM2Query) GetSet() *stanza.ResultSet {
return c.ResultSet return c.ResultSet
} }
// GetFlipPage obtains the flip-page element
func (c MAM2Query) GetFlipPage() *FlipPage {
return c.FlipPage
}
// Namespace is a namespace!
func (c MAM1Query) Namespace() string {
return c.XMLName.Space
}
// GetForm obtains the query form
func (c MAM1Query) GetForm() *stanza.Form {
return c.Form
}
// GetQueryId obtains the query id
func (c MAM1Query) GetQueryId() string {
return c.QueryId
}
// GetSet getsets!
func (c MAM1Query) GetSet() *stanza.ResultSet {
return c.ResultSet
}
// GetFlipPage is a stub as it's not supported in this MAM version
func (c MAM1Query) GetFlipPage() *FlipPage {
return nil
}
// Namespace is a namespace!
func (c MAM0Query) Namespace() string {
return c.XMLName.Space
}
// GetForm obtains the query form
func (c MAM0Query) GetForm() *stanza.Form {
return c.Form
}
// GetQueryId obtains the query id
func (c MAM0Query) GetQueryId() string {
return c.QueryId
}
// GetSet getsets!
func (c MAM0Query) GetSet() *stanza.ResultSet {
return c.ResultSet
}
// GetFlipPage is a stub as it's not supported in this MAM version
func (c MAM0Query) GetFlipPage() *FlipPage {
return nil
}
// Namespace is a namespace! // Namespace is a namespace!
func (c MAM2Fin) Namespace() string { func (c MAM2Fin) Namespace() string {
return c.XMLName.Space return c.XMLName.Space
@ -551,6 +672,26 @@ func (c MAM2Fin) GetSet() *stanza.ResultSet {
return c.ResultSet return c.ResultSet
} }
// Namespace is a namespace!
func (c MAM1Fin) Namespace() string {
return c.XMLName.Space
}
// GetSet getsets!
func (c MAM1Fin) GetSet() *stanza.ResultSet {
return c.ResultSet
}
// Namespace is a namespace!
func (c MAM0Fin) Namespace() string {
return c.XMLName.Space
}
// GetSet getsets!
func (c MAM0Fin) GetSet() *stanza.ResultSet {
return c.ResultSet
}
// Namespace is a namespace! // Namespace is a namespace!
func (c MAM2Metadata) Namespace() string { func (c MAM2Metadata) Namespace() string {
return c.XMLName.Space return c.XMLName.Space
@ -701,18 +842,54 @@ func init() {
"query", "query",
}, MAM2Query{}) }, MAM2Query{})
// MAM1 query
stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{
"urn:xmpp:mam:1",
"query",
}, MAM1Query{})
// MAM0 query
stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{
"urn:xmpp:mam:0",
"query",
}, MAM0Query{})
// MAM2 message result // MAM2 message result
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{ stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{
"urn:xmpp:mam:2", "urn:xmpp:mam:2",
"result", "result",
}, MAM2MessageResult{}) }, MAM2MessageResult{})
// MAM1 message result
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{
"urn:xmpp:mam:1",
"result",
}, MAM1MessageResult{})
// MAM0 message result
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{
"urn:xmpp:mam:0",
"result",
}, MAM0MessageResult{})
// MAM2 fin // MAM2 fin
stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{ stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{
"urn:xmpp:mam:2", "urn:xmpp:mam:2",
"fin", "fin",
}, MAM2Fin{}) }, MAM2Fin{})
// MAM1 fin
stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{
"urn:xmpp:mam:1",
"fin",
}, MAM1Fin{})
// MAM0 fin
stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{
"urn:xmpp:mam:0",
"fin",
}, MAM0Fin{})
// MAM2 metadata // MAM2 metadata
stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{ stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{
"urn:xmpp:mam:2", "urn:xmpp:mam:2",

View file

@ -50,6 +50,10 @@ const NodeAvatarMetadataNotify string = NodeAvatarMetadata + "+notify"
const NodeAvatarData string = "urn:xmpp:avatar:data" const NodeAvatarData string = "urn:xmpp:avatar:data"
const NSCommand string = "http://jabber.org/protocol/commands" const NSCommand string = "http://jabber.org/protocol/commands"
const NS_MAM2 = "urn:xmpp:mam:2"
const NS_MAM1 = "urn:xmpp:mam:1"
const NS_MAM0 = "urn:xmpp:mam:0"
// Queue stores presences to send later // Queue stores presences to send later
var Queue = make(map[string]*stanza.Presence) var Queue = make(map[string]*stanza.Presence)
var QueueLock = sync.Mutex{} var QueueLock = sync.Mutex{}
@ -437,23 +441,46 @@ func sendMessageWrapper(to, from, body, subject, errorText, id string, component
} }
sendMessage(&privilegeMessage, component) sendMessage(&privilegeMessage, component)
} else if mamQueryId != "" { } else if mamQueryId != "" {
mneVpadluProbrasyvatJoshParametryPoraRefaktorit := strings.Split(mamQueryId, " ")
ns := mneVpadluProbrasyvatJoshParametryPoraRefaktorit[0]
mamQueryId = mneVpadluProbrasyvatJoshParametryPoraRefaktorit[1]
delay := extensions.NewMessageDelay(timestamp, "") delay := extensions.NewMessageDelay(timestamp, "")
forwarded := extensions.ForwardedMessage{
Delay: &delay,
Message: &message,
}
var ext stanza.MsgExtension
switch ns {
case NS_MAM2:
ext = extensions.MAM2MessageResult{
Id: stanzaId,
QueryId: mamQueryId,
Forwarded: &forwarded,
}
case NS_MAM1:
ext = extensions.MAM1MessageResult{
Id: stanzaId,
QueryId: mamQueryId,
Forwarded: &forwarded,
}
case NS_MAM0:
ext = extensions.MAM0MessageResult{
Id: stanzaId,
QueryId: mamQueryId,
Forwarded: &forwarded,
}
}
mamMessage := stanza.Message{ mamMessage := stanza.Message{
Attrs: stanza.Attrs{ Attrs: stanza.Attrs{
From: mucJID, From: mucJID,
To: to, To: to,
Type: messageType, Type: messageType,
}, },
Extensions: []stanza.MsgExtension{ Extensions: []stanza.MsgExtension{ext},
extensions.MAM2MessageResult{
Id: stanzaId,
QueryId: mamQueryId,
Forwarded: &extensions.ForwardedMessage{
Delay: &delay,
Message: &message,
},
},
},
} }
sendMessage(&mamMessage, component) sendMessage(&mamMessage, component)
} else { } else {

View file

@ -85,7 +85,17 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) {
} }
queryMAM2, ok := iq.Payload.(*extensions.MAM2Query) queryMAM2, ok := iq.Payload.(*extensions.MAM2Query)
if ok { if ok {
go handleGetQueryMAM2(s, iq, queryMAM2) go handleGetQueryMAM(s, iq, queryMAM2)
return
}
queryMAM1, ok := iq.Payload.(*extensions.MAM1Query)
if ok {
go handleGetQueryMAM(s, iq, queryMAM1)
return
}
queryMAM0, ok := iq.Payload.(*extensions.MAM0Query)
if ok {
go handleGetQueryMAM(s, iq, queryMAM0)
return return
} }
_, ok = iq.Payload.(*extensions.MAM2Metadata) _, ok = iq.Payload.(*extensions.MAM2Metadata)
@ -116,7 +126,17 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) {
} }
queryMAM2, ok := iq.Payload.(*extensions.MAM2Query) queryMAM2, ok := iq.Payload.(*extensions.MAM2Query)
if ok { if ok {
go handleSetQueryMAM2(s, iq, queryMAM2) go handleSetQueryMAM(s, iq, queryMAM2)
return
}
queryMAM1, ok := iq.Payload.(*extensions.MAM1Query)
if ok {
go handleSetQueryMAM(s, iq, queryMAM1)
return
}
queryMAM0, ok := iq.Payload.(*extensions.MAM0Query)
if ok {
go handleSetQueryMAM(s, iq, queryMAM0)
return return
} }
} else if iq.Type == stanza.IQTypeResult { } else if iq.Type == stanza.IQTypeResult {
@ -976,7 +996,9 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ, di *stanza.DiscoInfo) {
"muc_unsecured", "muc_unsecured",
"http://jabber.org/protocol/muc#stable_id", "http://jabber.org/protocol/muc#stable_id",
"jabber:iq:register", "jabber:iq:register",
"urn:xmpp:mam:2", gateway.NS_MAM0,
gateway.NS_MAM1,
gateway.NS_MAM2,
"urn:xmpp:mam:2#extended", "urn:xmpp:mam:2#extended",
"urn:xmpp:sid:0", "urn:xmpp:sid:0",
"vcard-temp", "vcard-temp",
@ -1382,23 +1404,33 @@ func handleGetQueryMucOwner(s xmpp.Sender, iq *stanza.IQ) {
} }
} }
func handleGetQueryMAM2(s xmpp.Sender, iq *stanza.IQ, query *extensions.MAM2Query) { func handleGetQueryMAM(s xmpp.Sender, iq *stanza.IQ, query extensions.MAMQuery) {
component, answer, ok := iqResultStub(s, iq) component, answer, ok := iqResultStub(s, iq)
if !ok { if !ok {
return return
} }
defer gateway.ResumableSend(component, answer) defer gateway.ResumableSend(component, answer)
payload := &extensions.MAM2Query{} payload2 := &extensions.MAM2Query{}
answer.Payload = payload payload1 := &extensions.MAM1Query{}
payload0 := &extensions.MAM0Query{}
ns := query.Namespace()
switch ns {
case gateway.NS_MAM2:
answer.Payload = payload2
case gateway.NS_MAM1:
answer.Payload = payload1
case gateway.NS_MAM0:
answer.Payload = payload0
}
payload.Form = &stanza.Form{ form := stanza.Form{
Type: stanza.FormTypeForm, Type: stanza.FormTypeForm,
Fields: []*stanza.Field{ Fields: []*stanza.Field{
&stanza.Field{ &stanza.Field{
Var: "FORM_TYPE", Var: "FORM_TYPE",
Type: stanza.FieldTypeHidden, Type: stanza.FieldTypeHidden,
ValuesList: []string{"urn:xmpp:mam:2"}, ValuesList: []string{ns},
}, },
&stanza.Field{ &stanza.Field{
Var: "with", Var: "with",
@ -1426,6 +1458,9 @@ func handleGetQueryMAM2(s xmpp.Sender, iq *stanza.IQ, query *extensions.MAM2Quer
}, },
}, },
} }
payload2.Form = &form
payload1.Form = &form
payload0.Form = &form
log.Debugf("MAM info request: %#v", query) log.Debugf("MAM info request: %#v", query)
} }
@ -2154,7 +2189,7 @@ func handleSetQueryMucOwner(s xmpp.Sender, iq *stanza.IQ, query *extensions.Quer
} }
func handleSetQueryMAM2(s xmpp.Sender, iq *stanza.IQ, query *extensions.MAM2Query) { func handleSetQueryMAM(s xmpp.Sender, iq *stanza.IQ, query extensions.MAMQuery) {
component, answer, ok := iqResultStub(s, iq) component, answer, ok := iqResultStub(s, iq)
if !ok { if !ok {
return return
@ -2194,9 +2229,13 @@ func handleSetQueryMAM2(s xmpp.Sender, iq *stanza.IQ, query *extensions.MAM2Quer
var rsmLimit int32 var rsmLimit int32
var justCount bool var justCount bool
log.Debugf("MAM query to %v: %#v %#v %#v", toID, query, query.Form, query.ResultSet) form := query.GetForm()
if query.Form != nil && query.Form.Type == stanza.FormTypeSubmit { queryRs := query.GetSet()
for _, field := range query.Form.Fields { ns := query.Namespace()
log.Debugf("MAM query to %v: %#v %#v %#v", toID, query, form, queryRs)
if form != nil && form.Type == stanza.FormTypeSubmit {
for _, field := range form.Fields {
if len(field.ValuesList) < 1 { if len(field.ValuesList) < 1 {
iqAnswerSetError(answer, 400) iqAnswerSetError(answer, 400)
return return
@ -2207,7 +2246,7 @@ func handleSetQueryMAM2(s xmpp.Sender, iq *stanza.IQ, query *extensions.MAM2Quer
switch field.Var { switch field.Var {
case "FORM_TYPE": case "FORM_TYPE":
if value != "urn:xmpp:mam:2" { if value != ns {
iqAnswerSetError(answer, 400) iqAnswerSetError(answer, 400)
return return
} }
@ -2246,22 +2285,22 @@ func handleSetQueryMAM2(s xmpp.Sender, iq *stanza.IQ, query *extensions.MAM2Quer
} }
} }
if query.ResultSet != nil { if queryRs != nil {
if query.ResultSet.After != nil { if queryRs.After != nil {
rsmAfter, ok = parseMessageId(*query.ResultSet.After) rsmAfter, ok = parseMessageId(*queryRs.After)
if !ok { if !ok {
iqAnswerSetError(answer, 400) iqAnswerSetError(answer, 400)
return return
} }
log.Debugf("MAM RSM after: %v", rsmAfter) log.Debugf("MAM RSM after: %v", rsmAfter)
} }
if query.ResultSet.Before != nil { if queryRs.Before != nil {
before := *query.ResultSet.Before before := *queryRs.Before
if before == "" { if before == "" {
rsmLastPage = true rsmLastPage = true
log.Debugf("MAM RSM last page") log.Debugf("MAM RSM last page")
} else { } else {
rsmBefore, ok = parseMessageId(*query.ResultSet.Before) rsmBefore, ok = parseMessageId(*queryRs.Before)
if !ok { if !ok {
iqAnswerSetError(answer, 400) iqAnswerSetError(answer, 400)
return return
@ -2269,23 +2308,23 @@ func handleSetQueryMAM2(s xmpp.Sender, iq *stanza.IQ, query *extensions.MAM2Quer
log.Debugf("MAM RSM before: %v", rsmBefore) log.Debugf("MAM RSM before: %v", rsmBefore)
} }
} }
if query.ResultSet.Max != nil { if queryRs.Max != nil {
rsmLimit = int32(*query.ResultSet.Max) rsmLimit = int32(*queryRs.Max)
if rsmLimit == 0 { if rsmLimit == 0 {
justCount = true justCount = true
} }
log.Debugf("MAM RSM max: %v", rsmLimit) log.Debugf("MAM RSM max: %v", rsmLimit)
} }
if query.ResultSet.First != nil { if queryRs.First != nil {
iqAnswerSetError(answer, 400) iqAnswerSetError(answer, 400)
return return
} }
if query.ResultSet.Index != nil { if queryRs.Index != nil {
iqAnswerSetError(answer, 501) iqAnswerSetError(answer, 501)
return return
} }
if query.ResultSet.Last != nil { if queryRs.Last != nil {
iqAnswerSetError(answer, 400) iqAnswerSetError(answer, 400)
return return
} }
@ -2317,7 +2356,7 @@ func handleSetQueryMAM2(s xmpp.Sender, iq *stanza.IQ, query *extensions.MAM2Quer
fromStart := true fromStart := true
var overallyFirstMessageId, overallyLastMessageId int64 var overallyFirstMessageId, overallyLastMessageId int64
reverse := query.FlipPage != nil reverse := query.GetFlipPage() != nil
if ids != nil { if ids != nil {
for _, sId := range ids { for _, sId := range ids {
@ -2532,15 +2571,37 @@ func handleSetQueryMAM2(s xmpp.Sender, iq *stanza.IQ, query *extensions.MAM2Quer
} }
log.Debugf("obtained %v messages", len(messages)) log.Debugf("obtained %v messages", len(messages))
// ty zhe lopnesh, detochka
queryId := ns + " " + query.GetQueryId()
for _, message := range messages { for _, message := range messages {
session.SendDelayedMUCMessage(toID, message, iq.From, query.QueryId) session.SendDelayedMUCMessage(toID, message, iq.From, queryId)
} }
rs := stanza.ResultSet{} rs := stanza.ResultSet{}
answer.Payload = &extensions.MAM2Fin{ switch ns {
ResultSet: &rs, case gateway.NS_MAM2:
Complete: complete, answer.Payload = &extensions.MAM2Fin{
Stable: false, ResultSet: &rs,
Complete: complete,
Stable: false,
}
case gateway.NS_MAM1:
answer.Payload = &extensions.MAM1Fin{
ResultSet: &rs,
Complete: complete,
Stable: false,
}
case gateway.NS_MAM0:
answer.Payload = &extensions.MAM0Fin{
ResultSet: &rs,
Complete: complete,
Stable: false,
}
}
if answer.Payload == nil {
log.Error("Unknown MAM version")
return
} }
if beyond { if beyond {