Simulate carbons
This commit is contained in:
parent
90807b2d9e
commit
42ed16bf9e
31
README.md
31
README.md
|
@ -142,3 +142,34 @@ server {
|
|||
```
|
||||
|
||||
Finally, update `:upload:` in your config.yml to match `server_name` in nginx config.
|
||||
|
||||
### Carbons ###
|
||||
|
||||
Telegabber needs special privileges according to XEP-0356 to simulate message carbons from the users (to display messages they have sent earlier or via other clients). Example configuration for Prosody:
|
||||
|
||||
```
|
||||
modules_enabled = {
|
||||
[...]
|
||||
|
||||
"privilege";
|
||||
}
|
||||
|
||||
[...]
|
||||
|
||||
Component "telegabber.yourdomain.tld"
|
||||
component_secret = "yourpassword"
|
||||
modules_enabled = {"privilege"}
|
||||
|
||||
[...]
|
||||
|
||||
VirtualHost "yourdomain.tld"
|
||||
[...]
|
||||
|
||||
privileged_entities = {
|
||||
[...]
|
||||
|
||||
["telegabber.yourdomain.tld"] = {
|
||||
message = "outgoing";
|
||||
},
|
||||
}
|
||||
```
|
||||
|
|
|
@ -40,6 +40,7 @@ type Session struct {
|
|||
RawMessages bool `yaml:":rawmessages"`
|
||||
AsciiArrows bool `yaml:":asciiarrows"`
|
||||
OOBMode bool `yaml:":oobmode"`
|
||||
Carbons bool `yaml:":carbons"`
|
||||
}
|
||||
|
||||
var configKeys = []string{
|
||||
|
@ -48,6 +49,7 @@ var configKeys = []string{
|
|||
"rawmessages",
|
||||
"asciiarrows",
|
||||
"oobmode",
|
||||
"carbons",
|
||||
}
|
||||
|
||||
var sessionDB *SessionsYamlDB
|
||||
|
@ -122,6 +124,8 @@ func (s *Session) Get(key string) (string, error) {
|
|||
return fromBool(s.AsciiArrows), nil
|
||||
case "oobmode":
|
||||
return fromBool(s.OOBMode), nil
|
||||
case "carbons":
|
||||
return fromBool(s.Carbons), nil
|
||||
}
|
||||
|
||||
return "", errors.New("Unknown session property")
|
||||
|
@ -172,6 +176,13 @@ func (s *Session) Set(key string, value string) (string, error) {
|
|||
}
|
||||
s.OOBMode = b
|
||||
return value, nil
|
||||
case "carbons":
|
||||
b, err := toBool(value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
s.Carbons = b
|
||||
return value, nil
|
||||
}
|
||||
|
||||
return "", errors.New("Unknown session property")
|
||||
|
|
|
@ -193,6 +193,7 @@ func (c *Client) sendMessagesReverse(chatID int64, messages []*client.Message) {
|
|||
strconv.FormatInt(message.Id, 10),
|
||||
c.xmpp,
|
||||
reply,
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -361,6 +362,10 @@ func (c *Client) ProcessTransportCommand(cmdline string, resource string) string
|
|||
}
|
||||
case "config":
|
||||
if len(args) > 1 {
|
||||
if !gateway.MessageOutgoingPermission && args[0] == "carbons" && args[1] == "true" {
|
||||
return "The server did not allow to enable carbons"
|
||||
}
|
||||
|
||||
value, err := c.Session.Set(args[0], args[1])
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
|
|
|
@ -242,7 +242,7 @@ func (c *Client) updateMessageContent(update *client.UpdateMessageContent) {
|
|||
textContent.Text.Entities,
|
||||
markupFunction,
|
||||
))
|
||||
gateway.SendMessage(c.jid, strconv.FormatInt(update.ChatId, 10), text, "e" + strconv.FormatInt(update.MessageId, 10), c.xmpp, nil)
|
||||
gateway.SendMessage(c.jid, strconv.FormatInt(update.ChatId, 10), text, "e" + strconv.FormatInt(update.MessageId, 10), c.xmpp, nil, false)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -109,6 +109,33 @@ func (c *Client) GetContactByID(id int64, chat *client.Chat) (*client.Chat, *cli
|
|||
return chat, user, nil
|
||||
}
|
||||
|
||||
// IsPM checks if a chat is PM
|
||||
func (c *Client) IsPM(id int64) (bool, error) {
|
||||
if !c.Online() || id == 0 {
|
||||
return false, errOffline
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
chat, ok := c.cache.GetChat(id)
|
||||
if !ok {
|
||||
chat, err = c.client.GetChat(&client.GetChatRequest{
|
||||
ChatId: id,
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
c.cache.SetChat(id, chat)
|
||||
}
|
||||
|
||||
chatType := chat.Type.ChatTypeType()
|
||||
if chatType == client.TypeChatTypePrivate || chatType == client.TypeChatTypeSecret {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (c *Client) userStatusToText(status client.UserStatus, chatID int64) (string, string, string) {
|
||||
var show, textStatus, presenceType string
|
||||
|
||||
|
@ -782,6 +809,7 @@ func (c *Client) ensureDownloadFile(file *client.File) *client.File {
|
|||
// ProcessIncomingMessage transfers a message to XMPP side and marks it as read on Telegram side
|
||||
func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) {
|
||||
var text, oob, auxText string
|
||||
var err error
|
||||
|
||||
reply, replyMsg := c.getMessageReply(message)
|
||||
|
||||
|
@ -847,12 +875,33 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) {
|
|||
MessageIds: []int64{message.Id},
|
||||
ForceRead: true,
|
||||
})
|
||||
|
||||
// forward message to XMPP
|
||||
sId := strconv.FormatInt(message.Id, 10)
|
||||
sChatId := strconv.FormatInt(chatId, 10)
|
||||
gateway.SendMessageWithOOB(c.jid, sChatId, text, sId, c.xmpp, reply, oob)
|
||||
|
||||
var jids []string
|
||||
var isPM bool
|
||||
if gateway.MessageOutgoingPermission && c.Session.Carbons {
|
||||
isPM, err = c.IsPM(chatId)
|
||||
if err != nil {
|
||||
log.Errorf("Could not determine if chat is PM: %v", err)
|
||||
}
|
||||
}
|
||||
isOutgoing := isPM && message.IsOutgoing
|
||||
if isOutgoing {
|
||||
for resource := range c.resourcesRange() {
|
||||
jids = append(jids, c.jid + "/" + resource)
|
||||
}
|
||||
} else {
|
||||
jids = []string{c.jid}
|
||||
}
|
||||
|
||||
for _, jid := range jids {
|
||||
gateway.SendMessageWithOOB(jid, sChatId, text, sId, c.xmpp, reply, oob, isOutgoing)
|
||||
if auxText != "" {
|
||||
gateway.SendMessage(c.jid, sChatId, auxText, sId, c.xmpp, reply)
|
||||
gateway.SendMessage(jid, sChatId, auxText, sId, c.xmpp, reply, isOutgoing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1024,6 +1073,25 @@ func (c *Client) deleteResource(resource string) {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *Client) resourcesRange() chan string {
|
||||
c.locks.resourcesLock.Lock()
|
||||
|
||||
resourceChan := make(chan string, 1)
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
c.locks.resourcesLock.Unlock()
|
||||
close(resourceChan)
|
||||
}()
|
||||
|
||||
for resource := range c.resources {
|
||||
resourceChan <- resource
|
||||
}
|
||||
}()
|
||||
|
||||
return resourceChan
|
||||
}
|
||||
|
||||
// resend statuses to (to another resource, for example)
|
||||
func (c *Client) roster(resource string) {
|
||||
if _, ok := c.resources[resource]; ok {
|
||||
|
|
|
@ -141,6 +141,45 @@ type FallbackSubject struct {
|
|||
End string `xml:"end,attr"`
|
||||
}
|
||||
|
||||
// CarbonReceived is from XEP-0280
|
||||
type CarbonReceived struct {
|
||||
XMLName xml.Name `xml:"urn:xmpp:carbons:2 received"`
|
||||
Forwarded stanza.Forwarded `xml:"urn:xmpp:forward:0 forwarded"`
|
||||
}
|
||||
|
||||
// CarbonSent is from XEP-0280
|
||||
type CarbonSent struct {
|
||||
XMLName xml.Name `xml:"urn:xmpp:carbons:2 sent"`
|
||||
Forwarded stanza.Forwarded `xml:"urn:xmpp:forward:0 forwarded"`
|
||||
}
|
||||
|
||||
// ComponentPrivilege is from XEP-0356
|
||||
type ComponentPrivilege struct {
|
||||
XMLName xml.Name `xml:"urn:xmpp:privilege:1 privilege"`
|
||||
Perms []ComponentPerm `xml:"perm"`
|
||||
Forwarded stanza.Forwarded `xml:"urn:xmpp:forward:0 forwarded"`
|
||||
}
|
||||
|
||||
// ComponentPerm is from XEP-0356
|
||||
type ComponentPerm struct {
|
||||
XMLName xml.Name `xml:"perm"`
|
||||
Access string `xml:"access,attr"`
|
||||
Type string `xml:"type,attr"`
|
||||
Push bool `xml:"push,attr"`
|
||||
}
|
||||
|
||||
// ClientMessage is a jabber:client NS message compatible with Prosody's XEP-0356 implementation
|
||||
type ClientMessage struct {
|
||||
XMLName xml.Name `xml:"jabber:client message"`
|
||||
stanza.Attrs
|
||||
|
||||
Subject string `xml:"subject,omitempty"`
|
||||
Body string `xml:"body,omitempty"`
|
||||
Thread string `xml:"thread,omitempty"`
|
||||
Error stanza.Err `xml:"error,omitempty"`
|
||||
Extensions []stanza.MsgExtension `xml:",omitempty"`
|
||||
}
|
||||
|
||||
// Namespace is a namespace!
|
||||
func (c PresenceNickExtension) Namespace() string {
|
||||
return c.XMLName.Space
|
||||
|
@ -171,6 +210,26 @@ func (c Fallback) Namespace() string {
|
|||
return c.XMLName.Space
|
||||
}
|
||||
|
||||
// Namespace is a namespace!
|
||||
func (c CarbonReceived) Namespace() string {
|
||||
return c.XMLName.Space
|
||||
}
|
||||
|
||||
// Namespace is a namespace!
|
||||
func (c CarbonSent) Namespace() string {
|
||||
return c.XMLName.Space
|
||||
}
|
||||
|
||||
// Namespace is a namespace!
|
||||
func (c ComponentPrivilege) Namespace() string {
|
||||
return c.XMLName.Space
|
||||
}
|
||||
|
||||
// Name is a packet name
|
||||
func (ClientMessage) Name() string {
|
||||
return "message"
|
||||
}
|
||||
|
||||
// NewReplyFallback initializes a fallback range
|
||||
func NewReplyFallback(start uint64, end uint64) Fallback {
|
||||
return Fallback{
|
||||
|
@ -214,4 +273,22 @@ func init() {
|
|||
"urn:xmpp:fallback:0",
|
||||
"fallback",
|
||||
}, Fallback{})
|
||||
|
||||
// carbon received
|
||||
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{
|
||||
"urn:xmpp:carbons:2",
|
||||
"received",
|
||||
}, CarbonReceived{})
|
||||
|
||||
// carbon sent
|
||||
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{
|
||||
"urn:xmpp:carbons:2",
|
||||
"sent",
|
||||
}, CarbonSent{})
|
||||
|
||||
// component privilege
|
||||
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{
|
||||
"urn:xmpp:privilege:1",
|
||||
"privilege",
|
||||
}, ComponentPrivilege{})
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package gateway
|
|||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"github.com/pkg/errors"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
|
@ -33,31 +34,44 @@ var Jid *stanza.Jid
|
|||
// were changed and need to be re-flushed to the YamlDB
|
||||
var DirtySessions = false
|
||||
|
||||
// MessageOutgoingPermission allows to fake outgoing messages by foreign JIDs
|
||||
var MessageOutgoingPermission = false
|
||||
|
||||
// SendMessage creates and sends a message stanza
|
||||
func SendMessage(to string, from string, body string, id string, component *xmpp.Component, reply *Reply) {
|
||||
sendMessageWrapper(to, from, body, id, component, reply, "")
|
||||
func SendMessage(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, isOutgoing bool) {
|
||||
sendMessageWrapper(to, from, body, id, component, reply, "", isOutgoing)
|
||||
}
|
||||
|
||||
// SendServiceMessage creates and sends a simple message stanza from transport
|
||||
func SendServiceMessage(to string, body string, component *xmpp.Component) {
|
||||
sendMessageWrapper(to, "", body, "", component, nil, "")
|
||||
sendMessageWrapper(to, "", body, "", component, nil, "", false)
|
||||
}
|
||||
|
||||
// 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, "")
|
||||
sendMessageWrapper(to, from, body, "", component, nil, "", false)
|
||||
}
|
||||
|
||||
// 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 string) {
|
||||
sendMessageWrapper(to, from, body, id, component, reply, oob)
|
||||
func SendMessageWithOOB(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, oob string, isOutgoing bool) {
|
||||
sendMessageWrapper(to, from, body, id, component, reply, oob, isOutgoing)
|
||||
}
|
||||
|
||||
func sendMessageWrapper(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, oob string) {
|
||||
func sendMessageWrapper(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, oob string, isOutgoing bool) {
|
||||
toJid, err := stanza.NewJid(to)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"to": to,
|
||||
}).Error(errors.Wrap(err, "Invalid to JID!"))
|
||||
return
|
||||
}
|
||||
bareTo := toJid.Bare()
|
||||
|
||||
componentJid := Jid.Full()
|
||||
|
||||
var logFrom string
|
||||
var messageFrom string
|
||||
var messageTo string
|
||||
if from == "" {
|
||||
logFrom = componentJid
|
||||
messageFrom = componentJid
|
||||
|
@ -65,6 +79,12 @@ func sendMessageWrapper(to string, from string, body string, id string, componen
|
|||
logFrom = from
|
||||
messageFrom = from + "@" + componentJid
|
||||
}
|
||||
if isOutgoing {
|
||||
messageTo = messageFrom
|
||||
messageFrom = bareTo + "/" + Jid.Resource
|
||||
} else {
|
||||
messageTo = to
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"from": logFrom,
|
||||
|
@ -74,7 +94,7 @@ func sendMessageWrapper(to string, from string, body string, id string, componen
|
|||
message := stanza.Message{
|
||||
Attrs: stanza.Attrs{
|
||||
From: messageFrom,
|
||||
To: to,
|
||||
To: messageTo,
|
||||
Type: "chat",
|
||||
Id: id,
|
||||
},
|
||||
|
@ -96,8 +116,35 @@ func sendMessageWrapper(to string, from string, body string, id string, componen
|
|||
}
|
||||
}
|
||||
|
||||
if isOutgoing {
|
||||
carbonMessage := extensions.ClientMessage{
|
||||
Attrs: stanza.Attrs{
|
||||
From: bareTo,
|
||||
To: to,
|
||||
Type: "chat",
|
||||
},
|
||||
}
|
||||
carbonMessage.Extensions = append(carbonMessage.Extensions, extensions.CarbonSent{
|
||||
Forwarded: stanza.Forwarded{
|
||||
Stanza: extensions.ClientMessage(message),
|
||||
},
|
||||
})
|
||||
privilegeMessage := stanza.Message{
|
||||
Attrs: stanza.Attrs{
|
||||
From: Jid.Bare(),
|
||||
To: toJid.Domain,
|
||||
},
|
||||
}
|
||||
privilegeMessage.Extensions = append(privilegeMessage.Extensions, extensions.ComponentPrivilege{
|
||||
Forwarded: stanza.Forwarded{
|
||||
Stanza: carbonMessage,
|
||||
},
|
||||
})
|
||||
sendMessage(&privilegeMessage, component)
|
||||
} else {
|
||||
sendMessage(&message, component)
|
||||
}
|
||||
}
|
||||
|
||||
// SetNickname sets a new nickname for a contact
|
||||
func SetNickname(to string, from string, nickname string, component *xmpp.Component) {
|
||||
|
@ -297,3 +344,15 @@ func ResumableSend(component *xmpp.Component, packet stanza.Packet) error {
|
|||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// SplitJID tokenizes a JID string to bare JID and resource
|
||||
func SplitJID(from string) (string, string, bool) {
|
||||
fromJid, err := stanza.NewJid(from)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"from": from,
|
||||
}).Error(errors.Wrap(err, "Invalid from JID!"))
|
||||
return "", "", false
|
||||
}
|
||||
return fromJid.Bare(), fromJid.Resource, true
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) {
|
|||
}).Warn("Message")
|
||||
log.Debugf("%#v", msg)
|
||||
|
||||
bare, resource, ok := splitFrom(msg.From)
|
||||
bare, resource, ok := gateway.SplitJID(msg.From)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
@ -152,6 +152,23 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) {
|
|||
}
|
||||
log.Warn("Unknown purpose of the message, skipping")
|
||||
}
|
||||
|
||||
if msg.Body == "" {
|
||||
var privilege extensions.ComponentPrivilege
|
||||
if ok := msg.Get(&privilege); ok {
|
||||
log.Debugf("privilege: %#v", privilege)
|
||||
}
|
||||
|
||||
for _, perm := range privilege.Perms {
|
||||
if perm.Access == "message" && perm.Type == "outgoing" {
|
||||
gateway.MessageOutgoingPermission = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if msg.Type == "error" {
|
||||
log.Errorf("MESSAGE ERROR: %#v", p)
|
||||
}
|
||||
}
|
||||
|
||||
// HandlePresence processes an incoming XMPP presence
|
||||
|
@ -196,7 +213,7 @@ func handleSubscription(s xmpp.Sender, p stanza.Presence) {
|
|||
if !ok {
|
||||
return
|
||||
}
|
||||
bare, _, ok := splitFrom(p.From)
|
||||
bare, _, ok := gateway.SplitJID(p.From)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
@ -227,7 +244,7 @@ func handlePresence(s xmpp.Sender, p stanza.Presence) {
|
|||
log.Debugf("%#v", p)
|
||||
|
||||
// create session
|
||||
bare, resource, ok := splitFrom(p.From)
|
||||
bare, resource, ok := gateway.SplitJID(p.From)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
@ -385,17 +402,6 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
|
|||
_ = gateway.ResumableSend(component, answer)
|
||||
}
|
||||
|
||||
func splitFrom(from string) (string, string, bool) {
|
||||
fromJid, err := stanza.NewJid(from)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"from": from,
|
||||
}).Error(errors.Wrap(err, "Invalid from JID!"))
|
||||
return "", "", false
|
||||
}
|
||||
return fromJid.Bare(), fromJid.Resource, true
|
||||
}
|
||||
|
||||
func toToID(to string) (int64, bool) {
|
||||
toParts := strings.Split(to, "@")
|
||||
if len(toParts) < 2 {
|
||||
|
|
Loading…
Reference in a new issue