Merge pull request #2 from kelaresg/develop
This commit is contained in:
commit
6a28cae311
|
@ -0,0 +1,96 @@
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
skype "github.com/kelaresg/go-skypeapi"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (handler *CommandHandler) CommandSpecialMux(ce *CommandEvent) {
|
||||||
|
switch ce.Command {
|
||||||
|
case "special-create":
|
||||||
|
if !ce.User.HasSession() {
|
||||||
|
ce.Reply("You are not logged in. Use the `login` command to log into Skype.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch ce.Command {
|
||||||
|
case "special-create":
|
||||||
|
handler.CommandSpecialCreate(ce)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
ce.Reply("Unknown Command")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler *CommandHandler) CommandSpecialHelp(ce *CommandEvent) {
|
||||||
|
cmdPrefix := ""
|
||||||
|
if ce.User.ManagementRoom != ce.RoomID || ce.User.IsRelaybot {
|
||||||
|
cmdPrefix = handler.bridge.Config.Bridge.CommandPrefix + " "
|
||||||
|
}
|
||||||
|
|
||||||
|
ce.Reply("* " + strings.Join([]string{
|
||||||
|
cmdPrefix + cmdSpecialCreateHelp,
|
||||||
|
}, "\n* "))
|
||||||
|
}
|
||||||
|
|
||||||
|
const cmdSpecialCreateHelp = `special-create <_topic_> <_member user id_>,... - Create a group.`
|
||||||
|
|
||||||
|
func (handler *CommandHandler) CommandSpecialCreate(ce *CommandEvent) {
|
||||||
|
if len(ce.Args) < 2 {
|
||||||
|
ce.Reply("**Usage:** `special-create <topic> <member user id>,...`")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := ce.User
|
||||||
|
topic := ce.Args[0]
|
||||||
|
members := skype.Members{}
|
||||||
|
|
||||||
|
// The user who created the group must be in the members and have "Admin" rights
|
||||||
|
userId := ce.User.Conn.UserProfile.Username
|
||||||
|
member2 := skype.Member{
|
||||||
|
Id: "8:" + userId,
|
||||||
|
Role: "Admin",
|
||||||
|
}
|
||||||
|
|
||||||
|
members.Members = append(members.Members, member2)
|
||||||
|
members.Properties = skype.Properties{
|
||||||
|
HistoryDisclosed: "true",
|
||||||
|
Topic: topic,
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.log.Debugln("Create Group", topic, "with", members)
|
||||||
|
err := user.Conn.HandleGroupCreate(members)
|
||||||
|
inputArr := strings.Split(ce.Args[1], ",")
|
||||||
|
members = skype.Members{}
|
||||||
|
for _, memberId := range inputArr {
|
||||||
|
members.Members = append(members.Members, skype.Member{
|
||||||
|
Id: memberId,
|
||||||
|
Role: "Admin",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
conversationId, ok := <-user.Conn.CreateChan
|
||||||
|
if ok {
|
||||||
|
err = user.Conn.AddMember(members, conversationId)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
ce.Reply("Please confirm that parameters is correct.")
|
||||||
|
} else {
|
||||||
|
ce.Reply("Syncing group list...")
|
||||||
|
time.Sleep(time.Duration(3) * time.Second)
|
||||||
|
ce.Reply("Syncing group list completed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
121
commands.go
121
commands.go
|
@ -1,18 +1,16 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
skype "github.com/kelaresg/go-skypeapi"
|
skype "github.com/kelaresg/go-skypeapi"
|
||||||
"github.com/kelaresg/matrix-skype/database"
|
"github.com/kelaresg/matrix-skype/database"
|
||||||
skypeExt "github.com/kelaresg/matrix-skype/skype-ext"
|
skypeExt "github.com/kelaresg/matrix-skype/skype-ext"
|
||||||
"math"
|
"math"
|
||||||
"time"
|
|
||||||
|
|
||||||
//"math"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
//"time"
|
|
||||||
|
|
||||||
"maunium.net/go/maulogger/v2"
|
"maunium.net/go/maulogger/v2"
|
||||||
|
|
||||||
|
@ -21,8 +19,6 @@ import (
|
||||||
"maunium.net/go/mautrix/event"
|
"maunium.net/go/mautrix/event"
|
||||||
"maunium.net/go/mautrix/format"
|
"maunium.net/go/mautrix/format"
|
||||||
"maunium.net/go/mautrix/id"
|
"maunium.net/go/mautrix/id"
|
||||||
|
|
||||||
"github.com/kelaresg/matrix-skype/whatsapp-ext"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type CommandHandler struct {
|
type CommandHandler struct {
|
||||||
|
@ -42,6 +38,7 @@ func NewCommandHandler(bridge *Bridge) *CommandHandler {
|
||||||
type CommandEvent struct {
|
type CommandEvent struct {
|
||||||
Bot *appservice.IntentAPI
|
Bot *appservice.IntentAPI
|
||||||
Bridge *Bridge
|
Bridge *Bridge
|
||||||
|
Portal *Portal
|
||||||
Handler *CommandHandler
|
Handler *CommandHandler
|
||||||
RoomID id.RoomID
|
RoomID id.RoomID
|
||||||
User *User
|
User *User
|
||||||
|
@ -75,6 +72,9 @@ func (handler *CommandHandler) Handle(roomID id.RoomID, user *User, message stri
|
||||||
Command: strings.ToLower(args[0]),
|
Command: strings.ToLower(args[0]),
|
||||||
Args: args[1:],
|
Args: args[1:],
|
||||||
}
|
}
|
||||||
|
if ce.Command == "login" {
|
||||||
|
message = ""
|
||||||
|
}
|
||||||
handler.log.Debugfln("%s sent '%s' in %s", user.MXID, message, roomID)
|
handler.log.Debugfln("%s sent '%s' in %s", user.MXID, message, roomID)
|
||||||
if roomID == handler.bridge.Config.Bridge.Relaybot.ManagementRoom {
|
if roomID == handler.bridge.Config.Bridge.Relaybot.ManagementRoom {
|
||||||
handler.CommandRelaybot(ce)
|
handler.CommandRelaybot(ce)
|
||||||
|
@ -146,7 +146,7 @@ func (handler *CommandHandler) CommandMux(ce *CommandEvent) {
|
||||||
handler.CommandCreate(ce)
|
handler.CommandCreate(ce)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
ce.Reply("Unknown Command")
|
handler.CommandSpecialMux(ce)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -697,7 +697,7 @@ func (handler *CommandHandler) CommandOpen(ce *CommandEvent) {
|
||||||
jid := ce.Args[0]
|
jid := ce.Args[0]
|
||||||
|
|
||||||
if strings.HasSuffix(jid, skypeExt.NewUserSuffix) {
|
if strings.HasSuffix(jid, skypeExt.NewUserSuffix) {
|
||||||
ce.Reply("That looks like a user ID. Did you mean `pm %s`?", jid[:len(jid)-len(whatsappExt.NewUserSuffix)])
|
ce.Reply("That looks like a user ID. Did you mean `pm %s`?", jid[:len(jid)-len(skypeExt.NewUserSuffix)])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ce.User.Conn.GetConversations("", handler.bridge.Config.Bridge.InitialChatSync)
|
ce.User.Conn.GetConversations("", handler.bridge.Config.Bridge.InitialChatSync)
|
||||||
|
@ -977,7 +977,7 @@ func (handler *CommandHandler) CommandKick(ce *CommandEvent) {
|
||||||
// reason = ce.Args[0]
|
// reason = ce.Args[0]
|
||||||
//}
|
//}
|
||||||
|
|
||||||
if strings.HasSuffix(converationId, whatsappExt.NewUserSuffix) {
|
if strings.HasSuffix(converationId, skypeExt.NewUserSuffix) {
|
||||||
ce.Reply("**Usage:** `kick <group ID> <contact id>,... reason`")
|
ce.Reply("**Usage:** `kick <group ID> <contact id>,... reason`")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -995,8 +995,8 @@ func (handler *CommandHandler) CommandKick(ce *CommandEvent) {
|
||||||
portal := user.bridge.GetPortalByJID(database.GroupPortalKey(converationId))
|
portal := user.bridge.GetPortalByJID(database.GroupPortalKey(converationId))
|
||||||
|
|
||||||
for i, number := range userNumbers {
|
for i, number := range userNumbers {
|
||||||
userNumbers[i] = number // + whatsappExt.NewUserSuffix
|
userNumbers[i] = number // + skypeExt.NewUserSuffix
|
||||||
member := portal.bridge.GetPuppetByJID(number + whatsappExt.NewUserSuffix)
|
member := portal.bridge.GetPuppetByJID(number + skypeExt.NewUserSuffix)
|
||||||
|
|
||||||
if member == nil {
|
if member == nil {
|
||||||
portal.log.Errorln("%s is not a puppet", number)
|
portal.log.Errorln("%s is not a puppet", number)
|
||||||
|
@ -1025,7 +1025,7 @@ func (handler *CommandHandler) CommandLeave(ce *CommandEvent) {
|
||||||
user := ce.User
|
user := ce.User
|
||||||
groupId := ce.Args[0]
|
groupId := ce.Args[0]
|
||||||
|
|
||||||
if strings.HasSuffix(groupId, whatsappExt.NewUserSuffix) {
|
if strings.HasSuffix(groupId, skypeExt.NewUserSuffix) {
|
||||||
ce.Reply("**Usage:** `leave <group JID>`")
|
ce.Reply("**Usage:** `leave <group JID>`")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1155,50 +1155,97 @@ func (handler *CommandHandler) CommandJoin(ce *CommandEvent) {
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
|
||||||
const cmdCreateHelp = `create <_topic_> <_member user id_>,... - Create a group.`
|
const cmdCreateHelp = `create - Create a group chat.`
|
||||||
|
|
||||||
func (handler *CommandHandler) CommandCreate(ce *CommandEvent) {
|
func (handler *CommandHandler) CommandCreate(ce *CommandEvent) {
|
||||||
if len(ce.Args) < 2 {
|
if ce.Portal != nil {
|
||||||
ce.Reply("**Usage:** `create <topic> <member user id>,...`")
|
ce.Reply("This is already a portal room")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user := ce.User
|
members, err := ce.Bot.JoinedMembers(ce.RoomID)
|
||||||
topic := ce.Args[0]
|
handler.log.Debugln("Create Group-1", members)
|
||||||
members := skype.Members{}
|
if err != nil {
|
||||||
|
ce.Reply("Failed to get room members: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// The user who created the group must be in the members and have "Admin" rights
|
var roomNameEvent event.RoomNameEventContent
|
||||||
userId := ce.User.Conn.UserProfile.Username
|
err = ce.Bot.StateEvent(ce.RoomID, event.StateRoomName, "", &roomNameEvent)
|
||||||
|
if err != nil && !errors.Is(err, mautrix.MNotFound) {
|
||||||
|
ce.Reply("Failed to get room name")
|
||||||
|
return
|
||||||
|
} else if len(roomNameEvent.Name) == 0 {
|
||||||
|
ce.Reply("Please set a name for the room first")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var encryptionEvent event.EncryptionEventContent
|
||||||
|
err = ce.Bot.StateEvent(ce.RoomID, event.StateEncryption, "", &encryptionEvent)
|
||||||
|
if err != nil && !errors.Is(err, mautrix.MNotFound) {
|
||||||
|
ce.Reply("Failed to get room encryption status")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var participants []string
|
||||||
|
for userID := range members.Joined {
|
||||||
|
jid, ok := handler.bridge.ParsePuppetMXID(userID)
|
||||||
|
if ok && jid != ce.User.JID {
|
||||||
|
participants = append(participants, jid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selfMembers := skype.Members{}
|
||||||
member2 := skype.Member{
|
member2 := skype.Member{
|
||||||
Id: "8:" + userId,
|
Id: strings.Replace(ce.User.JID, skypeExt.NewUserSuffix,"", 1),
|
||||||
Role: "Admin",
|
Role: "Admin",
|
||||||
}
|
}
|
||||||
|
|
||||||
members.Members = append(members.Members, member2)
|
selfMembers.Members = append(selfMembers.Members, member2)
|
||||||
members.Properties = skype.Properties{
|
selfMembers.Properties = skype.Properties{
|
||||||
HistoryDisclosed: "true",
|
HistoryDisclosed: "true",
|
||||||
Topic: topic,
|
Topic: roomNameEvent.Name,
|
||||||
}
|
}
|
||||||
|
handler.log.Debugln("Create Group", roomNameEvent.Name, "with", selfMembers, participants)
|
||||||
handler.log.Debugln("Create Group", topic, "with", members)
|
err = ce.User.Conn.HandleGroupCreate(selfMembers)
|
||||||
err := user.Conn.HandleGroupCreate(members)
|
if err != nil {
|
||||||
inputArr := strings.Split(ce.Args[1], ",")
|
ce.Reply("Failed to create group: %v", err)
|
||||||
members = skype.Members{}
|
return
|
||||||
for _, memberId := range inputArr {
|
}
|
||||||
members.Members = append(members.Members, skype.Member{
|
participantMembers := skype.Members{}
|
||||||
|
for _, participant := range participants {
|
||||||
|
memberId := strings.Replace(participant, skypeExt.NewUserSuffix, "", 1)
|
||||||
|
participantMembers.Members = append(participantMembers.Members, skype.Member{
|
||||||
Id: memberId,
|
Id: memberId,
|
||||||
Role: "Admin",
|
Role: "Admin",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
conversationId, ok := <-user.Conn.CreateChan
|
conversationId, ok := <-ce.User.Conn.CreateChan
|
||||||
if ok {
|
if ok {
|
||||||
err = user.Conn.AddMember(members, conversationId)
|
portal := handler.bridge.GetPortalByJID(database.GroupPortalKey(conversationId))
|
||||||
|
portal.roomCreateLock.Lock()
|
||||||
|
defer portal.roomCreateLock.Unlock()
|
||||||
|
if len(portal.MXID) != 0 {
|
||||||
|
portal.log.Warnln("Detected race condition in room creation")
|
||||||
|
// TODO race condition, clean up the old room
|
||||||
}
|
}
|
||||||
|
portal.MXID = ce.RoomID
|
||||||
|
portal.Name = roomNameEvent.Name
|
||||||
|
portal.Encrypted = encryptionEvent.Algorithm == id.AlgorithmMegolmV1
|
||||||
|
if !portal.Encrypted && handler.bridge.Config.Bridge.Encryption.Default {
|
||||||
|
_, err = portal.MainIntent().SendStateEvent(portal.MXID, event.StateEncryption, "", &event.EncryptionEventContent{Algorithm: id.AlgorithmMegolmV1})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ce.Reply("Please confirm that parameters is correct.")
|
portal.log.Warnln("Failed to enable e2be:", err)
|
||||||
} else {
|
|
||||||
ce.Reply("Syncing group list...")
|
|
||||||
time.Sleep(time.Duration(3) * time.Second)
|
|
||||||
ce.Reply("Syncing group list completed")
|
|
||||||
}
|
}
|
||||||
|
portal.Encrypted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
portal.Update()
|
||||||
|
portal.UpdateBridgeInfo()
|
||||||
|
|
||||||
|
err = ce.User.Conn.AddMember(participantMembers, conversationId)
|
||||||
|
ce.Reply("Successfully created Skype group %s", portal.Key.JID)
|
||||||
|
}
|
||||||
|
|
||||||
|
//ce.User.addPortalToCommunity(portal)
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ func (user *User) updateCommunityProfile() {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
AvatarURL string `json:"avatar_url"`
|
AvatarURL string `json:"avatar_url"`
|
||||||
ShortDescription string `json:"short_description"`
|
ShortDescription string `json:"short_description"`
|
||||||
}{"WhatsApp", user.bridge.Config.AppService.Bot.Avatar, "Your WhatsApp bridged chats"}
|
}{"Skype", user.bridge.Config.AppService.Bot.Avatar, "Your Skype bridged chats"}
|
||||||
_, err := user.bridge.Bot.MakeRequest(http.MethodPost, url, &profileReq, nil)
|
_, err := user.bridge.Bot.MakeRequest(http.MethodPost, url, &profileReq, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
user.log.Warnfln("Failed to update metadata of %s: %v", user.CommunityID, err)
|
user.log.Warnfln("Failed to update metadata of %s: %v", user.CommunityID, err)
|
||||||
|
|
|
@ -61,6 +61,12 @@ type BridgeConfig struct {
|
||||||
RequireCrossSigning bool `yaml:"require_cross_signing"`
|
RequireCrossSigning bool `yaml:"require_cross_signing"`
|
||||||
RequireVerification bool `yaml:"require_verification"`
|
RequireVerification bool `yaml:"require_verification"`
|
||||||
} `yaml:"key_sharing"`
|
} `yaml:"key_sharing"`
|
||||||
|
|
||||||
|
PuppetId struct {
|
||||||
|
Allow bool `yaml:"allow"`
|
||||||
|
Key string `yaml:"key"`
|
||||||
|
UsernameTemplatePrefix string `yaml:"username_template_prefix"`
|
||||||
|
} `yaml:"puppet_id"`
|
||||||
} `yaml:"encryption"`
|
} `yaml:"encryption"`
|
||||||
|
|
||||||
Permissions PermissionConfig `yaml:"permissions"`
|
Permissions PermissionConfig `yaml:"permissions"`
|
||||||
|
|
|
@ -18,6 +18,7 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"maunium.net/go/mautrix/patch"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
@ -98,6 +99,10 @@ func (config *Config) MakeAppService() (*appservice.AppService, error) {
|
||||||
as.HomeserverURL = config.Homeserver.Address
|
as.HomeserverURL = config.Homeserver.Address
|
||||||
as.Host.Hostname = config.AppService.Hostname
|
as.Host.Hostname = config.AppService.Hostname
|
||||||
as.Host.Port = config.AppService.Port
|
as.Host.Port = config.AppService.Port
|
||||||
|
patch.ThirdPartyIdEncrypt = config.Bridge.Encryption.PuppetId.Allow
|
||||||
|
patch.AsBotName = config.AppService.Bot.Username
|
||||||
|
patch.AsUserPrefix = config.Bridge.Encryption.PuppetId.UsernameTemplatePrefix
|
||||||
|
patch.XorKey = config.Bridge.Encryption.PuppetId.Key
|
||||||
var err error
|
var err error
|
||||||
as.Registration, err = config.GetRegistration()
|
as.Registration, err = config.GetRegistration()
|
||||||
return as, err
|
return as, err
|
||||||
|
|
|
@ -138,7 +138,7 @@ func (helper *CryptoHelper) loginBot() (*mautrix.Client, error) {
|
||||||
Type: mautrix.AuthTypeAppservice,
|
Type: mautrix.AuthTypeAppservice,
|
||||||
Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: string(helper.bridge.AS.BotMXID())},
|
Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: string(helper.bridge.AS.BotMXID())},
|
||||||
DeviceID: deviceID,
|
DeviceID: deviceID,
|
||||||
InitialDeviceDisplayName: "WhatsApp Bridge",
|
InitialDeviceDisplayName: "Skype Bridge",
|
||||||
StoreCredentials: true,
|
StoreCredentials: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -166,7 +166,7 @@ func (helper *CryptoHelper) loginBotOld() (*mautrix.Client, error) {
|
||||||
Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: string(helper.bridge.AS.BotMXID())},
|
Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: string(helper.bridge.AS.BotMXID())},
|
||||||
Password: hex.EncodeToString(mac.Sum(nil)),
|
Password: hex.EncodeToString(mac.Sum(nil)),
|
||||||
DeviceID: deviceID,
|
DeviceID: deviceID,
|
||||||
InitialDeviceDisplayName: "WhatsApp Bridge",
|
InitialDeviceDisplayName: "Skype Bridge",
|
||||||
StoreCredentials: true,
|
StoreCredentials: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -51,8 +51,8 @@ func (puppet *Puppet) loginWithSharedSecret(mxid id.UserID) (string, error) {
|
||||||
Type: "m.login.password",
|
Type: "m.login.password",
|
||||||
Identifier: mautrix.UserIdentifier{Type: "m.id.user", User: string(mxid)},
|
Identifier: mautrix.UserIdentifier{Type: "m.id.user", User: string(mxid)},
|
||||||
Password: hex.EncodeToString(mac.Sum(nil)),
|
Password: hex.EncodeToString(mac.Sum(nil)),
|
||||||
DeviceID: "WhatsApp Bridge",
|
DeviceID: "Skype Bridge",
|
||||||
InitialDeviceDisplayName: "WhatsApp Bridge",
|
InitialDeviceDisplayName: "Skype Bridge",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
|
@ -168,6 +168,14 @@ bridge:
|
||||||
# It is recommended to also set private_chat_portal_meta to true when using this.
|
# It is recommended to also set private_chat_portal_meta to true when using this.
|
||||||
default: false
|
default: false
|
||||||
|
|
||||||
|
puppet_id:
|
||||||
|
# when set to true, the matrixid of the contact (puppet) from the bridge to the matrix will be encrypted into another string
|
||||||
|
default: false
|
||||||
|
# 8 characters
|
||||||
|
key: '12dsf323'
|
||||||
|
# Use the username_template prefix. (Warning: At present, username_template cannot be too complicated, otherwise this function may cause unknown errors)
|
||||||
|
username_template_prefix: 'skype&'
|
||||||
|
|
||||||
# Permissions for using the bridge.
|
# Permissions for using the bridge.
|
||||||
# Permitted values:
|
# Permitted values:
|
||||||
# relaybot - Talk through the relaybot (if enabled), no access otherwise
|
# relaybot - Talk through the relaybot (if enabled), no access otherwise
|
||||||
|
|
7
go.mod
7
go.mod
|
@ -3,10 +3,9 @@ module github.com/kelaresg/matrix-skype
|
||||||
go 1.14
|
go 1.14
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Rhymen/go-whatsapp v0.1.0
|
|
||||||
github.com/chai2010/webp v1.1.0
|
github.com/chai2010/webp v1.1.0
|
||||||
github.com/gorilla/websocket v1.4.2
|
github.com/gorilla/websocket v1.4.2
|
||||||
github.com/kelaresg/go-skypeapi v0.1.2-0.20201126103218-226d1ec92858
|
github.com/kelaresg/go-skypeapi v0.1.2-0.20201211120317-8651f9f08575
|
||||||
github.com/lib/pq v1.7.0
|
github.com/lib/pq v1.7.0
|
||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
|
@ -19,6 +18,4 @@ require (
|
||||||
maunium.net/go/mautrix v0.8.0-rc.4
|
maunium.net/go/mautrix v0.8.0-rc.4
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/Rhymen/go-whatsapp => github.com/tulir/go-whatsapp v0.2.8
|
replace maunium.net/go/mautrix => github.com/pidongqianqian/mautrix-go v0.8.0-rc.4.0.20201208081810-787323a21113
|
||||||
|
|
||||||
replace maunium.net/go/mautrix => github.com/pidongqianqian/mautrix-go v0.8.0-rc.4.0.20201126070406-7b13ac473bcc
|
|
||||||
|
|
25
matrix.go
25
matrix.go
|
@ -5,6 +5,7 @@ import (
|
||||||
skype "github.com/kelaresg/go-skypeapi"
|
skype "github.com/kelaresg/go-skypeapi"
|
||||||
"github.com/kelaresg/matrix-skype/database"
|
"github.com/kelaresg/matrix-skype/database"
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
|
"maunium.net/go/mautrix/patch"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -210,7 +211,15 @@ func (mx *MatrixHandler) HandlePuppetInvite(evt *event.Event, inviter *User, pup
|
||||||
}
|
}
|
||||||
var hasBridgeBot, hasOtherUsers bool
|
var hasBridgeBot, hasOtherUsers bool
|
||||||
for mxid, _ := range members.Joined {
|
for mxid, _ := range members.Joined {
|
||||||
if mxid == intent.UserID || mxid == inviter.MXID {
|
fmt.Println()
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("HandlePuppetInvite mxid", mxid)
|
||||||
|
fmt.Println("HandlePuppetInvite intent.UserID", intent.UserID)
|
||||||
|
fmt.Println("HandlePuppetInvite patch.Parse(intent.UserID)", id.UserID(patch.Parse(string(intent.UserID))))
|
||||||
|
fmt.Println("HandlePuppetInvite inviter.MXID", inviter.MXID)
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println()
|
||||||
|
if mxid == id.UserID(patch.Parse(string(intent.UserID))) || mxid == inviter.MXID {
|
||||||
continue
|
continue
|
||||||
} else if mxid == mx.bridge.Bot.UserID {
|
} else if mxid == mx.bridge.Bot.UserID {
|
||||||
hasBridgeBot = true
|
hasBridgeBot = true
|
||||||
|
@ -223,14 +232,16 @@ func (mx *MatrixHandler) HandlePuppetInvite(evt *event.Event, inviter *User, pup
|
||||||
mx.handlePrivatePortal(evt.RoomID, inviter, puppet, key)
|
mx.handlePrivatePortal(evt.RoomID, inviter, puppet, key)
|
||||||
} else if !hasBridgeBot {
|
} else if !hasBridgeBot {
|
||||||
mx.log.Debugln("Leaving multi-user room", evt.RoomID, "as", puppet.MXID, "after accepting invite from", evt.Sender)
|
mx.log.Debugln("Leaving multi-user room", evt.RoomID, "as", puppet.MXID, "after accepting invite from", evt.Sender)
|
||||||
_, _ = intent.SendNotice(evt.RoomID, "Please invite the bridge bot first if you want to bridge to a WhatsApp group.")
|
_, _ = intent.SendNotice(evt.RoomID, "Please invite the bridge bot first if you want to bridge to a skype group.")
|
||||||
_, _ = intent.LeaveRoom(evt.RoomID)
|
_, _ = intent.LeaveRoom(evt.RoomID)
|
||||||
} else {
|
} else {
|
||||||
_, _ = intent.SendNotice(evt.RoomID, "This puppet will remain inactive until this room is bridged to a WhatsApp group.")
|
_, _ = intent.SendNotice(evt.RoomID, "This puppet will remain inactive until this room is bridged to a Skype group.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mx *MatrixHandler) HandleMembership(evt *event.Event) {
|
func (mx *MatrixHandler) HandleMembership(evt *event.Event) {
|
||||||
|
fmt.Println("HandleMembership0 evt.Sender:", evt.Sender)
|
||||||
|
fmt.Println("HandleMembership0 evt.GetStateKey:", evt.GetStateKey())
|
||||||
if _, isPuppet := mx.bridge.ParsePuppetMXID(evt.Sender); evt.Sender == mx.bridge.Bot.UserID || isPuppet {
|
if _, isPuppet := mx.bridge.ParsePuppetMXID(evt.Sender); evt.Sender == mx.bridge.Bot.UserID || isPuppet {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -274,6 +285,14 @@ func (mx *MatrixHandler) HandleMembership(evt *event.Event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("HandleMembership evt.RoomID", evt.RoomID)
|
||||||
|
fmt.Println("HandleMembership id.UserID(evt.GetStateKey())", id.UserID(evt.GetStateKey()))
|
||||||
|
fmt.Println("HandleMembership event.MembershipLeave", event.MembershipLeave)
|
||||||
|
fmt.Println("HandleMembership user.", event.MembershipLeave)
|
||||||
|
fmt.Println()
|
||||||
|
//mx.as.StateStore.SetMembership(evt.RoomID, id.UserID(evt.GetStateKey()), event.MembershipLeave)
|
||||||
portal.HandleMatrixKick(user, evt)
|
portal.HandleMatrixKick(user, evt)
|
||||||
}
|
}
|
||||||
} else if content.Membership == event.MembershipInvite && !isSelf {
|
} else if content.Membership == event.MembershipInvite && !isSelf {
|
||||||
|
|
424
portal.go
424
portal.go
|
@ -2,7 +2,8 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/gob"
|
"maunium.net/go/mautrix/patch"
|
||||||
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
@ -25,14 +26,9 @@ import (
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
log "maunium.net/go/maulogger/v2"
|
log "maunium.net/go/maulogger/v2"
|
||||||
|
|
||||||
"maunium.net/go/mautrix/crypto/attachment"
|
|
||||||
|
|
||||||
"github.com/Rhymen/go-whatsapp"
|
|
||||||
waProto "github.com/Rhymen/go-whatsapp/binary/proto"
|
|
||||||
|
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/mautrix/appservice"
|
"maunium.net/go/mautrix/appservice"
|
||||||
|
"maunium.net/go/mautrix/crypto/attachment"
|
||||||
"maunium.net/go/mautrix/event"
|
"maunium.net/go/mautrix/event"
|
||||||
"maunium.net/go/mautrix/format"
|
"maunium.net/go/mautrix/format"
|
||||||
"maunium.net/go/mautrix/id"
|
"maunium.net/go/mautrix/id"
|
||||||
|
@ -40,7 +36,6 @@ import (
|
||||||
|
|
||||||
"github.com/kelaresg/matrix-skype/database"
|
"github.com/kelaresg/matrix-skype/database"
|
||||||
"github.com/kelaresg/matrix-skype/types"
|
"github.com/kelaresg/matrix-skype/types"
|
||||||
"github.com/kelaresg/matrix-skype/whatsapp-ext"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (bridge *Bridge) GetPortalByMXID(mxid id.RoomID) *Portal {
|
func (bridge *Bridge) GetPortalByMXID(mxid id.RoomID) *Portal {
|
||||||
|
@ -251,36 +246,36 @@ func (portal *Portal) isDuplicate(clientMessageId types.SkypeMessageID, id strin
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
//func init() {
|
||||||
gob.Register(&waProto.Message{})
|
// gob.Register(&waProto.Message{})
|
||||||
}
|
//}
|
||||||
|
|
||||||
func (portal *Portal) markHandled(source *User, message *waProto.WebMessageInfo, mxid id.EventID) {
|
//func (portal *Portal) markHandled(source *User, message *waProto.WebMessageInfo, mxid id.EventID) {
|
||||||
msg := portal.bridge.DB.Message.New()
|
// msg := portal.bridge.DB.Message.New()
|
||||||
msg.Chat = portal.Key
|
// msg.Chat = portal.Key
|
||||||
msg.JID = message.GetKey().GetId()
|
// msg.JID = message.GetKey().GetId()
|
||||||
msg.MXID = mxid
|
// msg.MXID = mxid
|
||||||
msg.Timestamp = message.GetMessageTimestamp()
|
// msg.Timestamp = message.GetMessageTimestamp()
|
||||||
if message.GetKey().GetFromMe() {
|
// if message.GetKey().GetFromMe() {
|
||||||
msg.Sender = source.JID
|
// msg.Sender = source.JID
|
||||||
} else if portal.IsPrivateChat() {
|
// } else if portal.IsPrivateChat() {
|
||||||
msg.Sender = portal.Key.JID
|
// msg.Sender = portal.Key.JID
|
||||||
} else {
|
// } else {
|
||||||
msg.Sender = message.GetKey().GetParticipant()
|
// msg.Sender = message.GetKey().GetParticipant()
|
||||||
if len(msg.Sender) == 0 {
|
// if len(msg.Sender) == 0 {
|
||||||
msg.Sender = message.GetParticipant()
|
// msg.Sender = message.GetParticipant()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
//msg.Content = message.Message
|
// //msg.Content = message.Message
|
||||||
msg.Content = &skype.Resource{}
|
// msg.Content = &skype.Resource{}
|
||||||
msg.Insert()
|
// msg.Insert()
|
||||||
|
//
|
||||||
portal.recentlyHandledLock.Lock()
|
// portal.recentlyHandledLock.Lock()
|
||||||
index := portal.recentlyHandledIndex
|
// index := portal.recentlyHandledIndex
|
||||||
portal.recentlyHandledIndex = (portal.recentlyHandledIndex + 1) % recentlyHandledLength
|
// portal.recentlyHandledIndex = (portal.recentlyHandledIndex + 1) % recentlyHandledLength
|
||||||
portal.recentlyHandledLock.Unlock()
|
// portal.recentlyHandledLock.Unlock()
|
||||||
portal.recentlyHandled[index] = msg.JID
|
// portal.recentlyHandled[index] = msg.JID
|
||||||
}
|
//}
|
||||||
|
|
||||||
func (portal *Portal) markHandledSkype(source *User, message *skype.Resource, mxid id.EventID) {
|
func (portal *Portal) markHandledSkype(source *User, message *skype.Resource, mxid id.EventID) {
|
||||||
msg := portal.bridge.DB.Message.New()
|
msg := portal.bridge.DB.Message.New()
|
||||||
|
@ -312,20 +307,20 @@ fmt.Println("markHandledSkype2", msg.JID)
|
||||||
portal.recentlyHandled[index] = msg.JID
|
portal.recentlyHandled[index] = msg.JID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (portal *Portal) getMessageIntent(user *User, info whatsapp.MessageInfo) *appservice.IntentAPI {
|
//func (portal *Portal) getMessageIntent(user *User, info whatsapp.MessageInfo) *appservice.IntentAPI {
|
||||||
if info.FromMe {
|
// if info.FromMe {
|
||||||
return portal.bridge.GetPuppetByJID(user.JID).IntentFor(portal)
|
// return portal.bridge.GetPuppetByJID(user.JID).IntentFor(portal)
|
||||||
} else if portal.IsPrivateChat() {
|
// } else if portal.IsPrivateChat() {
|
||||||
return portal.MainIntent()
|
// return portal.MainIntent()
|
||||||
} else if len(info.SenderJid) == 0 {
|
// } else if len(info.SenderJid) == 0 {
|
||||||
if len(info.Source.GetParticipant()) != 0 {
|
// if len(info.Source.GetParticipant()) != 0 {
|
||||||
info.SenderJid = info.Source.GetParticipant()
|
// info.SenderJid = info.Source.GetParticipant()
|
||||||
} else {
|
// } else {
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return portal.bridge.GetPuppetByJID(info.SenderJid).IntentFor(portal)
|
// return portal.bridge.GetPuppetByJID(info.SenderJid).IntentFor(portal)
|
||||||
}
|
//}
|
||||||
|
|
||||||
func (portal *Portal) getMessageIntentSkype(user *User, info skype.Resource) *appservice.IntentAPI {
|
func (portal *Portal) getMessageIntentSkype(user *User, info skype.Resource) *appservice.IntentAPI {
|
||||||
if info.GetFromMe(user.Conn.Conn) {
|
if info.GetFromMe(user.Conn.Conn) {
|
||||||
|
@ -385,11 +380,11 @@ func (portal *Portal) startHandlingSkype(source *User, info skype.Resource) (*ap
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (portal *Portal) finishHandling(source *User, message *waProto.WebMessageInfo, mxid id.EventID) {
|
//func (portal *Portal) finishHandling(source *User, message *waProto.WebMessageInfo, mxid id.EventID) {
|
||||||
portal.markHandled(source, message, mxid)
|
// portal.markHandled(source, message, mxid)
|
||||||
portal.sendDeliveryReceipt(mxid)
|
// portal.sendDeliveryReceipt(mxid)
|
||||||
portal.log.Debugln("Handled message", message.GetKey().GetId(), "->", mxid)
|
// portal.log.Debugln("Handled message", message.GetKey().GetId(), "->", mxid)
|
||||||
}
|
//}
|
||||||
|
|
||||||
func (portal *Portal) finishHandlingSkype(source *User, message *skype.Resource, mxid id.EventID) {
|
func (portal *Portal) finishHandlingSkype(source *User, message *skype.Resource, mxid id.EventID) {
|
||||||
portal.markHandledSkype(source, message, mxid)
|
portal.markHandledSkype(source, message, mxid)
|
||||||
|
@ -638,10 +633,10 @@ func (portal *Portal) SyncSkype(user *User, chat skype.Conversation) {
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("SyncSkype ensureUserInvited", portal.MXID)
|
fmt.Println("SyncSkype ensureUserInvited", portal.MXID)
|
||||||
portal.ensureUserInvited(user)
|
portal.ensureUserInvited(user)
|
||||||
rep, err := portal.MainIntent().SetPowerLevel(portal.MXID, user.MXID, 95)
|
//rep, err := portal.MainIntent().SetPowerLevel(portal.MXID, user.MXID, 95)
|
||||||
if err != nil {
|
//if err != nil {
|
||||||
portal.log.Warnfln("SyncSkype: SetPowerLevel err: ", err, rep)
|
// portal.log.Warnfln("SyncSkype: SetPowerLevel err: ", err, rep)
|
||||||
}
|
//}
|
||||||
|
|
||||||
//if portal.IsPrivateChat() {
|
//if portal.IsPrivateChat() {
|
||||||
// preUserIds,_ := portal.GetMatrixUsers()
|
// preUserIds,_ := portal.GetMatrixUsers()
|
||||||
|
@ -776,7 +771,7 @@ func (portal *Portal) ChangeAdminStatus(jids []string, setAdmin bool) {
|
||||||
// UserID: member.MXID,
|
// UserID: member.MXID,
|
||||||
// })
|
// })
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// portal.log.Errorln("Error %s member from whatsapp: %v", action, err)
|
// portal.log.Errorln("Error %s member from skype: %v", action, err)
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
@ -786,13 +781,14 @@ func (portal *Portal) membershipRemove(content string) {
|
||||||
err := xml.Unmarshal([]byte(content), &xmlFormat)
|
err := xml.Unmarshal([]byte(content), &xmlFormat)
|
||||||
for _, target := range xmlFormat.Targets {
|
for _, target := range xmlFormat.Targets {
|
||||||
member := portal.bridge.GetPuppetByJID(target)
|
member := portal.bridge.GetPuppetByJID(target)
|
||||||
|
memberMXID := id.UserID(patch.Parse(string(member.MXID)))
|
||||||
memberMaxid := strings.Replace(string(member.MXID), "@skype&8:", "@skype&8-", 1)
|
if portal.bridge.AS.StateStore.IsInRoom(portal.MXID, memberMXID) {
|
||||||
_, err = portal.MainIntent().KickUser(portal.MXID, &mautrix.ReqKickUser{
|
_, err = portal.MainIntent().KickUser(portal.MXID, &mautrix.ReqKickUser{
|
||||||
UserID: id.UserID(memberMaxid),
|
UserID: member.MXID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
portal.log.Errorln("Error %v member from whatsapp:", err)
|
portal.log.Errorln("Error kick member from matrix after kick from skype: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1291,21 +1287,21 @@ func (portal *Portal) MainIntent() *appservice.IntentAPI {
|
||||||
return portal.bridge.Bot
|
return portal.bridge.Bot
|
||||||
}
|
}
|
||||||
|
|
||||||
func (portal *Portal) SetReply(content *event.MessageEventContent, info whatsapp.ContextInfo) {
|
//func (portal *Portal) SetReply(content *event.MessageEventContent, info whatsapp.ContextInfo) {
|
||||||
if len(info.QuotedMessageID) == 0 {
|
// if len(info.QuotedMessageID) == 0 {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
message := portal.bridge.DB.Message.GetByJID(portal.Key, info.QuotedMessageID)
|
// message := portal.bridge.DB.Message.GetByJID(portal.Key, info.QuotedMessageID)
|
||||||
if message != nil {
|
// if message != nil {
|
||||||
evt, err := portal.MainIntent().GetEvent(portal.MXID, message.MXID)
|
// evt, err := portal.MainIntent().GetEvent(portal.MXID, message.MXID)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
portal.log.Warnln("Failed to get reply target:", err)
|
// portal.log.Warnln("Failed to get reply target:", err)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
content.SetReply(evt)
|
// content.SetReply(evt)
|
||||||
}
|
// }
|
||||||
return
|
// return
|
||||||
}
|
//}
|
||||||
|
|
||||||
func (portal *Portal) SetReplySkype(content *event.MessageEventContent, info skype.Resource) {
|
func (portal *Portal) SetReplySkype(content *event.MessageEventContent, info skype.Resource) {
|
||||||
if len(info.Id) == 0 {
|
if len(info.Id) == 0 {
|
||||||
|
@ -1347,32 +1343,31 @@ func (portal *Portal) HandleMessageRevokeSkype(user *User, message skype.Resourc
|
||||||
msg.Delete()
|
msg.Delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//func (portal *Portal) HandleMessageRevoke(user *User, message whatsappExt.MessageRevocation) {
|
||||||
func (portal *Portal) HandleMessageRevoke(user *User, message whatsappExt.MessageRevocation) {
|
// msg := portal.bridge.DB.Message.GetByJID(portal.Key, message.Id)
|
||||||
msg := portal.bridge.DB.Message.GetByJID(portal.Key, message.Id)
|
// if msg == nil {
|
||||||
if msg == nil {
|
// return
|
||||||
return
|
// }
|
||||||
}
|
// var intent *appservice.IntentAPI
|
||||||
var intent *appservice.IntentAPI
|
// if message.FromMe {
|
||||||
if message.FromMe {
|
// if portal.IsPrivateChat() {
|
||||||
if portal.IsPrivateChat() {
|
// intent = portal.bridge.GetPuppetByJID(user.JID).CustomIntent()
|
||||||
intent = portal.bridge.GetPuppetByJID(user.JID).CustomIntent()
|
// } else {
|
||||||
} else {
|
// intent = portal.bridge.GetPuppetByJID(user.JID).IntentFor(portal)
|
||||||
intent = portal.bridge.GetPuppetByJID(user.JID).IntentFor(portal)
|
// }
|
||||||
}
|
// } else if len(message.Participant) > 0 {
|
||||||
} else if len(message.Participant) > 0 {
|
// intent = portal.bridge.GetPuppetByJID(message.Participant).IntentFor(portal)
|
||||||
intent = portal.bridge.GetPuppetByJID(message.Participant).IntentFor(portal)
|
// }
|
||||||
}
|
// if intent == nil {
|
||||||
if intent == nil {
|
// intent = portal.MainIntent()
|
||||||
intent = portal.MainIntent()
|
// }
|
||||||
}
|
// _, err := intent.RedactEvent(portal.MXID, msg.MXID)
|
||||||
_, err := intent.RedactEvent(portal.MXID, msg.MXID)
|
// if err != nil {
|
||||||
if err != nil {
|
// portal.log.Errorln("Failed to redact %s: %v", msg.JID, err)
|
||||||
portal.log.Errorln("Failed to redact %s: %v", msg.JID, err)
|
// return
|
||||||
return
|
// }
|
||||||
}
|
// msg.Delete()
|
||||||
msg.Delete()
|
//}
|
||||||
}
|
|
||||||
|
|
||||||
func (portal *Portal) HandleFakeMessage(source *User, message FakeMessage) {
|
func (portal *Portal) HandleFakeMessage(source *User, message FakeMessage) {
|
||||||
if portal.isRecentlyHandled(message.ID) {
|
if portal.isRecentlyHandled(message.ID) {
|
||||||
|
@ -1484,13 +1479,12 @@ func (portal *Portal) HandleLocationMessageSkype(source *User, message skype.Res
|
||||||
geo := fmt.Sprintf("geo:%.6f,%.6f", float32(latitude)/1000000, float32(longitude)/1000000)
|
geo := fmt.Sprintf("geo:%.6f,%.6f", float32(latitude)/1000000, float32(longitude)/1000000)
|
||||||
content := &event.MessageEventContent{
|
content := &event.MessageEventContent{
|
||||||
MsgType: event.MsgText,
|
MsgType: event.MsgText,
|
||||||
Body: fmt.Sprintf("Location: <a href='%s'>%s</a>%s<br>", locationMessage.A.Href, locationMessage.Address, geo),
|
Body: fmt.Sprintf("Location: <a href='%s'>%s</a>%s<br>\n", locationMessage.A.Href, locationMessage.Address, geo),
|
||||||
Format: event.FormatHTML,
|
Format: event.FormatHTML,
|
||||||
FormattedBody: fmt.Sprintf("Location: <a href='%s'>%s</a>%s<br>", locationMessage.A.Href, locationMessage.Address, geo),
|
FormattedBody: fmt.Sprintf("Location: <a href='%s'>%s</a>%s<br>\n", locationMessage.A.Href, locationMessage.Address, geo),
|
||||||
GeoURI: geo,
|
GeoURI: geo,
|
||||||
}
|
}
|
||||||
|
|
||||||
//portal.SetReply(content, message.ContextInfo)
|
|
||||||
portal.SetReplySkype(content, message)
|
portal.SetReplySkype(content, message)
|
||||||
|
|
||||||
_, _ = intent.UserTyping(portal.MXID, false, 0)
|
_, _ = intent.UserTyping(portal.MXID, false, 0)
|
||||||
|
@ -1579,7 +1573,7 @@ func (portal *Portal) HandleMediaMessageSkype(source *User, download func(conn *
|
||||||
}
|
}
|
||||||
|
|
||||||
data, mediaMessage, err := download(source.Conn.Conn, mediaType)
|
data, mediaMessage, err := download(source.Conn.Conn, mediaType)
|
||||||
if err == whatsapp.ErrMediaDownloadFailedWith404 || err == whatsapp.ErrMediaDownloadFailedWith410 {
|
if err == skype.ErrMediaDownloadFailedWith404 || err == skype.ErrMediaDownloadFailedWith410 {
|
||||||
portal.log.Warnfln("Failed to download media for %s: %v. Calling LoadMediaInfo and retrying download...", info.Id, err)
|
portal.log.Warnfln("Failed to download media for %s: %v. Calling LoadMediaInfo and retrying download...", info.Id, err)
|
||||||
//_, err = source.Conn.LoadMediaInfo(info.RemoteJid, info.Id, info.FromMe)
|
//_, err = source.Conn.LoadMediaInfo(info.RemoteJid, info.Id, info.FromMe)
|
||||||
//if err != nil {
|
//if err != nil {
|
||||||
|
@ -1588,7 +1582,7 @@ func (portal *Portal) HandleMediaMessageSkype(source *User, download func(conn *
|
||||||
//}
|
//}
|
||||||
data, mediaMessage, err = download(source.Conn.Conn, mediaType)
|
data, mediaMessage, err = download(source.Conn.Conn, mediaType)
|
||||||
}
|
}
|
||||||
if err == whatsapp.ErrNoURLPresent {
|
if err == skype.ErrNoURLPresent {
|
||||||
portal.log.Debugfln("No URL present error for media message %s, ignoring...", info.Id)
|
portal.log.Debugfln("No URL present error for media message %s, ignoring...", info.Id)
|
||||||
return
|
return
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
|
@ -1819,54 +1813,6 @@ func (portal *Portal) preprocessMatrixMediaSkype(relaybotFormatted bool, content
|
||||||
return caption, uint64(len(data)), data
|
return caption, uint64(len(data)), data
|
||||||
}
|
}
|
||||||
|
|
||||||
func (portal *Portal) preprocessMatrixMedia(sender *User, relaybotFormatted bool, content *event.MessageEventContent, eventID id.EventID, mediaType whatsapp.MediaType) *MediaUpload {
|
|
||||||
//var caption string
|
|
||||||
//if relaybotFormatted {
|
|
||||||
// caption = portal.bridge.Formatter.ParseMatrix(content.FormattedBody)
|
|
||||||
//}
|
|
||||||
|
|
||||||
var file *event.EncryptedFileInfo
|
|
||||||
rawMXC := content.URL
|
|
||||||
if content.File != nil {
|
|
||||||
file = content.File
|
|
||||||
rawMXC = file.URL
|
|
||||||
}
|
|
||||||
mxc, err := rawMXC.Parse()
|
|
||||||
if err != nil {
|
|
||||||
portal.log.Errorln("Malformed content URL in %s: %v", eventID, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
data, err := portal.MainIntent().DownloadBytes(mxc)
|
|
||||||
if err != nil {
|
|
||||||
portal.log.Errorfln("Failed to download media in %s: %v", eventID, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if file != nil {
|
|
||||||
data, err = file.Decrypt(data)
|
|
||||||
if err != nil {
|
|
||||||
portal.log.Errorfln("Failed to decrypt media in %s: %v", eventID, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//url, mediaKey, fileEncSHA256, fileSHA256, fileLength, err := sender.Conn.Upload(bytes.NewReader(data), mediaType)
|
|
||||||
//if err != nil {
|
|
||||||
// portal.log.Errorfln("Failed to upload media in %s: %v", eventID, err)
|
|
||||||
// return nil
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//return &MediaUpload{
|
|
||||||
// Caption: caption,
|
|
||||||
// URL: url,
|
|
||||||
// MediaKey: mediaKey,
|
|
||||||
// FileEncSHA256: fileEncSHA256,
|
|
||||||
// FileSHA256: fileSHA256,
|
|
||||||
// FileLength: fileLength,
|
|
||||||
// Thumbnail: portal.downloadThumbnail(content, eventID),
|
|
||||||
//}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type MediaUpload struct {
|
type MediaUpload struct {
|
||||||
Caption string
|
Caption string
|
||||||
URL string
|
URL string
|
||||||
|
@ -2098,145 +2044,6 @@ func (portal *Portal) convertMatrixMessageSkype(sender *User, evt *event.Event)
|
||||||
return info, sender, content
|
return info, sender, content
|
||||||
}
|
}
|
||||||
|
|
||||||
func (portal *Portal) convertMatrixMessage(sender *User, evt *event.Event) (*waProto.WebMessageInfo, *User, *event.MessageEventContent) {
|
|
||||||
content, ok := evt.Content.Parsed.(*event.MessageEventContent)
|
|
||||||
if !ok {
|
|
||||||
portal.log.Debugfln("Failed to handle event %s: unexpected parsed content type %T", evt.ID, evt.Content.Parsed)
|
|
||||||
return nil, sender, content
|
|
||||||
}
|
|
||||||
|
|
||||||
ts := uint64(evt.Timestamp / 1000)
|
|
||||||
status := waProto.WebMessageInfo_ERROR
|
|
||||||
fromMe := true
|
|
||||||
info := &waProto.WebMessageInfo{
|
|
||||||
Key: &waProto.MessageKey{
|
|
||||||
FromMe: &fromMe,
|
|
||||||
Id: makeMessageID(),
|
|
||||||
RemoteJid: &portal.Key.JID,
|
|
||||||
},
|
|
||||||
MessageTimestamp: &ts,
|
|
||||||
Message: &waProto.Message{},
|
|
||||||
Status: &status,
|
|
||||||
}
|
|
||||||
//ctxInfo := &waProto.ContextInfo{}
|
|
||||||
replyToID := content.GetReplyTo()
|
|
||||||
if len(replyToID) > 0 {
|
|
||||||
content.RemoveReplyFallback()
|
|
||||||
msg := portal.bridge.DB.Message.GetByMXID(replyToID)
|
|
||||||
//if msg != nil && msg.Content != nil {
|
|
||||||
if msg != nil {
|
|
||||||
//ctxInfo.StanzaId = &msg.JID
|
|
||||||
//ctxInfo.Participant = &msg.Sender
|
|
||||||
//ctxInfo.QuotedMessage = msg.Content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
relaybotFormatted := false
|
|
||||||
if sender.NeedsRelaybot(portal) {
|
|
||||||
if !portal.HasRelaybot() {
|
|
||||||
if sender.HasSession() {
|
|
||||||
portal.log.Debugln("Database says", sender.MXID, "not in chat and no relaybot, but trying to send anyway")
|
|
||||||
} else {
|
|
||||||
portal.log.Debugln("Ignoring message from", sender.MXID, "in chat with no relaybot")
|
|
||||||
return nil, sender, content
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
relaybotFormatted = portal.addRelaybotFormat(sender, content)
|
|
||||||
sender = portal.bridge.Relaybot
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if evt.Type == event.EventSticker {
|
|
||||||
content.MsgType = event.MsgImage
|
|
||||||
}
|
|
||||||
fmt.Println("convertMatrixMessage content.MsgType: ", content.MsgType)
|
|
||||||
switch content.MsgType {
|
|
||||||
case event.MsgText, event.MsgEmote, event.MsgNotice:
|
|
||||||
text := content.Body
|
|
||||||
if content.Format == event.FormatHTML {
|
|
||||||
text = portal.bridge.Formatter.ParseMatrix(content.FormattedBody)
|
|
||||||
}
|
|
||||||
if content.MsgType == event.MsgEmote && !relaybotFormatted {
|
|
||||||
text = "/me " + text
|
|
||||||
}
|
|
||||||
//ctxInfo.MentionedJid = mentionRegex.FindAllString(text, -1)
|
|
||||||
//for index, mention := range ctxInfo.MentionedJid {
|
|
||||||
// ctxInfo.MentionedJid[index] = mention[1:] + whatsappExt.NewUserSuffix
|
|
||||||
//}
|
|
||||||
//if ctxInfo.StanzaId != nil || ctxInfo.MentionedJid != nil {
|
|
||||||
// info.Message.ExtendedTextMessage = &waProto.ExtendedTextMessage{
|
|
||||||
// Text: &text,
|
|
||||||
// ContextInfo: ctxInfo,
|
|
||||||
// }
|
|
||||||
//} else {
|
|
||||||
// info.Message.Conversation = &text
|
|
||||||
//}
|
|
||||||
case event.MsgImage:
|
|
||||||
media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsapp.MediaImage)
|
|
||||||
if media == nil {
|
|
||||||
return nil, sender, content
|
|
||||||
}
|
|
||||||
info.Message.ImageMessage = &waProto.ImageMessage{
|
|
||||||
Caption: &media.Caption,
|
|
||||||
JpegThumbnail: media.Thumbnail,
|
|
||||||
Url: &media.URL,
|
|
||||||
MediaKey: media.MediaKey,
|
|
||||||
Mimetype: &content.GetInfo().MimeType,
|
|
||||||
FileEncSha256: media.FileEncSHA256,
|
|
||||||
FileSha256: media.FileSHA256,
|
|
||||||
FileLength: &media.FileLength,
|
|
||||||
}
|
|
||||||
case event.MsgVideo:
|
|
||||||
media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsapp.MediaVideo)
|
|
||||||
if media == nil {
|
|
||||||
return nil, sender, content
|
|
||||||
}
|
|
||||||
duration := uint32(content.GetInfo().Duration)
|
|
||||||
info.Message.VideoMessage = &waProto.VideoMessage{
|
|
||||||
Caption: &media.Caption,
|
|
||||||
JpegThumbnail: media.Thumbnail,
|
|
||||||
Url: &media.URL,
|
|
||||||
MediaKey: media.MediaKey,
|
|
||||||
Mimetype: &content.GetInfo().MimeType,
|
|
||||||
Seconds: &duration,
|
|
||||||
FileEncSha256: media.FileEncSHA256,
|
|
||||||
FileSha256: media.FileSHA256,
|
|
||||||
FileLength: &media.FileLength,
|
|
||||||
}
|
|
||||||
case event.MsgAudio:
|
|
||||||
media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsapp.MediaAudio)
|
|
||||||
if media == nil {
|
|
||||||
return nil, sender, content
|
|
||||||
}
|
|
||||||
duration := uint32(content.GetInfo().Duration)
|
|
||||||
info.Message.AudioMessage = &waProto.AudioMessage{
|
|
||||||
Url: &media.URL,
|
|
||||||
MediaKey: media.MediaKey,
|
|
||||||
Mimetype: &content.GetInfo().MimeType,
|
|
||||||
Seconds: &duration,
|
|
||||||
FileEncSha256: media.FileEncSHA256,
|
|
||||||
FileSha256: media.FileSHA256,
|
|
||||||
FileLength: &media.FileLength,
|
|
||||||
}
|
|
||||||
case event.MsgFile:
|
|
||||||
media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsapp.MediaDocument)
|
|
||||||
if media == nil {
|
|
||||||
return nil, sender, content
|
|
||||||
}
|
|
||||||
info.Message.DocumentMessage = &waProto.DocumentMessage{
|
|
||||||
Url: &media.URL,
|
|
||||||
FileName: &content.Body,
|
|
||||||
MediaKey: media.MediaKey,
|
|
||||||
Mimetype: &content.GetInfo().MimeType,
|
|
||||||
FileEncSha256: media.FileEncSHA256,
|
|
||||||
FileSha256: media.FileSHA256,
|
|
||||||
FileLength: &media.FileLength,
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
portal.log.Debugln("Unhandled Matrix event %s: unknown msgtype %s", evt.ID, content.MsgType)
|
|
||||||
return nil, sender, content
|
|
||||||
}
|
|
||||||
return info, sender, content
|
|
||||||
}
|
|
||||||
|
|
||||||
func (portal *Portal) wasMessageSent(sender *User, id string) bool {
|
func (portal *Portal) wasMessageSent(sender *User, id string) bool {
|
||||||
//_, err := sender.Conn.LoadMessagesAfter(portal.Key.JID, id, true, 0)
|
//_, err := sender.Conn.LoadMessagesAfter(portal.Key.JID, id, true, 0)
|
||||||
//if err != nil {
|
//if err != nil {
|
||||||
|
@ -2512,32 +2319,27 @@ func (portal *Portal) HandleMatrixLeave(sender *User) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (portal *Portal) HandleMatrixKick(sender *User, evt *event.Event) {
|
func (portal *Portal) HandleMatrixKick(sender *User, evt *event.Event) {
|
||||||
number, _:= portal.bridge.ParsePuppetMXID(id.UserID(evt.GetStateKey()))
|
jid, _:= portal.bridge.ParsePuppetMXID(id.UserID(evt.GetStateKey()))
|
||||||
puppet := portal.bridge.GetPuppetByMXID(id.UserID(evt.GetStateKey()))
|
puppet := portal.bridge.GetPuppetByJID(jid)
|
||||||
fmt.Println("HandleMatrixKick", puppet)
|
|
||||||
if puppet != nil {
|
if puppet != nil {
|
||||||
number = strings.Replace(number, skypeExt.NewUserSuffix, "", 1)
|
jid = strings.Replace(jid, skypeExt.NewUserSuffix, "", 1)
|
||||||
err := sender.Conn.HandleGroupKick(portal.Key.JID, []string{number})
|
err := sender.Conn.HandleGroupKick(portal.Key.JID, []string{jid})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
portal.log.Errorfln("Failed to kick %s from group as %s: %v", puppet.JID, sender.MXID, err)
|
portal.log.Errorfln("Failed to kick %s from group as %s: %v", puppet.JID, sender.MXID, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//portal.log.Infoln("Kick %s response: %s", puppet.JID, <-resp)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (portal *Portal) HandleMatrixInvite(sender *User, evt *event.Event) {
|
func (portal *Portal) HandleMatrixInvite(sender *User, evt *event.Event) {
|
||||||
number, _:= portal.bridge.ParsePuppetMXID(id.UserID(evt.GetStateKey()))
|
jid, _:= portal.bridge.ParsePuppetMXID(id.UserID(evt.GetStateKey()))
|
||||||
puppet := portal.bridge.GetPuppetByMXID(id.UserID(evt.GetStateKey()))
|
puppet := portal.bridge.GetPuppetByJID(jid)
|
||||||
fmt.Println("HandleMatrixInvite", puppet)
|
|
||||||
if puppet != nil {
|
if puppet != nil {
|
||||||
number = strings.Replace(number, "8:", "", 1)
|
jid = strings.Replace(jid, skypeExt.NewUserSuffix, "", 1)
|
||||||
number = strings.Replace(number, skypeExt.NewUserSuffix, "", 1)
|
err := sender.Conn.HandleGroupInvite(portal.Key.JID, []string{jid})
|
||||||
err := sender.Conn.HandleGroupInvite(portal.Key.JID, []string{number})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
portal.log.Errorfln("Failed to add %s to group as %s: %v", puppet.JID, sender.MXID, err)
|
portal.log.Errorfln("Failed to add %s to group as %s: %v", puppet.JID, sender.MXID, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//portal.log.Infoln("Add %s response: %s", puppet.JID, <-resp)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
puppet.go
10
puppet.go
|
@ -15,7 +15,7 @@ import (
|
||||||
|
|
||||||
"github.com/kelaresg/matrix-skype/database"
|
"github.com/kelaresg/matrix-skype/database"
|
||||||
"github.com/kelaresg/matrix-skype/types"
|
"github.com/kelaresg/matrix-skype/types"
|
||||||
"github.com/kelaresg/matrix-skype/whatsapp-ext"
|
//"github.com/kelaresg/matrix-skype/whatsapp-ext"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (bridge *Bridge) ParsePuppetMXID(mxid id.UserID) (types.SkypeID, bool) {
|
func (bridge *Bridge) ParsePuppetMXID(mxid id.UserID) (types.SkypeID, bool) {
|
||||||
|
@ -134,10 +134,8 @@ func (bridge *Bridge) NewPuppet(dbPuppet *database.Puppet) *Puppet {
|
||||||
|
|
||||||
MXID: id.NewUserID(
|
MXID: id.NewUserID(
|
||||||
bridge.Config.Bridge.FormatUsername(
|
bridge.Config.Bridge.FormatUsername(
|
||||||
// dbPuppet.JID,
|
|
||||||
//),
|
|
||||||
strings.Replace(
|
strings.Replace(
|
||||||
strings.Replace(dbPuppet.JID, whatsappExt.NewUserSuffix, "", 1),
|
strings.Replace(dbPuppet.JID, skypeExt.NewUserSuffix, "", 1),
|
||||||
":",
|
":",
|
||||||
"-",
|
"-",
|
||||||
-1,
|
-1,
|
||||||
|
@ -164,7 +162,7 @@ type Puppet struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (puppet *Puppet) PhoneNumber() string {
|
func (puppet *Puppet) PhoneNumber() string {
|
||||||
return strings.Replace(puppet.JID, whatsappExt.NewUserSuffix, "", 1)
|
return strings.Replace(puppet.JID, skypeExt.NewUserSuffix, "", 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (puppet *Puppet) IntentFor(portal *Portal) *appservice.IntentAPI {
|
func (puppet *Puppet) IntentFor(portal *Portal) *appservice.IntentAPI {
|
||||||
|
@ -272,7 +270,7 @@ func (puppet *Puppet) UpdateName(source *User, contact skype.Contact) bool {
|
||||||
|
|
||||||
func (puppet *Puppet) updatePortalMeta(meta func(portal *Portal)) {
|
func (puppet *Puppet) updatePortalMeta(meta func(portal *Portal)) {
|
||||||
if puppet.bridge.Config.Bridge.PrivateChatPortalMeta {
|
if puppet.bridge.Config.Bridge.PrivateChatPortalMeta {
|
||||||
jid := strings.Replace(puppet.JID, whatsappExt.NewUserSuffix, "", 1)
|
jid := strings.Replace(puppet.JID, skypeExt.NewUserSuffix, "", 1)
|
||||||
for _, portal := range puppet.bridge.GetAllPortalsByJID(jid) {
|
for _, portal := range puppet.bridge.GetAllPortalsByJID(jid) {
|
||||||
meta(portal)
|
meta(portal)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,8 @@ package skypeExt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
skype "github.com/kelaresg/go-skypeapi"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Rhymen/go-whatsapp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ChatUpdateCommand string
|
type ChatUpdateCommand string
|
||||||
|
@ -155,7 +154,7 @@ func (cud *ChatUpdateData) UnmarshalJSON(data []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChatUpdateHandler interface {
|
type ChatUpdateHandler interface {
|
||||||
whatsapp.Handler
|
skype.Handler
|
||||||
HandleChatUpdate(ChatUpdate)
|
HandleChatUpdate(ChatUpdate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,8 @@ package skypeExt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
skype "github.com/kelaresg/go-skypeapi"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Rhymen/go-whatsapp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type CommandType string
|
type CommandType string
|
||||||
|
@ -41,7 +40,7 @@ type Command struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type CommandHandler interface {
|
type CommandHandler interface {
|
||||||
whatsapp.Handler
|
skype.Handler
|
||||||
HandleCommand(Command)
|
HandleCommand(Command)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,154 +0,0 @@
|
||||||
package skypeExt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UserID represents a Matrix user ID.
|
|
||||||
// https://matrix.org/docs/spec/appendices#user-identifiers
|
|
||||||
type UserID string
|
|
||||||
|
|
||||||
func NewUserID(localpart, homeserver string) UserID {
|
|
||||||
return UserID(fmt.Sprintf("@%s:%s", localpart, homeserver))
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewEncodedUserID(localpart, homeserver string) UserID {
|
|
||||||
return NewUserID(EncodeUserLocalpart(localpart), homeserver)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse parses the user ID into the localpart and server name.
|
|
||||||
// See http://matrix.org/docs/spec/intro.html#user-identifiers
|
|
||||||
func (userID UserID) Parse() (localpart, homeserver string, err error) {
|
|
||||||
if len(userID) == 0 || userID[0] != '@' || !strings.ContainsRune(string(userID), ':') {
|
|
||||||
err = fmt.Errorf("%s is not a valid user id", userID)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
parts := strings.SplitN(string(userID), ":", 2)
|
|
||||||
localpart, homeserver = strings.TrimPrefix(parts[0], "@"), parts[1]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (userID UserID) ParseAndDecode() (localpart, homeserver string, err error) {
|
|
||||||
localpart, homeserver, err = userID.Parse()
|
|
||||||
if err == nil {
|
|
||||||
localpart, err = DecodeUserLocalpart(localpart)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (userID UserID) String() string {
|
|
||||||
return string(userID)
|
|
||||||
}
|
|
||||||
|
|
||||||
const lowerhex = "0123456789abcdef"
|
|
||||||
|
|
||||||
// encode the given byte using quoted-printable encoding (e.g "=2f")
|
|
||||||
// and writes it to the buffer
|
|
||||||
// See https://golang.org/src/mime/quotedprintable/writer.go
|
|
||||||
func encode(buf *bytes.Buffer, b byte) {
|
|
||||||
buf.WriteByte('=')
|
|
||||||
buf.WriteByte(lowerhex[b>>4])
|
|
||||||
buf.WriteByte(lowerhex[b&0x0f])
|
|
||||||
}
|
|
||||||
|
|
||||||
// escape the given alpha character and writes it to the buffer
|
|
||||||
func escape(buf *bytes.Buffer, b byte) {
|
|
||||||
buf.WriteByte('_')
|
|
||||||
if b == '_' {
|
|
||||||
buf.WriteByte('_') // another _
|
|
||||||
} else {
|
|
||||||
buf.WriteByte(b + 0x20) // ASCII shift A-Z to a-z
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func shouldEncode(b byte) bool {
|
|
||||||
return b != '-' && b != '.' && b != '_' && !(b >= '0' && b <= '9') && !(b >= 'a' && b <= 'z') && !(b >= 'A' && b <= 'Z')
|
|
||||||
}
|
|
||||||
|
|
||||||
func shouldEscape(b byte) bool {
|
|
||||||
return (b >= 'A' && b <= 'Z') || b == '_'
|
|
||||||
}
|
|
||||||
|
|
||||||
func isValidByte(b byte) bool {
|
|
||||||
return isValidEscapedChar(b) || (b >= '0' && b <= '9') || b == '.' || b == '=' || b == '-'
|
|
||||||
}
|
|
||||||
|
|
||||||
func isValidEscapedChar(b byte) bool {
|
|
||||||
return b == '_' || (b >= 'a' && b <= 'z')
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeUserLocalpart encodes the given string into Matrix-compliant user ID localpart form.
|
|
||||||
// See http://matrix.org/docs/spec/intro.html#mapping-from-other-character-sets
|
|
||||||
//
|
|
||||||
// This returns a string with only the characters "a-z0-9._=-". The uppercase range A-Z
|
|
||||||
// are encoded using leading underscores ("_"). Characters outside the aforementioned ranges
|
|
||||||
// (including literal underscores ("_") and equals ("=")) are encoded as UTF8 code points (NOT NCRs)
|
|
||||||
// and converted to lower-case hex with a leading "=". For example:
|
|
||||||
// Alph@Bet_50up => _alph=40_bet=5f50up
|
|
||||||
func EncodeUserLocalpart(str string) string {
|
|
||||||
strBytes := []byte(str)
|
|
||||||
var outputBuffer bytes.Buffer
|
|
||||||
for _, b := range strBytes {
|
|
||||||
if shouldEncode(b) {
|
|
||||||
encode(&outputBuffer, b)
|
|
||||||
} else if shouldEscape(b) {
|
|
||||||
escape(&outputBuffer, b)
|
|
||||||
} else {
|
|
||||||
outputBuffer.WriteByte(b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return outputBuffer.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeUserLocalpart decodes the given string back into the original input string.
|
|
||||||
// Returns an error if the given string is not a valid user ID localpart encoding.
|
|
||||||
// See http://matrix.org/docs/spec/intro.html#mapping-from-other-character-sets
|
|
||||||
//
|
|
||||||
// This decodes quoted-printable bytes back into UTF8, and unescapes casing. For
|
|
||||||
// example:
|
|
||||||
// _alph=40_bet=5f50up => Alph@Bet_50up
|
|
||||||
// Returns an error if the input string contains characters outside the
|
|
||||||
// range "a-z0-9._=-", has an invalid quote-printable byte (e.g. not hex), or has
|
|
||||||
// an invalid _ escaped byte (e.g. "_5").
|
|
||||||
func DecodeUserLocalpart(str string) (string, error) {
|
|
||||||
strBytes := []byte(str)
|
|
||||||
var outputBuffer bytes.Buffer
|
|
||||||
for i := 0; i < len(strBytes); i++ {
|
|
||||||
b := strBytes[i]
|
|
||||||
if !isValidByte(b) {
|
|
||||||
return "", fmt.Errorf("Byte pos %d: Invalid byte", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b == '_' { // next byte is a-z and should be upper-case or is another _ and should be a literal _
|
|
||||||
if i+1 >= len(strBytes) {
|
|
||||||
return "", fmt.Errorf("Byte pos %d: expected _[a-z_] encoding but ran out of string", i)
|
|
||||||
}
|
|
||||||
if !isValidEscapedChar(strBytes[i+1]) { // invalid escaping
|
|
||||||
return "", fmt.Errorf("Byte pos %d: expected _[a-z_] encoding", i)
|
|
||||||
}
|
|
||||||
if strBytes[i+1] == '_' {
|
|
||||||
outputBuffer.WriteByte('_')
|
|
||||||
} else {
|
|
||||||
outputBuffer.WriteByte(strBytes[i+1] - 0x20) // ASCII shift a-z to A-Z
|
|
||||||
}
|
|
||||||
i++ // skip next byte since we just handled it
|
|
||||||
} else if b == '=' { // next 2 bytes are hex and should be buffered ready to be read as utf8
|
|
||||||
if i+2 >= len(strBytes) {
|
|
||||||
return "", fmt.Errorf("Byte pos: %d: expected quote-printable encoding but ran out of string", i)
|
|
||||||
}
|
|
||||||
dst := make([]byte, 1)
|
|
||||||
_, err := hex.Decode(dst, strBytes[i+1:i+3])
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
outputBuffer.WriteByte(dst[0])
|
|
||||||
i += 2 // skip next 2 bytes since we just handled it
|
|
||||||
} else { // pass through
|
|
||||||
outputBuffer.WriteByte(b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return outputBuffer.String(), nil
|
|
||||||
}
|
|
244
user.go
244
user.go
|
@ -12,20 +12,20 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
//"github.com/pkg/errors"
|
||||||
"github.com/skip2/go-qrcode"
|
"github.com/skip2/go-qrcode"
|
||||||
log "maunium.net/go/maulogger/v2"
|
log "maunium.net/go/maulogger/v2"
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
|
|
||||||
"github.com/Rhymen/go-whatsapp"
|
//"github.com/Rhymen/go-whatsapp"
|
||||||
waProto "github.com/Rhymen/go-whatsapp/binary/proto"
|
//waProto "github.com/Rhymen/go-whatsapp/binary/proto"
|
||||||
|
|
||||||
"maunium.net/go/mautrix/event"
|
"maunium.net/go/mautrix/event"
|
||||||
"maunium.net/go/mautrix/id"
|
"maunium.net/go/mautrix/id"
|
||||||
|
|
||||||
"github.com/kelaresg/matrix-skype/database"
|
"github.com/kelaresg/matrix-skype/database"
|
||||||
"github.com/kelaresg/matrix-skype/types"
|
"github.com/kelaresg/matrix-skype/types"
|
||||||
"github.com/kelaresg/matrix-skype/whatsapp-ext"
|
//"github.com/kelaresg/matrix-skype/whatsapp-ext"
|
||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
|
@ -183,7 +183,7 @@ func (user *User) GetManagementRoom() id.RoomID {
|
||||||
return user.ManagementRoom
|
return user.ManagementRoom
|
||||||
}
|
}
|
||||||
resp, err := user.bridge.Bot.CreateRoom(&mautrix.ReqCreateRoom{
|
resp, err := user.bridge.Bot.CreateRoom(&mautrix.ReqCreateRoom{
|
||||||
Topic: "WhatsApp bridge notices",
|
Topic: "Skype bridge notices",
|
||||||
IsDirect: true,
|
IsDirect: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -587,13 +587,13 @@ func (user *User) syncPortals(chatMap map[string]skype.Conversation, createAll b
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) HandleContactList(contacts []whatsapp.Contact) {
|
//func (user *User) HandleContactList(contacts []whatsapp.Contact) {
|
||||||
contactMap := make(map[string]whatsapp.Contact)
|
// contactMap := make(map[string]whatsapp.Contact)
|
||||||
for _, contact := range contacts {
|
// for _, contact := range contacts {
|
||||||
contactMap[contact.Jid] = contact
|
// contactMap[contact.Jid] = contact
|
||||||
}
|
// }
|
||||||
// go user.syncPuppets(contactMap)
|
// // go user.syncPuppets(contactMap)
|
||||||
}
|
//}
|
||||||
|
|
||||||
func (user *User) syncPuppets(contacts map[string]skype.Contact) {
|
func (user *User) syncPuppets(contacts map[string]skype.Contact) {
|
||||||
if contacts == nil {
|
if contacts == nil {
|
||||||
|
@ -636,20 +636,6 @@ func (user *User) updateLastConnectionIfNecessary() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) HandleError(err error) {
|
func (user *User) HandleError(err error) {
|
||||||
if errors.Cause(err) != whatsapp.ErrInvalidWsData {
|
|
||||||
user.log.Errorfln("WhatsApp error: %v", err)
|
|
||||||
}
|
|
||||||
if closed, ok := err.(*whatsapp.ErrConnectionClosed); ok {
|
|
||||||
if closed.Code == 1000 && user.cleanDisconnection {
|
|
||||||
user.cleanDisconnection = false
|
|
||||||
user.log.Infoln("Clean disconnection by server")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
go user.tryReconnect(fmt.Sprintf("Your WhatsApp connection was closed with websocket status code %d", closed.Code))
|
|
||||||
} else if failed, ok := err.(*whatsapp.ErrConnectionFailed); ok {
|
|
||||||
user.ConnectionErrors++
|
|
||||||
go user.tryReconnect(fmt.Sprintf("Your WhatsApp connection failed: %v", failed.Err))
|
|
||||||
}
|
|
||||||
// Otherwise unknown error, probably mostly harmless
|
// Otherwise unknown error, probably mostly harmless
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -762,21 +748,21 @@ func (user *User) HandleImageMessage(message skype.Resource) {
|
||||||
user.putMessage(PortalMessage{message.Jid, user, message, uint64(message.Timestamp)})
|
user.putMessage(PortalMessage{message.Jid, user, message, uint64(message.Timestamp)})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) HandleStickerMessage(message whatsapp.StickerMessage) {
|
//func (user *User) HandleStickerMessage(message whatsapp.StickerMessage) {
|
||||||
user.putMessage(PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp})
|
// user.putMessage(PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp})
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
func (user *User) HandleVideoMessage(message whatsapp.VideoMessage) {
|
//func (user *User) HandleVideoMessage(message whatsapp.VideoMessage) {
|
||||||
user.putMessage(PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp})
|
// user.putMessage(PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp})
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
func (user *User) HandleAudioMessage(message whatsapp.AudioMessage) {
|
//func (user *User) HandleAudioMessage(message whatsapp.AudioMessage) {
|
||||||
user.putMessage(PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp})
|
// user.putMessage(PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp})
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
func (user *User) HandleDocumentMessage(message whatsapp.DocumentMessage) {
|
//func (user *User) HandleDocumentMessage(message whatsapp.DocumentMessage) {
|
||||||
user.putMessage(PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp})
|
// user.putMessage(PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp})
|
||||||
}
|
//}
|
||||||
|
|
||||||
func (user *User) HandleContactMessage(message skype.Resource) {
|
func (user *User) HandleContactMessage(message skype.Resource) {
|
||||||
user.log.Debugf("HandleContactMessage: ", message)
|
user.log.Debugf("HandleContactMessage: ", message)
|
||||||
|
@ -798,40 +784,40 @@ type FakeMessage struct {
|
||||||
Alert bool
|
Alert bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) HandleCallInfo(info whatsappExt.CallInfo) {
|
//func (user *User) HandleCallInfo(info whatsappExt.CallInfo) {
|
||||||
if info.Data != nil {
|
// if info.Data != nil {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
data := FakeMessage{
|
// data := FakeMessage{
|
||||||
ID: info.ID,
|
// ID: info.ID,
|
||||||
}
|
// }
|
||||||
switch info.Type {
|
// switch info.Type {
|
||||||
case whatsappExt.CallOffer:
|
// case whatsappExt.CallOffer:
|
||||||
if !user.bridge.Config.Bridge.CallNotices.Start {
|
// if !user.bridge.Config.Bridge.CallNotices.Start {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
data.Text = "Incoming call"
|
// data.Text = "Incoming call"
|
||||||
data.Alert = true
|
// data.Alert = true
|
||||||
case whatsappExt.CallOfferVideo:
|
// case whatsappExt.CallOfferVideo:
|
||||||
if !user.bridge.Config.Bridge.CallNotices.Start {
|
// if !user.bridge.Config.Bridge.CallNotices.Start {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
data.Text = "Incoming video call"
|
// data.Text = "Incoming video call"
|
||||||
data.Alert = true
|
// data.Alert = true
|
||||||
case whatsappExt.CallTerminate:
|
// case whatsappExt.CallTerminate:
|
||||||
if !user.bridge.Config.Bridge.CallNotices.End {
|
// if !user.bridge.Config.Bridge.CallNotices.End {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
data.Text = "Call ended"
|
// data.Text = "Call ended"
|
||||||
data.ID += "E"
|
// data.ID += "E"
|
||||||
default:
|
// default:
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
portal := user.GetPortalByJID(info.From)
|
// portal := user.GetPortalByJID(info.From)
|
||||||
if portal != nil {
|
// if portal != nil {
|
||||||
portal.messages <- PortalMessage{info.From, user, data, 0}
|
// portal.messages <- PortalMessage{info.From, user, data, 0}
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
func (user *User) HandleTypingStatus(info skype.Resource) {
|
func (user *User) HandleTypingStatus(info skype.Resource) {
|
||||||
sendId := info.SendId + skypeExt.NewUserSuffix
|
sendId := info.SendId + skypeExt.NewUserSuffix
|
||||||
|
@ -904,62 +890,62 @@ func (user *User) HandlePresence(info skype.Resource) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) HandlePresenceWA(info whatsappExt.Presence) {
|
//func (user *User) HandlePresenceWA(info whatsappExt.Presence) {
|
||||||
puppet := user.bridge.GetPuppetByJID(info.SenderJID)
|
// puppet := user.bridge.GetPuppetByJID(info.SenderJID)
|
||||||
switch info.Status {
|
// switch info.Status {
|
||||||
case whatsapp.PresenceUnavailable:
|
// case whatsapp.PresenceUnavailable:
|
||||||
_ = puppet.DefaultIntent().SetPresence("offline")
|
// _ = puppet.DefaultIntent().SetPresence("offline")
|
||||||
case whatsapp.PresenceAvailable:
|
// case whatsapp.PresenceAvailable:
|
||||||
if len(puppet.typingIn) > 0 && puppet.typingAt+15 > time.Now().Unix() {
|
// if len(puppet.typingIn) > 0 && puppet.typingAt+15 > time.Now().Unix() {
|
||||||
portal := user.bridge.GetPortalByMXID(puppet.typingIn)
|
// portal := user.bridge.GetPortalByMXID(puppet.typingIn)
|
||||||
_, _ = puppet.IntentFor(portal).UserTyping(puppet.typingIn, false, 0)
|
// _, _ = puppet.IntentFor(portal).UserTyping(puppet.typingIn, false, 0)
|
||||||
puppet.typingIn = ""
|
// puppet.typingIn = ""
|
||||||
puppet.typingAt = 0
|
// puppet.typingAt = 0
|
||||||
}
|
// }
|
||||||
_ = puppet.DefaultIntent().SetPresence("online")
|
// _ = puppet.DefaultIntent().SetPresence("online")
|
||||||
case whatsapp.PresenceComposing:
|
// case whatsapp.PresenceComposing:
|
||||||
portal := user.GetPortalByJID(info.JID)
|
// portal := user.GetPortalByJID(info.JID)
|
||||||
if len(puppet.typingIn) > 0 && puppet.typingAt+15 > time.Now().Unix() {
|
// if len(puppet.typingIn) > 0 && puppet.typingAt+15 > time.Now().Unix() {
|
||||||
if puppet.typingIn == portal.MXID {
|
// if puppet.typingIn == portal.MXID {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
_, _ = puppet.IntentFor(portal).UserTyping(puppet.typingIn, false, 0)
|
// _, _ = puppet.IntentFor(portal).UserTyping(puppet.typingIn, false, 0)
|
||||||
}
|
// }
|
||||||
puppet.typingIn = portal.MXID
|
// puppet.typingIn = portal.MXID
|
||||||
puppet.typingAt = time.Now().Unix()
|
// puppet.typingAt = time.Now().Unix()
|
||||||
_, _ = puppet.IntentFor(portal).UserTyping(portal.MXID, true, 15*1000)
|
// _, _ = puppet.IntentFor(portal).UserTyping(portal.MXID, true, 15*1000)
|
||||||
_ = puppet.DefaultIntent().SetPresence("online")
|
// _ = puppet.DefaultIntent().SetPresence("online")
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
func (user *User) HandleMsgInfo(info whatsappExt.MsgInfo) {
|
//func (user *User) HandleMsgInfo(info whatsappExt.MsgInfo) {
|
||||||
if (info.Command == whatsappExt.MsgInfoCommandAck || info.Command == whatsappExt.MsgInfoCommandAcks) && info.Acknowledgement == whatsappExt.AckMessageRead {
|
// if (info.Command == whatsappExt.MsgInfoCommandAck || info.Command == whatsappExt.MsgInfoCommandAcks) && info.Acknowledgement == whatsappExt.AckMessageRead {
|
||||||
portal := user.GetPortalByJID(info.ToJID)
|
// portal := user.GetPortalByJID(info.ToJID)
|
||||||
if len(portal.MXID) == 0 {
|
// if len(portal.MXID) == 0 {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
go func() {
|
// go func() {
|
||||||
intent := user.bridge.GetPuppetByJID(info.SenderJID).IntentFor(portal)
|
// intent := user.bridge.GetPuppetByJID(info.SenderJID).IntentFor(portal)
|
||||||
for _, id := range info.IDs {
|
// for _, id := range info.IDs {
|
||||||
msg := user.bridge.DB.Message.GetByJID(portal.Key, id)
|
// msg := user.bridge.DB.Message.GetByJID(portal.Key, id)
|
||||||
if msg == nil {
|
// if msg == nil {
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
err := intent.MarkRead(portal.MXID, msg.MXID)
|
// err := intent.MarkRead(portal.MXID, msg.MXID)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
user.log.Warnln("Failed to mark message %s as read by %s: %v", msg.MXID, info.SenderJID, err)
|
// user.log.Warnln("Failed to mark message %s as read by %s: %v", msg.MXID, info.SenderJID, err)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}()
|
// }()
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
func (user *User) HandleCommand(cmd skypeExt.Command) {
|
func (user *User) HandleCommand(cmd skypeExt.Command) {
|
||||||
switch cmd.Type {
|
switch cmd.Type {
|
||||||
case skypeExt.CommandPicture:
|
case skypeExt.CommandPicture:
|
||||||
if strings.HasSuffix(cmd.JID, whatsappExt.NewUserSuffix) {
|
if strings.HasSuffix(cmd.JID, skypeExt.NewUserSuffix) {
|
||||||
puppet := user.bridge.GetPuppetByJID(cmd.JID)
|
puppet := user.bridge.GetPuppetByJID(cmd.JID)
|
||||||
go puppet.UpdateAvatar(user, cmd.ProfilePicInfo)
|
go puppet.UpdateAvatar(user, cmd.ProfilePicInfo)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1145,9 +1131,9 @@ func (user *User) HandleJsonMessage(message string) {
|
||||||
user.updateLastConnectionIfNecessary()
|
user.updateLastConnectionIfNecessary()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) HandleRawMessage(message *waProto.WebMessageInfo) {
|
//func (user *User) HandleRawMessage(message *waProto.WebMessageInfo) {
|
||||||
user.updateLastConnectionIfNecessary()
|
// user.updateLastConnectionIfNecessary()
|
||||||
}
|
//}
|
||||||
|
|
||||||
func (user *User) NeedsRelaybot(portal *Portal) bool {
|
func (user *User) NeedsRelaybot(portal *Portal) bool {
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -1,72 +0,0 @@
|
||||||
// matrix-skype - A Matrix-WhatsApp puppeting bridge.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package whatsappExt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Rhymen/go-whatsapp"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CallInfoType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
CallOffer CallInfoType = "offer"
|
|
||||||
CallOfferVideo CallInfoType = "offer_video"
|
|
||||||
CallTransport CallInfoType = "transport"
|
|
||||||
CallRelayLatency CallInfoType = "relaylatency"
|
|
||||||
CallTerminate CallInfoType = "terminate"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CallInfo struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Type CallInfoType `json:"type"`
|
|
||||||
From string `json:"from"`
|
|
||||||
|
|
||||||
Platform string `json:"platform"`
|
|
||||||
Version []int `json:"version"`
|
|
||||||
|
|
||||||
Data [][]interface{} `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CallInfoHandler interface {
|
|
||||||
whatsapp.Handler
|
|
||||||
HandleCallInfo(CallInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ext *ExtendedConn) handleMessageCall(message []byte) {
|
|
||||||
var event CallInfo
|
|
||||||
err := json.Unmarshal(message, &event)
|
|
||||||
if err != nil {
|
|
||||||
ext.jsonParseError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
event.From = strings.Replace(event.From, OldUserSuffix, NewUserSuffix, 1)
|
|
||||||
for _, handler := range ext.handlers {
|
|
||||||
callInfoHandler, ok := handler.(CallInfoHandler)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ext.shouldCallSynchronously(callInfoHandler) {
|
|
||||||
callInfoHandler.HandleCallInfo(event)
|
|
||||||
} else {
|
|
||||||
go callInfoHandler.HandleCallInfo(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,174 +0,0 @@
|
||||||
// matrix-skype - A Matrix-WhatsApp puppeting bridge.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package whatsappExt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Rhymen/go-whatsapp"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ChatUpdateCommand string
|
|
||||||
|
|
||||||
const (
|
|
||||||
ChatUpdateCommandAction ChatUpdateCommand = "action"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ChatUpdate struct {
|
|
||||||
JID string `json:"id"`
|
|
||||||
Command ChatUpdateCommand `json:"cmd"`
|
|
||||||
Data ChatUpdateData `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ChatActionType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
ChatActionNameChange ChatActionType = "subject"
|
|
||||||
ChatActionAddTopic ChatActionType = "desc_add"
|
|
||||||
ChatActionRemoveTopic ChatActionType = "desc_remove"
|
|
||||||
ChatActionRestrict ChatActionType = "restrict"
|
|
||||||
ChatActionAnnounce ChatActionType = "announce"
|
|
||||||
ChatActionPromote ChatActionType = "promote"
|
|
||||||
ChatActionDemote ChatActionType = "demote"
|
|
||||||
ChatActionRemove ChatActionType = "remove"
|
|
||||||
ChatActionAdd ChatActionType = "add"
|
|
||||||
ChatActionIntroduce ChatActionType = "introduce"
|
|
||||||
ChatActionCreate ChatActionType = "create"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ChatUpdateData struct {
|
|
||||||
Action ChatActionType
|
|
||||||
SenderJID string
|
|
||||||
|
|
||||||
NameChange struct {
|
|
||||||
Name string `json:"subject"`
|
|
||||||
SetAt int64 `json:"s_t"`
|
|
||||||
SetBy string `json:"s_o"`
|
|
||||||
}
|
|
||||||
|
|
||||||
AddTopic struct {
|
|
||||||
Topic string `json:"desc"`
|
|
||||||
ID string `json:"descId"`
|
|
||||||
SetAt int64 `json:"descTime"`
|
|
||||||
}
|
|
||||||
|
|
||||||
RemoveTopic struct {
|
|
||||||
ID string `json:"descId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
Restrict bool
|
|
||||||
|
|
||||||
Announce bool
|
|
||||||
|
|
||||||
PermissionChange struct {
|
|
||||||
JIDs []string `json:"participants"`
|
|
||||||
}
|
|
||||||
|
|
||||||
MemberAction struct {
|
|
||||||
JIDs []string `json:"participants"`
|
|
||||||
}
|
|
||||||
|
|
||||||
Create struct {
|
|
||||||
Creation int64 `json:"creation"`
|
|
||||||
Name string `json:"subject"`
|
|
||||||
SetAt int64 `json:"s_t"`
|
|
||||||
SetBy string `json:"s_o"`
|
|
||||||
Admins []string `json:"admins"`
|
|
||||||
SuperAdmins []string `json:"superadmins"`
|
|
||||||
Regulars []string `json:"regulars"`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cud *ChatUpdateData) UnmarshalJSON(data []byte) error {
|
|
||||||
var arr []json.RawMessage
|
|
||||||
err := json.Unmarshal(data, &arr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
} else if len(arr) < 3 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal(arr[0], &cud.Action)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal(arr[1], &cud.SenderJID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cud.SenderJID = strings.Replace(cud.SenderJID, OldUserSuffix, NewUserSuffix, 1)
|
|
||||||
|
|
||||||
var unmarshalTo interface{}
|
|
||||||
switch cud.Action {
|
|
||||||
case ChatActionNameChange:
|
|
||||||
unmarshalTo = &cud.NameChange
|
|
||||||
case ChatActionAddTopic:
|
|
||||||
unmarshalTo = &cud.AddTopic
|
|
||||||
case ChatActionRemoveTopic:
|
|
||||||
unmarshalTo = &cud.RemoveTopic
|
|
||||||
case ChatActionRestrict:
|
|
||||||
unmarshalTo = &cud.Restrict
|
|
||||||
case ChatActionAnnounce:
|
|
||||||
unmarshalTo = &cud.Announce
|
|
||||||
case ChatActionPromote, ChatActionDemote:
|
|
||||||
unmarshalTo = &cud.PermissionChange
|
|
||||||
case ChatActionAdd, ChatActionRemove:
|
|
||||||
unmarshalTo = &cud.MemberAction
|
|
||||||
case ChatActionCreate:
|
|
||||||
unmarshalTo = &cud.Create
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(arr[2], unmarshalTo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cud.NameChange.SetBy = strings.Replace(cud.NameChange.SetBy, OldUserSuffix, NewUserSuffix, 1)
|
|
||||||
for index, jid := range cud.PermissionChange.JIDs {
|
|
||||||
cud.PermissionChange.JIDs[index] = strings.Replace(jid, OldUserSuffix, NewUserSuffix, 1)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ChatUpdateHandler interface {
|
|
||||||
whatsapp.Handler
|
|
||||||
HandleChatUpdate(ChatUpdate)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ext *ExtendedConn) handleMessageChatUpdate(message []byte) {
|
|
||||||
var event ChatUpdate
|
|
||||||
err := json.Unmarshal(message, &event)
|
|
||||||
if err != nil {
|
|
||||||
ext.jsonParseError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
event.JID = strings.Replace(event.JID, OldUserSuffix, NewUserSuffix, 1)
|
|
||||||
for _, handler := range ext.handlers {
|
|
||||||
chatUpdateHandler, ok := handler.(ChatUpdateHandler)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ext.shouldCallSynchronously(chatUpdateHandler) {
|
|
||||||
chatUpdateHandler.HandleChatUpdate(event)
|
|
||||||
} else {
|
|
||||||
go chatUpdateHandler.HandleChatUpdate(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
// matrix-skype - A Matrix-WhatsApp puppeting bridge.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package whatsappExt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Rhymen/go-whatsapp"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CommandType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
CommandPicture CommandType = "picture"
|
|
||||||
CommandDisconnect CommandType = "disconnect"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Command struct {
|
|
||||||
Type CommandType `json:"type"`
|
|
||||||
JID string `json:"jid"`
|
|
||||||
|
|
||||||
*ProfilePicInfo
|
|
||||||
Kind string `json:"kind"`
|
|
||||||
|
|
||||||
Raw json.RawMessage `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandHandler interface {
|
|
||||||
whatsapp.Handler
|
|
||||||
HandleCommand(Command)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ext *ExtendedConn) handleMessageCommand(message []byte) {
|
|
||||||
var event Command
|
|
||||||
err := json.Unmarshal(message, &event)
|
|
||||||
if err != nil {
|
|
||||||
ext.jsonParseError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
event.Raw = message
|
|
||||||
event.JID = strings.Replace(event.JID, OldUserSuffix, NewUserSuffix, 1)
|
|
||||||
for _, handler := range ext.handlers {
|
|
||||||
commandHandler, ok := handler.(CommandHandler)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ext.shouldCallSynchronously(commandHandler) {
|
|
||||||
commandHandler.HandleCommand(event)
|
|
||||||
} else {
|
|
||||||
go commandHandler.HandleCommand(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
// matrix-skype - A Matrix-WhatsApp puppeting bridge.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package whatsappExt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/Rhymen/go-whatsapp"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConnInfo struct {
|
|
||||||
ProtocolVersion []int `json:"protoVersion"`
|
|
||||||
BinaryVersion int `json:"binVersion"`
|
|
||||||
Phone struct {
|
|
||||||
WhatsAppVersion string `json:"wa_version"`
|
|
||||||
MCC string `json:"mcc"`
|
|
||||||
MNC string `json:"mnc"`
|
|
||||||
OSVersion string `json:"os_version"`
|
|
||||||
DeviceManufacturer string `json:"device_manufacturer"`
|
|
||||||
DeviceModel string `json:"device_model"`
|
|
||||||
OSBuildNumber string `json:"os_build_number"`
|
|
||||||
} `json:"phone"`
|
|
||||||
Features map[string]interface{} `json:"features"`
|
|
||||||
PushName string `json:"pushname"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConnInfoHandler interface {
|
|
||||||
whatsapp.Handler
|
|
||||||
HandleConnInfo(ConnInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ext *ExtendedConn) handleMessageConn(message []byte) {
|
|
||||||
var event ConnInfo
|
|
||||||
err := json.Unmarshal(message, &event)
|
|
||||||
if err != nil {
|
|
||||||
ext.jsonParseError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, handler := range ext.handlers {
|
|
||||||
connInfoHandler, ok := handler.(ConnInfoHandler)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ext.shouldCallSynchronously(connInfoHandler) {
|
|
||||||
connInfoHandler.HandleConnInfo(event)
|
|
||||||
} else {
|
|
||||||
go connInfoHandler.HandleConnInfo(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,105 +0,0 @@
|
||||||
// matrix-skype - A Matrix-WhatsApp puppeting bridge.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package whatsappExt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/Rhymen/go-whatsapp"
|
|
||||||
)
|
|
||||||
|
|
||||||
type JSONMessage []json.RawMessage
|
|
||||||
|
|
||||||
type JSONMessageType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
MessageMsgInfo JSONMessageType = "MsgInfo"
|
|
||||||
MessageMsg JSONMessageType = "Msg"
|
|
||||||
MessagePresence JSONMessageType = "Presence"
|
|
||||||
MessageStream JSONMessageType = "Stream"
|
|
||||||
MessageConn JSONMessageType = "Conn"
|
|
||||||
MessageProps JSONMessageType = "Props"
|
|
||||||
MessageCmd JSONMessageType = "Cmd"
|
|
||||||
MessageChat JSONMessageType = "Chat"
|
|
||||||
MessageCall JSONMessageType = "Call"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (ext *ExtendedConn) HandleError(error) {}
|
|
||||||
|
|
||||||
type UnhandledJSONMessageHandler interface {
|
|
||||||
whatsapp.Handler
|
|
||||||
HandleUnhandledJSONMessage(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
type JSONParseErrorHandler interface {
|
|
||||||
whatsapp.Handler
|
|
||||||
HandleJSONParseError(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ext *ExtendedConn) jsonParseError(err error) {
|
|
||||||
for _, handler := range ext.handlers {
|
|
||||||
errorHandler, ok := handler.(JSONParseErrorHandler)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
errorHandler.HandleJSONParseError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ext *ExtendedConn) HandleJsonMessage(message string) {
|
|
||||||
msg := JSONMessage{}
|
|
||||||
err := json.Unmarshal([]byte(message), &msg)
|
|
||||||
if err != nil || len(msg) < 2 {
|
|
||||||
ext.jsonParseError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var msgType JSONMessageType
|
|
||||||
json.Unmarshal(msg[0], &msgType)
|
|
||||||
|
|
||||||
switch msgType {
|
|
||||||
case MessagePresence:
|
|
||||||
ext.handleMessagePresence(msg[1])
|
|
||||||
case MessageStream:
|
|
||||||
ext.handleMessageStream(msg[1:])
|
|
||||||
case MessageConn:
|
|
||||||
ext.handleMessageConn(msg[1])
|
|
||||||
case MessageProps:
|
|
||||||
ext.handleMessageProps(msg[1])
|
|
||||||
case MessageMsgInfo, MessageMsg:
|
|
||||||
ext.handleMessageMsgInfo(msgType, msg[1])
|
|
||||||
case MessageCmd:
|
|
||||||
ext.handleMessageCommand(msg[1])
|
|
||||||
case MessageChat:
|
|
||||||
ext.handleMessageChatUpdate(msg[1])
|
|
||||||
case MessageCall:
|
|
||||||
ext.handleMessageCall(msg[1])
|
|
||||||
default:
|
|
||||||
for _, handler := range ext.handlers {
|
|
||||||
ujmHandler, ok := handler.(UnhandledJSONMessageHandler)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ext.shouldCallSynchronously(ujmHandler) {
|
|
||||||
ujmHandler.HandleUnhandledJSONMessage(message)
|
|
||||||
} else {
|
|
||||||
go ujmHandler.HandleUnhandledJSONMessage(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,95 +0,0 @@
|
||||||
// matrix-skype - A Matrix-WhatsApp puppeting bridge.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package whatsappExt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Rhymen/go-whatsapp"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MsgInfoCommand string
|
|
||||||
|
|
||||||
const (
|
|
||||||
MsgInfoCommandAck MsgInfoCommand = "ack"
|
|
||||||
MsgInfoCommandAcks MsgInfoCommand = "acks"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Acknowledgement int
|
|
||||||
|
|
||||||
const (
|
|
||||||
AckMessageSent Acknowledgement = 1
|
|
||||||
AckMessageDelivered Acknowledgement = 2
|
|
||||||
AckMessageRead Acknowledgement = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
type JSONStringOrArray []string
|
|
||||||
|
|
||||||
func (jsoa *JSONStringOrArray) UnmarshalJSON(data []byte) error {
|
|
||||||
var str string
|
|
||||||
if json.Unmarshal(data, &str) == nil {
|
|
||||||
*jsoa = []string{str}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var strs []string
|
|
||||||
json.Unmarshal(data, &strs)
|
|
||||||
*jsoa = strs
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type MsgInfo struct {
|
|
||||||
Command MsgInfoCommand `json:"cmd"`
|
|
||||||
IDs JSONStringOrArray `json:"id"`
|
|
||||||
Acknowledgement Acknowledgement `json:"ack"`
|
|
||||||
MessageFromJID string `json:"from"`
|
|
||||||
SenderJID string `json:"participant"`
|
|
||||||
ToJID string `json:"to"`
|
|
||||||
Timestamp int64 `json:"t"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type MsgInfoHandler interface {
|
|
||||||
whatsapp.Handler
|
|
||||||
HandleMsgInfo(MsgInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ext *ExtendedConn) handleMessageMsgInfo(msgType JSONMessageType, message []byte) {
|
|
||||||
var event MsgInfo
|
|
||||||
err := json.Unmarshal(message, &event)
|
|
||||||
if err != nil {
|
|
||||||
ext.jsonParseError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
event.MessageFromJID = strings.Replace(event.MessageFromJID, OldUserSuffix, NewUserSuffix, 1)
|
|
||||||
event.SenderJID = strings.Replace(event.SenderJID, OldUserSuffix, NewUserSuffix, 1)
|
|
||||||
event.ToJID = strings.Replace(event.ToJID, OldUserSuffix, NewUserSuffix, 1)
|
|
||||||
if msgType == MessageMsg {
|
|
||||||
event.SenderJID = event.ToJID
|
|
||||||
}
|
|
||||||
for _, handler := range ext.handlers {
|
|
||||||
msgInfoHandler, ok := handler.(MsgInfoHandler)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ext.shouldCallSynchronously(msgInfoHandler) {
|
|
||||||
msgInfoHandler.HandleMsgInfo(event)
|
|
||||||
} else {
|
|
||||||
go msgInfoHandler.HandleMsgInfo(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
// matrix-skype - A Matrix-WhatsApp puppeting bridge.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package whatsappExt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Rhymen/go-whatsapp"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Presence struct {
|
|
||||||
JID string `json:"id"`
|
|
||||||
SenderJID string `json:"participant"`
|
|
||||||
Status whatsapp.Presence `json:"type"`
|
|
||||||
Timestamp int64 `json:"t"`
|
|
||||||
Deny bool `json:"deny"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PresenceHandler interface {
|
|
||||||
whatsapp.Handler
|
|
||||||
HandlePresence(Presence)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ext *ExtendedConn) handleMessagePresence(message []byte) {
|
|
||||||
var event Presence
|
|
||||||
err := json.Unmarshal(message, &event)
|
|
||||||
if err != nil {
|
|
||||||
ext.jsonParseError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
event.JID = strings.Replace(event.JID, OldUserSuffix, NewUserSuffix, 1)
|
|
||||||
if len(event.SenderJID) == 0 {
|
|
||||||
if strings.Index(event.JID,"@g.us") > -1 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
event.SenderJID = event.JID
|
|
||||||
} else {
|
|
||||||
event.SenderJID = strings.Replace(event.SenderJID, OldUserSuffix, NewUserSuffix, 1)
|
|
||||||
}
|
|
||||||
for _, handler := range ext.handlers {
|
|
||||||
presenceHandler, ok := handler.(PresenceHandler)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ext.shouldCallSynchronously(presenceHandler) {
|
|
||||||
presenceHandler.HandlePresence(event)
|
|
||||||
} else {
|
|
||||||
go presenceHandler.HandlePresence(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
// matrix-skype - A Matrix-WhatsApp puppeting bridge.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package whatsappExt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/Rhymen/go-whatsapp"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ProtocolProps struct {
|
|
||||||
WebPresence bool `json:"webPresence"`
|
|
||||||
NotificationQuery bool `json:"notificationQuery"`
|
|
||||||
FacebookCrashLog bool `json:"fbCrashlog"`
|
|
||||||
Bucket string `json:"bucket"`
|
|
||||||
GIFSearch string `json:"gifSearch"`
|
|
||||||
Spam bool `json:"SPAM"`
|
|
||||||
SetBlock bool `json:"SET_BLOCK"`
|
|
||||||
MessageInfo bool `json:"MESSAGE_INFO"`
|
|
||||||
MaxFileSize int `json:"maxFileSize"`
|
|
||||||
Media int `json:"media"`
|
|
||||||
GroupNameLength int `json:"maxSubject"`
|
|
||||||
GroupDescriptionLength int `json:"groupDescLength"`
|
|
||||||
MaxParticipants int `json:"maxParticipants"`
|
|
||||||
VideoMaxEdge int `json:"videoMaxEdge"`
|
|
||||||
ImageMaxEdge int `json:"imageMaxEdge"`
|
|
||||||
ImageMaxKilobytes int `json:"imageMaxKBytes"`
|
|
||||||
Edit int `json:"edit"`
|
|
||||||
FwdUIStartTimestamp int `json:"fwdUiStartTs"`
|
|
||||||
GroupsV3 int `json:"groupsV3"`
|
|
||||||
RestrictGroups int `json:"restrictGroups"`
|
|
||||||
AnnounceGroups int `json:"announceGroups"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProtocolPropsHandler interface {
|
|
||||||
whatsapp.Handler
|
|
||||||
HandleProtocolProps(ProtocolProps)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ext *ExtendedConn) handleMessageProps(message []byte) {
|
|
||||||
var event ProtocolProps
|
|
||||||
err := json.Unmarshal(message, &event)
|
|
||||||
if err != nil {
|
|
||||||
ext.jsonParseError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, handler := range ext.handlers {
|
|
||||||
protocolPropsHandler, ok := handler.(ProtocolPropsHandler)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ext.shouldCallSynchronously(protocolPropsHandler) {
|
|
||||||
protocolPropsHandler.HandleProtocolProps(event)
|
|
||||||
} else {
|
|
||||||
go protocolPropsHandler.HandleProtocolProps(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
// matrix-skype - A Matrix-WhatsApp puppeting bridge.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package whatsappExt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Rhymen/go-whatsapp"
|
|
||||||
"github.com/Rhymen/go-whatsapp/binary/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MessageRevokeHandler interface {
|
|
||||||
whatsapp.Handler
|
|
||||||
HandleMessageRevoke(key MessageRevocation)
|
|
||||||
}
|
|
||||||
|
|
||||||
type MessageRevocation struct {
|
|
||||||
Id string
|
|
||||||
RemoteJid string
|
|
||||||
FromMe bool
|
|
||||||
Participant string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ext *ExtendedConn) HandleRawMessage(message *proto.WebMessageInfo) {
|
|
||||||
protoMsg := message.GetMessage().GetProtocolMessage()
|
|
||||||
if protoMsg != nil && protoMsg.GetType() == proto.ProtocolMessage_REVOKE {
|
|
||||||
key := protoMsg.GetKey()
|
|
||||||
deletedMessage := MessageRevocation{
|
|
||||||
Id: key.GetId(),
|
|
||||||
RemoteJid: key.GetRemoteJid(),
|
|
||||||
FromMe: key.GetFromMe(),
|
|
||||||
Participant: key.GetParticipant(),
|
|
||||||
}
|
|
||||||
for _, handler := range ext.handlers {
|
|
||||||
mrHandler, ok := handler.(MessageRevokeHandler)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ext.shouldCallSynchronously(mrHandler) {
|
|
||||||
mrHandler.HandleMessageRevoke(deletedMessage)
|
|
||||||
} else {
|
|
||||||
go mrHandler.HandleMessageRevoke(deletedMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
// matrix-skype - A Matrix-WhatsApp puppeting bridge.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package whatsappExt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/Rhymen/go-whatsapp"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StreamType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
StreamUpdate = "update"
|
|
||||||
StreamSleep = "asleep"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StreamEvent struct {
|
|
||||||
Type StreamType
|
|
||||||
Boolean bool
|
|
||||||
Version string
|
|
||||||
}
|
|
||||||
|
|
||||||
type StreamEventHandler interface {
|
|
||||||
whatsapp.Handler
|
|
||||||
HandleStreamEvent(StreamEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ext *ExtendedConn) handleMessageStream(message []json.RawMessage) {
|
|
||||||
var event StreamEvent
|
|
||||||
err := json.Unmarshal(message[0], &event.Type)
|
|
||||||
if err != nil {
|
|
||||||
ext.jsonParseError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if event.Type == StreamUpdate && len(message) > 4 {
|
|
||||||
json.Unmarshal(message[1], event.Boolean)
|
|
||||||
json.Unmarshal(message[2], event.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, handler := range ext.handlers {
|
|
||||||
streamHandler, ok := handler.(StreamEventHandler)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ext.shouldCallSynchronously(streamHandler) {
|
|
||||||
streamHandler.HandleStreamEvent(event)
|
|
||||||
} else {
|
|
||||||
go streamHandler.HandleStreamEvent(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,206 +0,0 @@
|
||||||
// matrix-skype - A Matrix-WhatsApp puppeting bridge.
|
|
||||||
// Copyright (C) 2019 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package whatsappExt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Rhymen/go-whatsapp"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
OldUserSuffix = "@c.us"
|
|
||||||
//NewUserSuffix = "@s.whatsapp.net"
|
|
||||||
NewUserSuffix = "@s.skype.net"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ExtendedConn struct {
|
|
||||||
*whatsapp.Conn
|
|
||||||
|
|
||||||
handlers []whatsapp.Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExtendConn(conn *whatsapp.Conn) *ExtendedConn {
|
|
||||||
ext := &ExtendedConn{
|
|
||||||
Conn: conn,
|
|
||||||
}
|
|
||||||
ext.Conn.AddHandler(ext)
|
|
||||||
return ext
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ext *ExtendedConn) AddHandler(handler whatsapp.Handler) {
|
|
||||||
ext.Conn.AddHandler(handler)
|
|
||||||
ext.handlers = append(ext.handlers, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func (ext *ExtendedConn) RemoveHandler(handler whatsapp.Handler) bool {
|
|
||||||
ext.Conn.RemoveHandler(handler)
|
|
||||||
for i, v := range ext.handlers {
|
|
||||||
if v == handler {
|
|
||||||
ext.handlers = append(ext.handlers[:i], ext.handlers[i+1:]...)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ext *ExtendedConn) RemoveHandlers() {
|
|
||||||
ext.Conn.RemoveHandlers()
|
|
||||||
ext.handlers = make([]whatsapp.Handler, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ext *ExtendedConn) shouldCallSynchronously(handler whatsapp.Handler) bool {
|
|
||||||
sh, ok := handler.(whatsapp.SyncHandler)
|
|
||||||
return ok && sh.ShouldCallSynchronously()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ext *ExtendedConn) ShouldCallSynchronously() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type GroupInfo struct {
|
|
||||||
JID string `json:"jid"`
|
|
||||||
OwnerJID string `json:"owner"`
|
|
||||||
|
|
||||||
Name string `json:"subject"`
|
|
||||||
NameSetTime int64 `json:"subjectTime"`
|
|
||||||
NameSetBy string `json:"subjectOwner"`
|
|
||||||
|
|
||||||
Topic string `json:"desc"`
|
|
||||||
TopicID string `json:"descId"`
|
|
||||||
TopicSetAt int64 `json:"descTime"`
|
|
||||||
TopicSetBy string `json:"descOwner"`
|
|
||||||
|
|
||||||
GroupCreated int64 `json:"creation"`
|
|
||||||
|
|
||||||
Status int16 `json:"status"`
|
|
||||||
|
|
||||||
Participants []struct {
|
|
||||||
JID string `json:"id"`
|
|
||||||
IsAdmin bool `json:"isAdmin"`
|
|
||||||
IsSuperAdmin bool `json:"isSuperAdmin"`
|
|
||||||
} `json:"participants"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ext *ExtendedConn) GetGroupMetaData(jid string) (*GroupInfo, error) {
|
|
||||||
data, err := ext.Conn.GetGroupMetaData(jid)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get group metadata: %v", err)
|
|
||||||
}
|
|
||||||
content := <-data
|
|
||||||
|
|
||||||
info := &GroupInfo{}
|
|
||||||
err = json.Unmarshal([]byte(content), info)
|
|
||||||
if err != nil {
|
|
||||||
return info, fmt.Errorf("failed to unmarshal group metadata: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for index, participant := range info.Participants {
|
|
||||||
info.Participants[index].JID = strings.Replace(participant.JID, OldUserSuffix, NewUserSuffix, 1)
|
|
||||||
}
|
|
||||||
info.NameSetBy = strings.Replace(info.NameSetBy, OldUserSuffix, NewUserSuffix, 1)
|
|
||||||
info.TopicSetBy = strings.Replace(info.TopicSetBy, OldUserSuffix, NewUserSuffix, 1)
|
|
||||||
|
|
||||||
return info, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProfilePicInfo struct {
|
|
||||||
URL string `json:"eurl"`
|
|
||||||
Tag string `json:"tag"`
|
|
||||||
|
|
||||||
Status int16 `json:"status"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ppi *ProfilePicInfo) Download() (io.ReadCloser, error) {
|
|
||||||
resp, err := http.Get(ppi.URL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return resp.Body, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ppi *ProfilePicInfo) DownloadBytes() ([]byte, error) {
|
|
||||||
body, err := ppi.Download()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer body.Close()
|
|
||||||
data, err := ioutil.ReadAll(body)
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ext *ExtendedConn) GetProfilePicThumb(jid string) (*ProfilePicInfo, error) {
|
|
||||||
data, err := ext.Conn.GetProfilePicThumb(jid)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get avatar: %v", err)
|
|
||||||
}
|
|
||||||
content := <-data
|
|
||||||
info := &ProfilePicInfo{}
|
|
||||||
err = json.Unmarshal([]byte(content), info)
|
|
||||||
if err != nil {
|
|
||||||
return info, fmt.Errorf("failed to unmarshal avatar info: %v", err)
|
|
||||||
}
|
|
||||||
return info, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ext *ExtendedConn) HandleGroupInvite(groupJid string, numbers[]string) (err error) {
|
|
||||||
var parts []string
|
|
||||||
parts = append(parts, numbers...)
|
|
||||||
_, err = ext.Conn.AddMember(groupJid, parts)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("%s Handle Invite err", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ext *ExtendedConn) HandleGroupJoin(code string) (jid string, err error) {
|
|
||||||
return ext.Conn.GroupAcceptInviteCode(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ext *ExtendedConn) HandleGroupKick(groupJid string, numbers[]string) (err error) {
|
|
||||||
var parts []string
|
|
||||||
parts = append(parts, numbers...)
|
|
||||||
_, err = ext.Conn.RemoveMember(groupJid, parts)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("%s Handle kick err", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ext *ExtendedConn) HandleGroupCreate(subject string, numbers[]string) (err error) {
|
|
||||||
var parts []string
|
|
||||||
parts = append(parts, numbers...)
|
|
||||||
_, err = ext.Conn.CreateGroup(subject, parts)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("%s HandleGroupCreate err", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ext *ExtendedConn) HandleGroupLeave(groupJid string) (err error) {
|
|
||||||
_, err = ext.Conn.LeaveGroup(groupJid)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("%s HandleGroupLeave err", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
Loading…
Reference in New Issue