|
|
|
@ -3,9 +3,9 @@ package gateway
|
|
|
|
|
import (
|
|
|
|
|
"encoding/xml"
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
"sync"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"dev.narayana.im/narayana/telegabber/badger"
|
|
|
|
|
"dev.narayana.im/narayana/telegabber/xmpp/extensions"
|
|
|
|
@ -23,18 +23,6 @@ type Reply struct {
|
|
|
|
|
End uint64
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type MarkerType byte
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
MarkerTypeReceived MarkerType = iota
|
|
|
|
|
MarkerTypeDisplayed
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type marker struct {
|
|
|
|
|
Type MarkerType
|
|
|
|
|
Id string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const NSNick string = "http://jabber.org/protocol/nick"
|
|
|
|
|
|
|
|
|
|
// Queue stores presences to send later
|
|
|
|
@ -55,34 +43,41 @@ var DirtySessions = false
|
|
|
|
|
var MessageOutgoingPermissionVersion = 0
|
|
|
|
|
|
|
|
|
|
// SendMessage creates and sends a message stanza
|
|
|
|
|
func SendMessage(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, replaceId string, isCarbon, requestReceipt bool) {
|
|
|
|
|
sendMessageWrapper(to, from, body, id, component, reply, nil, "", replaceId, isCarbon, requestReceipt)
|
|
|
|
|
func SendMessage(to, from, body, id string, component *xmpp.Component, reply *Reply, timestamp int64, isCarbon, isGroupchat bool, originalFrom string) {
|
|
|
|
|
sendMessageWrapper(to, from, body, "", "", id, component, reply, timestamp, "", isCarbon, isGroupchat, false, originalFrom, 0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SendServiceMessage creates and sends a simple message stanza from transport
|
|
|
|
|
func SendServiceMessage(to string, body string, component *xmpp.Component) {
|
|
|
|
|
sendMessageWrapper(to, "", body, "", component, nil, nil, "", "", false, false)
|
|
|
|
|
func SendServiceMessage(to, body string, component *xmpp.Component) {
|
|
|
|
|
sendMessageWrapper(to, "", body, "", "", "", component, nil, 0, "", false, false, false, "", 0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SendTextMessage creates and sends a simple message stanza
|
|
|
|
|
func SendTextMessage(to string, from string, body string, component *xmpp.Component) {
|
|
|
|
|
sendMessageWrapper(to, from, body, "", component, nil, nil, "", "", false, false)
|
|
|
|
|
func SendTextMessage(to, from, body string, component *xmpp.Component) {
|
|
|
|
|
sendMessageWrapper(to, from, body, "", "", "", component, nil, 0, "", false, false, false, "", 0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SendErrorMessage creates and sends an error message stanza
|
|
|
|
|
func SendErrorMessage(to, from, text string, code int, isGroupchat bool, component *xmpp.Component) {
|
|
|
|
|
sendMessageWrapper(to, from, "", "", text, "", component, nil, 0, "", false, isGroupchat, false, "", code)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SendErrorMessageWithBody creates and sends an error message stanza with body payload
|
|
|
|
|
func SendErrorMessageWithBody(to, from, body, errorText, id string, code int, isGroupchat bool, component *xmpp.Component) {
|
|
|
|
|
sendMessageWrapper(to, from, body, "", errorText, id, component, nil, 0, "", false, isGroupchat, false, "", code)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SendMessageWithOOB creates and sends a message stanza with OOB URL
|
|
|
|
|
func SendMessageWithOOB(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, oob, replaceId string, isCarbon, requestReceipt bool) {
|
|
|
|
|
sendMessageWrapper(to, from, body, id, component, reply, nil, oob, replaceId, isCarbon, requestReceipt)
|
|
|
|
|
func SendMessageWithOOB(to, from, body, id string, component *xmpp.Component, reply *Reply, timestamp int64, oob string, isCarbon, isGroupchat bool, originalFrom string) {
|
|
|
|
|
sendMessageWrapper(to, from, body, "", "", id, component, reply, timestamp, oob, isCarbon, isGroupchat, false, originalFrom, 0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SendMessageMarker creates and sends a message stanza with a XEP-0333 marker
|
|
|
|
|
func SendMessageMarker(to string, from string, component *xmpp.Component, markerType MarkerType, markerId string) {
|
|
|
|
|
sendMessageWrapper(to, from, "", "", component, nil, &marker{
|
|
|
|
|
Type: markerType,
|
|
|
|
|
Id: markerId,
|
|
|
|
|
}, "", "", false, false)
|
|
|
|
|
// SendSubjectMessage creates and sends a MUC subject
|
|
|
|
|
func SendSubjectMessage(to, from, subject, id string, component *xmpp.Component, timestamp int64) {
|
|
|
|
|
sendMessageWrapper(to, from, "", subject, "", id, component, nil, timestamp, "", false, true, true, "", 0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func sendMessageWrapper(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, marker *marker, oob, replaceId string, isCarbon, requestReceipt bool) {
|
|
|
|
|
func sendMessageWrapper(to, from, body, subject, errorText, id string, component *xmpp.Component, reply *Reply, timestamp int64, oob string, isCarbon, isGroupchat, forceSubject bool, originalFrom string, errorCode int) {
|
|
|
|
|
toJid, err := stanza.NewJid(to)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.WithFields(log.Fields{
|
|
|
|
@ -97,12 +92,17 @@ func sendMessageWrapper(to string, from string, body string, id string, componen
|
|
|
|
|
var logFrom string
|
|
|
|
|
var messageFrom string
|
|
|
|
|
var messageTo string
|
|
|
|
|
if from == "" {
|
|
|
|
|
logFrom = componentJid
|
|
|
|
|
messageFrom = componentJid
|
|
|
|
|
} else {
|
|
|
|
|
if isGroupchat {
|
|
|
|
|
logFrom = from
|
|
|
|
|
messageFrom = from + "@" + componentJid
|
|
|
|
|
messageFrom = from
|
|
|
|
|
} else {
|
|
|
|
|
if from == "" {
|
|
|
|
|
logFrom = componentJid
|
|
|
|
|
messageFrom = componentJid
|
|
|
|
|
} else {
|
|
|
|
|
logFrom = from
|
|
|
|
|
messageFrom = from + "@" + componentJid
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if isCarbon {
|
|
|
|
|
messageTo = messageFrom
|
|
|
|
@ -116,14 +116,51 @@ func sendMessageWrapper(to string, from string, body string, id string, componen
|
|
|
|
|
"to": to,
|
|
|
|
|
}).Warn("Got message")
|
|
|
|
|
|
|
|
|
|
var messageType stanza.StanzaType
|
|
|
|
|
if errorCode != 0 {
|
|
|
|
|
messageType = stanza.MessageTypeError
|
|
|
|
|
} else if isGroupchat {
|
|
|
|
|
messageType = stanza.MessageTypeGroupchat
|
|
|
|
|
} else {
|
|
|
|
|
messageType = stanza.MessageTypeChat
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
message := stanza.Message{
|
|
|
|
|
Attrs: stanza.Attrs{
|
|
|
|
|
From: messageFrom,
|
|
|
|
|
To: messageTo,
|
|
|
|
|
Type: "chat",
|
|
|
|
|
Type: messageType,
|
|
|
|
|
Id: id,
|
|
|
|
|
},
|
|
|
|
|
Body: body,
|
|
|
|
|
Subject: subject,
|
|
|
|
|
Body: body,
|
|
|
|
|
}
|
|
|
|
|
if errorCode != 0 {
|
|
|
|
|
message.Error = stanza.Err{
|
|
|
|
|
Code: errorCode,
|
|
|
|
|
Text: errorText,
|
|
|
|
|
}
|
|
|
|
|
switch errorCode {
|
|
|
|
|
case 400:
|
|
|
|
|
message.Error.Type = stanza.ErrorTypeModify
|
|
|
|
|
message.Error.Reason = "bad-request"
|
|
|
|
|
case 403:
|
|
|
|
|
message.Error.Type = stanza.ErrorTypeAuth
|
|
|
|
|
message.Error.Reason = "forbidden"
|
|
|
|
|
case 404:
|
|
|
|
|
message.Error.Type = stanza.ErrorTypeCancel
|
|
|
|
|
message.Error.Reason = "item-not-found"
|
|
|
|
|
case 406:
|
|
|
|
|
message.Error.Type = stanza.ErrorTypeModify
|
|
|
|
|
message.Error.Reason = "not-acceptable"
|
|
|
|
|
case 500:
|
|
|
|
|
message.Error.Type = stanza.ErrorTypeWait
|
|
|
|
|
message.Error.Reason = "internal-server-error"
|
|
|
|
|
default:
|
|
|
|
|
log.Error("Unknown error code, falling back with empty reason")
|
|
|
|
|
message.Error.Type = stanza.ErrorTypeCancel
|
|
|
|
|
message.Error.Reason = "undefined-condition"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if oob != "" {
|
|
|
|
@ -140,22 +177,35 @@ func sendMessageWrapper(to string, from string, body string, id string, componen
|
|
|
|
|
message.Extensions = append(message.Extensions, extensions.NewReplyFallback(reply.Start, reply.End))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if marker != nil {
|
|
|
|
|
if marker.Type == MarkerTypeReceived {
|
|
|
|
|
message.Extensions = append(message.Extensions, stanza.MarkReceived{ID: marker.Id})
|
|
|
|
|
} else if marker.Type == MarkerTypeDisplayed {
|
|
|
|
|
message.Extensions = append(message.Extensions, stanza.MarkDisplayed{ID: marker.Id})
|
|
|
|
|
message.Extensions = append(message.Extensions, stanza.ReceiptReceived{ID: marker.Id})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if !isCarbon && toJid.Resource != "" {
|
|
|
|
|
if !isGroupchat && !isCarbon && toJid.Resource != "" {
|
|
|
|
|
message.Extensions = append(message.Extensions, stanza.HintNoCopy{})
|
|
|
|
|
}
|
|
|
|
|
if requestReceipt {
|
|
|
|
|
message.Extensions = append(message.Extensions, stanza.Markable{})
|
|
|
|
|
if timestamp != 0 {
|
|
|
|
|
var delayFrom string
|
|
|
|
|
if isGroupchat {
|
|
|
|
|
delayFrom, _, _ = SplitJID(from)
|
|
|
|
|
}
|
|
|
|
|
message.Extensions = append(message.Extensions, extensions.MessageDelay{
|
|
|
|
|
From: delayFrom,
|
|
|
|
|
Stamp: time.Unix(timestamp, 0).UTC().Format(time.RFC3339),
|
|
|
|
|
})
|
|
|
|
|
message.Extensions = append(message.Extensions, extensions.MessageDelayLegacy{
|
|
|
|
|
From: delayFrom,
|
|
|
|
|
Stamp: time.Unix(timestamp, 0).UTC().Format("20060102T15:04:05"),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
if originalFrom != "" {
|
|
|
|
|
message.Extensions = append(message.Extensions, extensions.MessageAddresses{
|
|
|
|
|
Addresses: []extensions.MessageAddress{
|
|
|
|
|
extensions.MessageAddress{
|
|
|
|
|
Type: "ofrom",
|
|
|
|
|
Jid: originalFrom,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
if replaceId != "" {
|
|
|
|
|
message.Extensions = append(message.Extensions, extensions.Replace{Id: replaceId})
|
|
|
|
|
if subject == "" && forceSubject {
|
|
|
|
|
message.Extensions = append(message.Extensions, extensions.EmptySubject{})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if isCarbon {
|
|
|
|
@ -163,7 +213,7 @@ func sendMessageWrapper(to string, from string, body string, id string, componen
|
|
|
|
|
Attrs: stanza.Attrs{
|
|
|
|
|
From: bareTo,
|
|
|
|
|
To: to,
|
|
|
|
|
Type: "chat",
|
|
|
|
|
Type: messageType,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
carbonMessage.Extensions = append(carbonMessage.Extensions, extensions.CarbonSent{
|
|
|
|
@ -275,6 +325,18 @@ var SPResource = args.NewString()
|
|
|
|
|
// SPImmed skips queueing
|
|
|
|
|
var SPImmed = args.NewBool(args.Default(true))
|
|
|
|
|
|
|
|
|
|
// SPMUCAffiliation is a XEP-0045 MUC affiliation
|
|
|
|
|
var SPMUCAffiliation = args.NewString()
|
|
|
|
|
|
|
|
|
|
// SPMUCNick is a XEP-0045 MUC user nick
|
|
|
|
|
var SPMUCNick = args.NewString()
|
|
|
|
|
|
|
|
|
|
// SPMUCJid is a real jid of a MUC member
|
|
|
|
|
var SPMUCJid = args.NewString()
|
|
|
|
|
|
|
|
|
|
// SPMUCStatusCodes is a set of XEP-0045 MUC status codes
|
|
|
|
|
var SPMUCStatusCodes = args.New()
|
|
|
|
|
|
|
|
|
|
func newPresence(bareJid string, to string, args ...args.V) stanza.Presence {
|
|
|
|
|
var presenceFrom string
|
|
|
|
|
if SPFrom.IsSet(args) {
|
|
|
|
@ -330,6 +392,32 @@ func newPresence(bareJid string, to string, args ...args.V) stanza.Presence {
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if SPMUCAffiliation.IsSet(args) {
|
|
|
|
|
affiliation := SPMUCAffiliation.Get(args)
|
|
|
|
|
if affiliation != "" {
|
|
|
|
|
userExt := extensions.PresenceXMucUserExtension{
|
|
|
|
|
Item: extensions.PresenceXMucUserItem{
|
|
|
|
|
Affiliation: affiliation,
|
|
|
|
|
Role: affilationToRole(affiliation),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
if SPMUCNick.IsSet(args) {
|
|
|
|
|
userExt.Item.Nick = SPMUCNick.Get(args)
|
|
|
|
|
}
|
|
|
|
|
if SPMUCJid.IsSet(args) {
|
|
|
|
|
userExt.Item.Jid = SPMUCJid.Get(args)
|
|
|
|
|
}
|
|
|
|
|
if SPMUCStatusCodes.IsSet(args) {
|
|
|
|
|
statusCodes := SPMUCStatusCodes.Get(args).([]uint16)
|
|
|
|
|
for _, statusCode := range statusCodes {
|
|
|
|
|
userExt.Statuses = append(userExt.Statuses, extensions.PresenceXMucUserStatus{
|
|
|
|
|
Code: statusCode,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
presence.Extensions = append(presence.Extensions, userExt)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return presence
|
|
|
|
|
}
|
|
|
|
@ -378,20 +466,6 @@ func SendPresence(component *xmpp.Component, to string, args ...args.V) error {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SPAppendFrom appends numeric from and resource to varargs
|
|
|
|
|
func SPAppendFrom(oldArgs []args.V, id int64) []args.V {
|
|
|
|
|
newArgs := append(oldArgs, SPFrom(strconv.FormatInt(id, 10)))
|
|
|
|
|
newArgs = append(newArgs, SPResource(Jid.Resource))
|
|
|
|
|
return newArgs
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SimplePresence crafts simple presence varargs
|
|
|
|
|
func SimplePresence(from int64, typ string) []args.V {
|
|
|
|
|
args := []args.V{SPType(typ)}
|
|
|
|
|
args = SPAppendFrom(args, from)
|
|
|
|
|
return args
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ResumableSend tries to resume the connection once and sends the packet again
|
|
|
|
|
func ResumableSend(component *xmpp.Component, packet stanza.Packet) error {
|
|
|
|
|
err := component.Send(packet)
|
|
|
|
@ -426,3 +500,13 @@ func SplitJID(from string) (string, string, bool) {
|
|
|
|
|
}
|
|
|
|
|
return fromJid.Bare(), fromJid.Resource, true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func affilationToRole(affilation string) string {
|
|
|
|
|
switch affilation {
|
|
|
|
|
case "owner", "admin":
|
|
|
|
|
return "moderator"
|
|
|
|
|
case "member":
|
|
|
|
|
return "participant"
|
|
|
|
|
}
|
|
|
|
|
return "none"
|
|
|
|
|
}
|
|
|
|
|