Begin some bridging
This commit is contained in:
parent
046ec6380b
commit
d2ccd6763a
8 changed files with 479 additions and 6 deletions
|
@ -1,6 +1,10 @@
|
|||
package appservice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"git.deuxfleurs.fr/Deuxfleurs/easybridge/mxlib"
|
||||
. "git.deuxfleurs.fr/Deuxfleurs/easybridge/connector"
|
||||
)
|
||||
|
||||
|
@ -12,11 +16,31 @@ type Account struct {
|
|||
}
|
||||
|
||||
func (a *Account) Joined(roomId RoomID) {
|
||||
// TODO
|
||||
mx_room_id, err := dbGetMxRoom(a.Protocol, roomId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Joined %s (%s)\n", roomId, a.MatrixUser)
|
||||
|
||||
err = mxRoomInvite(mx_room_id, a.MatrixUser)
|
||||
if err != nil {
|
||||
log.Printf("Could not invite %s to %s", a.MatrixUser, mx_room_id)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Account) Left(roomId RoomID) {
|
||||
// TODO
|
||||
mx_room_id, err := dbGetMxRoom(a.Protocol, roomId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Joined %s (%s)\n", roomId, a.MatrixUser)
|
||||
|
||||
err = mxRoomKick(mx_room_id, a.MatrixUser, fmt.Sprintf("got leave room event on %s", a.Protocol))
|
||||
if err != nil {
|
||||
log.Printf("Could not invite %s to %s", a.MatrixUser, mx_room_id)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Account) UserInfoUpdated(user UserID, info *UserInfo) {
|
||||
|
@ -28,5 +52,125 @@ func (a *Account) RoomInfoUpdated(roomId RoomID, info *RoomInfo) {
|
|||
}
|
||||
|
||||
func (a *Account) Event(event *Event) {
|
||||
mx_user_id, err := dbGetMxUser(a.Protocol, event.Author)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if event.Type == EVENT_JOIN {
|
||||
log.Printf("%s join %s %s", a.Protocol, event.Author, event.Room)
|
||||
mx_room_id, err := dbGetMxRoom(a.Protocol, event.Room)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = mxRoomInvite(mx_room_id, mx_user_id)
|
||||
if err != nil {
|
||||
log.Printf("Could not invite %s to %s", a.MatrixUser, mx_room_id)
|
||||
}
|
||||
|
||||
err = mxRoomJoinAs(mx_room_id, mx_user_id)
|
||||
if err != nil {
|
||||
log.Printf("Could not join %s as %s", a.MatrixUser, mx_room_id)
|
||||
}
|
||||
} else if event.Type == EVENT_LEAVE {
|
||||
log.Printf("%s join %s %s", a.Protocol, event.Author, event.Room)
|
||||
mx_room_id, err := dbGetMxRoom(a.Protocol, event.Room)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = mxRoomLeaveAs(mx_room_id, mx_user_id)
|
||||
if err != nil {
|
||||
log.Printf("Could not leave %s as %s", a.MatrixUser, mx_room_id)
|
||||
}
|
||||
} else if event.Type == EVENT_MESSAGE {
|
||||
if len(event.Room) > 0 {
|
||||
log.Printf("%s msg %s %s", a.Protocol, event.Author, event.Room)
|
||||
mx_room_id, err := dbGetMxRoom(a.Protocol, event.Room)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = mxSendMessageAs(mx_room_id, event.Text, mx_user_id)
|
||||
if err != nil {
|
||||
log.Printf("Could not send %s as %s", event.Text, mx_user_id)
|
||||
}
|
||||
} else {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
// TODO
|
||||
}
|
||||
|
||||
// ----
|
||||
|
||||
func dbGetMxRoom(protocol string, roomId RoomID) (string, error) {
|
||||
var room DbRoomMap
|
||||
|
||||
// Check if room exists in our mapping,
|
||||
// If not create it
|
||||
must_create := db.First(&room, DbRoomMap{
|
||||
Protocol: protocol,
|
||||
RoomID: roomId,
|
||||
}).RecordNotFound()
|
||||
if must_create {
|
||||
alias := roomAlias(protocol, roomId)
|
||||
// Lookup alias
|
||||
mx_room_id, err := mxDirectoryRoom(fmt.Sprintf("#%s:%s", alias, config.MatrixDomain))
|
||||
|
||||
// If no alias found, create room
|
||||
if err != nil {
|
||||
name := fmt.Sprintf("%s (%s)", roomId, protocol)
|
||||
|
||||
mx_room_id, err = mxCreateRoom(name, alias, []string{})
|
||||
if err != nil {
|
||||
log.Printf("Could not create room for %s: %s", name, err)
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
room = DbRoomMap{
|
||||
Protocol: protocol,
|
||||
RoomID: roomId,
|
||||
MxRoomID: mx_room_id,
|
||||
}
|
||||
db.Create(&room)
|
||||
}
|
||||
log.Printf("Got room id: %s", room.MxRoomID)
|
||||
|
||||
return room.MxRoomID, nil
|
||||
}
|
||||
|
||||
func dbGetMxUser(protocol string, userId UserID) (string, error) {
|
||||
var user DbUserMap
|
||||
|
||||
must_create := db.First(&user, DbUserMap{
|
||||
Protocol: protocol,
|
||||
UserID: userId,
|
||||
}).RecordNotFound()
|
||||
if must_create {
|
||||
username := userMxId(protocol, userId)
|
||||
|
||||
err := mxRegisterUser(username)
|
||||
if err != nil {
|
||||
if mxE, ok := err.(*mxlib.MxError); !ok || mxE.ErrCode != "M_USER_IN_USE" {
|
||||
log.Printf("Could not register %s: %s", username, err)
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
mxid := fmt.Sprintf("@%s:%s", username, config.MatrixDomain)
|
||||
mxProfileDisplayname(mxid, fmt.Sprintf("%s (%s)", userId, protocol))
|
||||
|
||||
user = DbUserMap{
|
||||
Protocol: protocol,
|
||||
UserID: userId,
|
||||
MxUserID: mxid,
|
||||
}
|
||||
db.Create(&user)
|
||||
}
|
||||
|
||||
return user.MxUserID, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package appservice
|
||||
|
||||
import (
|
||||
"git.deuxfleurs.fr/Deuxfleurs/easybridge/connector"
|
||||
"github.com/jinzhu/gorm"
|
||||
_ "github.com/jinzhu/gorm/dialects/mysql"
|
||||
_ "github.com/jinzhu/gorm/dialects/postgres"
|
||||
|
@ -17,5 +18,54 @@ func InitDb() error {
|
|||
return err
|
||||
}
|
||||
|
||||
db.AutoMigrate(&DbUserMap{})
|
||||
db.Model(&DbUserMap{}).AddIndex("idx_protocol_user", "protocol", "user_id")
|
||||
|
||||
db.AutoMigrate(&DbRoomMap{})
|
||||
db.Model(&DbRoomMap{}).AddIndex("idx_protocol_room", "protocol", "room_id")
|
||||
|
||||
db.AutoMigrate(&DbPmRoomMap{})
|
||||
db.Model(&DbPmRoomMap{}).AddIndex("idx_protocol_user_account_user", "protocol", "user_id", "mx_user_id", "account_name")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// User mapping between protocol user IDs and puppeted matrix ids
|
||||
type DbUserMap struct {
|
||||
gorm.Model
|
||||
|
||||
Protocol string
|
||||
UserID connector.UserID
|
||||
MxUserID string `gorm:"index:mxuserid"`
|
||||
}
|
||||
|
||||
// Room mapping between Matrix rooms and outside rooms
|
||||
type DbRoomMap struct {
|
||||
gorm.Model
|
||||
|
||||
// Network protocol
|
||||
Protocol string
|
||||
|
||||
// Room id on the bridged network
|
||||
RoomID connector.RoomID
|
||||
|
||||
// Bridged room matrix id
|
||||
MxRoomID string `gorm:"index:mxroomid"`
|
||||
}
|
||||
|
||||
// Room mapping between Matrix rooms and private messages
|
||||
type DbPmRoomMap struct {
|
||||
gorm.Model
|
||||
|
||||
// User id and account name of the local end viewed on Matrix
|
||||
MxUserID string
|
||||
Protocol string
|
||||
AccountName string
|
||||
|
||||
// User id to reach them
|
||||
UserID connector.RoomID
|
||||
|
||||
// Bridged room for PMs
|
||||
MxRoomID string `gorm:"index:mxroomoid"`
|
||||
}
|
||||
|
||||
|
|
192
appservice/matrix.go
Normal file
192
appservice/matrix.go
Normal file
|
@ -0,0 +1,192 @@
|
|||
package appservice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
|
||||
. "git.deuxfleurs.fr/Deuxfleurs/easybridge/mxlib"
|
||||
)
|
||||
|
||||
var httpClient *http.Client
|
||||
|
||||
func init() {
|
||||
tr := &http.Transport{
|
||||
MaxIdleConns: 10,
|
||||
IdleConnTimeout: 30 * time.Second,
|
||||
DisableCompression: true,
|
||||
}
|
||||
httpClient = &http.Client{Transport: tr}
|
||||
}
|
||||
|
||||
func mxGetApiCall(endpoint string, response interface{}) error {
|
||||
log.Printf("Matrix GET request: %s\n", endpoint)
|
||||
|
||||
req, err := http.NewRequest("GET", config.Server + endpoint, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return mxDoAndParse(req, response)
|
||||
}
|
||||
|
||||
func mxPutApiCall(endpoint string, data interface{}, response interface{}) error {
|
||||
body, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Matrix PUT request: %s %s\n", endpoint, string(body))
|
||||
|
||||
req, err := http.NewRequest("PUT", config.Server + endpoint, bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
return mxDoAndParse(req, response)
|
||||
}
|
||||
|
||||
func mxPostApiCall(endpoint string, data interface{}, response interface{}) error {
|
||||
body, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Matrix POST request: %s %s\n", endpoint, string(body))
|
||||
|
||||
req, err := http.NewRequest("POST", config.Server + endpoint, bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
return mxDoAndParse(req, response)
|
||||
}
|
||||
|
||||
func mxDoAndParse(req *http.Request, response interface{}) error {
|
||||
req.Header.Add("Authorization", "Bearer " + registration.AsToken)
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
var e MxError
|
||||
err = json.NewDecoder(resp.Body).Decode(&e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("Response (%d): %#v\n", resp.StatusCode, e)
|
||||
return &e
|
||||
}
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Response: %#v\n", response)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ----
|
||||
|
||||
func mxRegisterUser(username string) error {
|
||||
req := RegisterRequest{
|
||||
Username: username,
|
||||
}
|
||||
var rep RegisterResponse
|
||||
return mxPostApiCall("/_matrix/client/r0/register?kind=user", &req, &rep)
|
||||
}
|
||||
|
||||
func mxProfileDisplayname(userid string, displayname string) error {
|
||||
req := ProfileDisplaynameRequest{
|
||||
Displayname: displayname,
|
||||
}
|
||||
var rep struct{}
|
||||
err := mxPutApiCall(fmt.Sprintf("/_matrix/client/r0/profile/%s/displayname?user_id=%s",
|
||||
url.QueryEscape(userid), url.QueryEscape(userid)),
|
||||
&req, &rep)
|
||||
return err
|
||||
}
|
||||
|
||||
func mxDirectoryRoom(alias string) (string, error) {
|
||||
var rep DirectoryRoomResponse
|
||||
err := mxGetApiCall("/_matrix/client/r0/directory/room/" + url.QueryEscape(alias), &rep)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return rep.RoomId, nil
|
||||
}
|
||||
|
||||
func mxCreateRoom(name string, alias string, invite []string) (string, error) {
|
||||
rq := CreateRoomRequest{
|
||||
Preset: "private_chat",
|
||||
RoomAliasName: alias,
|
||||
Name: name,
|
||||
Topic: "",
|
||||
Invite: invite,
|
||||
CreationContent: map[string]interface{} {
|
||||
"m.federate": false,
|
||||
},
|
||||
}
|
||||
var rep CreateRoomResponse
|
||||
err := mxPostApiCall("/_matrix/client/r0/createRoom", &rq, &rep)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return rep.RoomId, nil
|
||||
}
|
||||
|
||||
func mxRoomInvite(room string, user string) error {
|
||||
rq := RoomInviteRequest{
|
||||
UserId: user,
|
||||
}
|
||||
var rep struct{}
|
||||
err := mxPostApiCall("/_matrix/client/r0/rooms/" + url.QueryEscape(room) + "/invite", &rq, &rep)
|
||||
return err
|
||||
}
|
||||
|
||||
func mxRoomKick(room string, user string, reason string) error {
|
||||
rq := RoomKickRequest{
|
||||
UserId: user,
|
||||
Reason: reason,
|
||||
}
|
||||
var rep struct{}
|
||||
err := mxPostApiCall("/_matrix/client/r0/rooms/" + url.QueryEscape(room) + "/kick", &rq, &rep)
|
||||
return err
|
||||
}
|
||||
|
||||
func mxRoomJoinAs(room string, user string) error {
|
||||
rq := struct{}{}
|
||||
var rep RoomJoinResponse
|
||||
err := mxPostApiCall("/_matrix/client/r0/rooms/" + url.QueryEscape(room) + "/join?user_id=" + url.QueryEscape(user), &rq, &rep)
|
||||
return err
|
||||
}
|
||||
|
||||
func mxRoomLeaveAs(room string, user string) error {
|
||||
rq := struct{}{}
|
||||
var rep struct{}
|
||||
err := mxPostApiCall("/_matrix/client/r0/rooms/" + url.QueryEscape(room) + "/leave?user_id=" + url.QueryEscape(user), &rq, &rep)
|
||||
return err
|
||||
}
|
||||
|
||||
func mxSendMessageAs(room string, body string, user string) error {
|
||||
txn_id := time.Now().UnixNano()
|
||||
rq := RoomSendRequest{
|
||||
MsgType: "m.text",
|
||||
Body: body,
|
||||
}
|
||||
var rep RoomSendResponse
|
||||
err := mxPutApiCall(fmt.Sprintf(
|
||||
"/_matrix/client/r0/rooms/%s/send/m.room.message/%d?user_id=%s",
|
||||
url.QueryEscape(room), txn_id, url.QueryEscape(user)),
|
||||
&rq, &rep)
|
||||
return err
|
||||
}
|
21
appservice/names.go
Normal file
21
appservice/names.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package appservice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
. "git.deuxfleurs.fr/Deuxfleurs/easybridge/connector"
|
||||
)
|
||||
|
||||
func roomAlias(protocol string, id RoomID) string {
|
||||
id2 := strings.ReplaceAll(string(id), "#", "")
|
||||
id2 = strings.ReplaceAll(id2, "@", "__")
|
||||
|
||||
return fmt.Sprintf("_ezbr__%s__%s", id2, protocol)
|
||||
}
|
||||
|
||||
func userMxId(protocol string, id UserID) string {
|
||||
id2 := strings.ReplaceAll(string(id), "@", "__")
|
||||
|
||||
return fmt.Sprintf("_ezbr__%s__%s", id2, protocol)
|
||||
}
|
|
@ -16,6 +16,7 @@ type Config struct {
|
|||
Server string
|
||||
DbType string
|
||||
DbPath string
|
||||
MatrixDomain string
|
||||
}
|
||||
|
||||
|
||||
|
@ -33,6 +34,7 @@ func Start(r *mxlib.Registration, c *Config) (chan error, error) {
|
|||
|
||||
router := mux.NewRouter()
|
||||
router.HandleFunc("/_matrix/app/v1/transactions/{txnId}", handleTxn)
|
||||
router.HandleFunc("/transactions/{txnId}", handleTxn)
|
||||
|
||||
errch := make(chan error)
|
||||
go func() {
|
||||
|
@ -68,5 +70,5 @@ func handleTxn(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
log.Printf("Got transaction %#v\n", txn)
|
||||
|
||||
fmt.Fprintf(w, "{}")
|
||||
fmt.Fprintf(w, "{}\n")
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package irc
|
|||
|
||||
import (
|
||||
"time"
|
||||
"os"
|
||||
_ "os"
|
||||
"strings"
|
||||
"fmt"
|
||||
|
||||
|
@ -67,7 +67,7 @@ func (irc *IRC) Configure(c Configuration) error {
|
|||
Port: port,
|
||||
Nick: irc.nick,
|
||||
User: irc.nick,
|
||||
Out: os.Stderr,
|
||||
//Out: os.Stderr,
|
||||
SSL: ssl,
|
||||
})
|
||||
|
||||
|
|
5
main.go
5
main.go
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"flag"
|
||||
|
@ -32,6 +33,7 @@ type ConfigFile struct {
|
|||
Server string `json:"homeserver_url"`
|
||||
DbType string `json:"db_type"`
|
||||
DbPath string `json:"db_path"`
|
||||
MatrixDomain string `json:"matrix_domain"`
|
||||
Accounts map[string]map[string]ConfigAccount `json:"accounts"`
|
||||
}
|
||||
|
||||
|
@ -164,6 +166,7 @@ func main() {
|
|||
Server: config.Server,
|
||||
DbType: config.DbType,
|
||||
DbPath: config.DbPath,
|
||||
MatrixDomain: config.MatrixDomain,
|
||||
}
|
||||
|
||||
errch, err := appservice.Start(registration, as_config)
|
||||
|
@ -181,7 +184,7 @@ func main() {
|
|||
conn = &xmpp.XMPP{}
|
||||
}
|
||||
account := &appservice.Account{
|
||||
MatrixUser: user,
|
||||
MatrixUser: fmt.Sprintf("@%s:%s", user, config.MatrixDomain),
|
||||
AccountName: name,
|
||||
Protocol: params.Protocol,
|
||||
Conn: conn,
|
||||
|
|
61
mxlib/api.go
61
mxlib/api.go
|
@ -4,6 +4,15 @@ import (
|
|||
_ "encoding/json"
|
||||
)
|
||||
|
||||
type MxError struct {
|
||||
ErrCode string `json:"errcode"`
|
||||
ErrMsg string `json:"error"`
|
||||
}
|
||||
|
||||
func (e *MxError) Error() string {
|
||||
return e.ErrMsg
|
||||
}
|
||||
|
||||
type Transaction struct {
|
||||
Events []Event `json:"events"`
|
||||
}
|
||||
|
@ -16,3 +25,55 @@ type Event struct {
|
|||
Sender string `json:"sender"`
|
||||
OriginServerTs int `json:"origin_server_ts"`
|
||||
}
|
||||
|
||||
type RegisterRequest struct {
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
type RegisterResponse struct {
|
||||
UserId string `json:"user_id"`
|
||||
AccessToken string `json:"access_token"`
|
||||
DeviceId string `json:"device_id"`
|
||||
}
|
||||
|
||||
type ProfileDisplaynameRequest struct {
|
||||
Displayname string `json:"displayname"`
|
||||
}
|
||||
|
||||
type CreateRoomRequest struct {
|
||||
Preset string `json:"preset"`
|
||||
RoomAliasName string `json:"room_alias_name"`
|
||||
Name string `json:"name"`
|
||||
Topic string `json:"topic"`
|
||||
Invite []string `json:"invite"`
|
||||
CreationContent map[string]interface{} `json:"creation_content"`
|
||||
}
|
||||
|
||||
type CreateRoomResponse struct {
|
||||
RoomId string `json:"room_id"`
|
||||
}
|
||||
|
||||
type DirectoryRoomResponse struct {
|
||||
RoomId string `json:"room_id"`
|
||||
Servers []string `json:"string"`
|
||||
}
|
||||
|
||||
type RoomInviteRequest struct {
|
||||
UserId string `json:"user_id"`
|
||||
}
|
||||
|
||||
type RoomKickRequest struct {
|
||||
UserId string `json:"user_id"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
type RoomJoinResponse struct {
|
||||
RoomId string `json:"room_id"`
|
||||
}
|
||||
|
||||
type RoomSendRequest struct {
|
||||
MsgType string `json:"msgtype"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
type RoomSendResponse struct {
|
||||
EventId string `json:"event_id"`
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue