Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • hosting/chat/matrix-bigbluebutton-bot-go
1 result
Show changes
Commits on Source (3)
.idea
package main
import (
"crypto/sha1"
"encoding/hex"
"encoding/xml"
"errors"
"fmt"
"github.com/google/uuid"
"io/ioutil"
"net/http"
"time"
)
func CreateQueryString(method string, params []BBBUrlParam) string {
queryString := ""
for _, key := range params {
queryString = queryString + fmt.Sprintf("%s=%s&", key.name, key.value)
}
p := fmt.Sprintf("%s%s%s", method, queryString, Conf.BbbSecret)
h := sha1.New()
h.Write([]byte(p))
hash := hex.EncodeToString(h.Sum(nil))
queryStringWithHash := fmt.Sprintf("%s&checksum=%s", queryString, hash)
return queryStringWithHash
}
func CreateBbbMeeting(config Config) (BBBCreateMeetingResponseBody, error) {
var body BBBCreateMeetingResponseBody
tr := &http.Transport{
MaxIdleConns: 10,
IdleConnTimeout: 30 * time.Second,
DisableCompression: true,
}
tMeetingId, err := uuid.NewRandom()
if err != nil {
return body, err
}
tParams := []BBBUrlParam{}
tParams = append(tParams, BBBUrlParam{
name: "meetingID",
value: tMeetingId.String(),
})
tParams = append(tParams, BBBUrlParam{
name: "name",
value: Conf.BbbMeetingTitle,
})
tParams = append(tParams, BBBUrlParam{
name: "attendeePW",
value: Conf.AttendeePW,
})
tParams = append(tParams, BBBUrlParam{
name: "moderatorPW",
value: Conf.ModeratorPW,
})
bbbQueryString := CreateQueryString("create", tParams)
fmt.Println("bbbQueryString: ")
fmt.Println(bbbQueryString)
bbbCreateUrl := config.BbbServer + "create?" + bbbQueryString
fmt.Println("bbbCreateUrl: ")
fmt.Println(bbbCreateUrl)
client := &http.Client{Transport: tr}
resp, err := client.Get(bbbCreateUrl)
if (err) != nil {
return body, err
}
defer resp.Body.Close()
rawBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return BBBCreateMeetingResponseBody{}, err
}
if err := xml.Unmarshal(rawBody, &body); err != nil {
return BBBCreateMeetingResponseBody{}, err
}
if body.Returncode != "SUCCESS" {
return BBBCreateMeetingResponseBody{}, errors.New("Request encountered an error: " + body.Returncode)
}
return body, nil
}
func CreateMeetingLinks(meeting BBBCreateMeetingResponseBody, moderator string, users []string) (links BBBMeetingLinks, err error) {
tParamsModerator := []BBBUrlParam{}
tParamsModerator = append(tParamsModerator, BBBUrlParam{
name: "meetingID",
value: meeting.MeetingID,
})
tParamsModerator = append(tParamsModerator, BBBUrlParam{
name: "moderatorPW",
value: Conf.ModeratorPW,
})
tParamsModerator = append(tParamsModerator, BBBUrlParam{
name: "fullName",
value: moderator,
})
tQuery := CreateQueryString("join", tParamsModerator)
joinUrlModerator := Conf.BbbServer + "join?" + tQuery
fmt.Println("joinUrlModerator: ")
fmt.Println(joinUrlModerator)
var tUserLinks []BBBJoinLink
for _, key := range users {
tUserLinks = append(tUserLinks, BBBJoinLink{key, "join link for User"})
}
links = BBBMeetingLinks{
ModeratorLink: BBBJoinLink{moderator, "join links for moderator"},
UserLinks: tUserLinks,
}
return links, nil
}
type BBBCreateMeetingResponseBody struct {
XMLName xml.Name `xml:"response"`
Returncode string `xml:"returncode"`
MeetingID string `xml:"meetingID"`
InternalMeetingID string `xml:"internalMeetingID"`
ParentMeetingID string `xml:"parentMeetingID"`
AttendeePW string `xml:"attendeePW"`
ModeratorPW string `xml:"moderatorPW"`
CreateTime string `xml:"createTime"`
VoiceBridge string `xml:"voiceBridge"`
DialNumber string `xml:"dialNumber"`
CreateDate string `xml:"createDate"`
HasUserJoined string `xml:"hasUserJoined"`
Duration string `xml:"duration"`
HasBeenForciblyEnded string `xml:"hasBeenForciblyEnded"`
MessageKey string `xml:"messageKey"`
Message string `xml:"message"`
}
type BBBMeetingLinks struct {
ModeratorLink BBBJoinLink
UserLinks []BBBJoinLink
}
type BBBJoinLink struct {
Name string
Url string
}
type BBBUrlParam struct {
name string
value string
}
package main
import (
"github.com/namsral/flag"
)
func (c *Config) init(args []string) error {
var (
matrixHomeserver = flag.String("homeserver", "", "Matrix homeserver")
matrixUser = flag.String("username", "", "Matrix username localpart")
matrixPassword = flag.String("password", "", "Matrix password")
bbbSecret = flag.String("bbbSecret", "", "BBB server secret")
bbbServer = flag.String("bbbServer", "", "BBB server url")
bbbMeetingTitle = flag.String("bbbMeetingTitle", "", "BBB meeting title")
database = flag.String("database", "", "SQLite runtime database path")
attendeePW = flag.String("attendeePW", "", "password for attendees")
moderatorPW = flag.String("moderatorPW", "", "password for moderators")
)
flag.Parse()
c.MatrixHomeserver = *matrixHomeserver
c.MatrixUser = *matrixUser
c.MatrixPassword = *matrixPassword
c.BbbSecret = *bbbSecret
c.BbbServer = *bbbServer
c.BbbMeetingTitle = *bbbMeetingTitle
c.Database = *database
c.AttendeePW = *attendeePW
c.ModeratorPW = *moderatorPW
return nil
}
type Config struct {
MatrixHomeserver string
MatrixUser string
MatrixPassword string
BbbSecret string
BbbServer string
BbbMeetingTitle string
Database string
AttendeePW string
ModeratorPW string
}
module matrix-bigbluebutton-bot-go
go 1.22
require (
github.com/google/uuid v1.6.0
github.com/mattn/go-sqlite3 v1.14.22
github.com/namsral/flag v1.7.4-pre
github.com/rs/zerolog v1.32.0
maunium.net/go/mautrix v0.18.1
)
require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/tidwall/gjson v1.17.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/yuin/goldmark v1.7.1 // indirect
go.mau.fi/util v0.4.2 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
)
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
var Conf Config
var meetingRequestChannel chan MeetingCreationRequest
var chatResponseChannel chan string
var terminateChannel chan int
func main() {
err := Conf.init(os.Args)
if err != nil {
}
meetingRequestChannel = make(chan MeetingCreationRequest)
chatResponseChannel = make(chan string)
terminateChannel = make(chan int)
go matrix()
go func() {
for {
select {
case tReq := <-meetingRequestChannel:
tMeeting, err := CreateBbbMeeting(Conf)
if err != nil {
panic(err)
}
tLinks, _ := CreateMeetingLinks(tMeeting, "asdf", tReq.Members)
tMesg := CreateChatMessage(tMeeting, tLinks)
chatResponseChannel <- tMesg
}
}
}()
quitChannel := make(chan os.Signal, 1)
signal.Notify(quitChannel, syscall.SIGINT, syscall.SIGTERM)
<-quitChannel
}
func Close(err error, exitCode int) {
fmt.Println("err: ")
fmt.Println(err)
terminateChannel <- exitCode
}
type MeetingCreationRequest struct {
Members []string
Moderator string
}
func CreateChatMessage(resp BBBCreateMeetingResponseBody, links BBBMeetingLinks) string {
var msg = fmt.Sprintf("<p><b>Successfully created meeting.</b></p>\n "+
"<p>Meeting ID:%s</p> "+
"<p>Dial Number:%s</p>",
resp.MeetingID,
resp.DialNumber,
)
msg = msg + fmt.Sprintf("<p><a href=\"%s\">Join as %s (Moderator)</a></p>\n", links.ModeratorLink.Url, links.ModeratorLink.Name)
for _, key := range links.UserLinks {
msg = msg + fmt.Sprintf("<p><a href=\"%s\">Join as %s</a></p>\n", key.Url, key.Name)
}
return msg
}
// Copyright (C) 2017 Tulir Asokan
// Copyright (C) 2018-2020 Luca Weiss
// Copyright (C) 2023 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import (
"context"
"errors"
"flag"
"fmt"
_ "github.com/mattn/go-sqlite3"
"github.com/rs/zerolog/log"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/crypto/cryptohelper"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/format"
"maunium.net/go/mautrix/id"
"os"
"sync"
)
func matrix() {
flag.Parse()
//if Conf.MatrixUser == "" || Conf.MatrixUser == "" || *homeserver == "" {
// _, _ = fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
// flag.PrintDefaults()
// os.Exit(1)
//}
//
// create client
client, err := mautrix.NewClient(Conf.MatrixHomeserver, "", "")
if err != nil {
panic(err)
}
// create prompt
//rl, err := readline.New("[no room]> ")
//if err != nil {
// panic(err)
//}
//defer rl.Close()
//log := zerolog.New(zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) {
// w.Out = rl.Stdout()
// w.TimeFormat = time.Stamp
//})).With().Timestamp().Logger()
//if !*debug {
//log = log.Level(zerolog.InfoLevel)
//}
//exzerolog.SetupDefaults(&log)
//client.Log = log
var lastRoomID id.RoomID
// create syncer
syncer := client.Syncer.(*mautrix.DefaultSyncer)
// #### event listeners
syncer.OnEventType(event.EventMessage, func(ctx context.Context, evt *event.Event) {
lastRoomID = evt.RoomID
//rl.SetPrompt(fmt.Sprintf("%s> ", lastRoomID))
//if time.Now().UnixMilli()-evt.Timestamp > 30000 {
// fmt.Println("msg too old, skipping ")
// return
//}
//botCommand := "!create"
//match, _ := regexp.MatchString("^"+botCommand+" ", evt.Content.AsMessage().Body)
//if !match {
// fmt.Println("msg doesn't begin with bot command, skipping ")
// return
//}
// don't react to own messages
if evt.Sender.String() == client.UserID.String() {
return
}
// for testing
//resp, err := client.SendText(context.TODO(), lastRoomID, "asdf")
//if err != nil {
// fmt.Println("resp: ")
//
// fmt.Println(resp)
//}
//5. get the Config.MatrixUser of the person that sent the create command
//sender := evt.Sender.String()
allMembers, err := client.JoinedMembers(ctx, lastRoomID)
if err != nil {
// todo: error handling
}
//fmt.Println("allMembers: ")
//fmt.Println(allMembers)
//cReq <- allMembers
members := []string{}
for _, key := range allMembers.Joined {
// skip if empty display name or member is the bot
if key.DisplayName == "" || key.DisplayName == Conf.MatrixUser {
continue
}
members = append(members, key.DisplayName)
}
//7. get room name
// lastRoomID
//8. generate uuid4 for meeting id
//if err != nil {
// // todo: error handling
//}
//fmt.Println("meetingId: ")
//fmt.Println(meetingId)
meetingReq := MeetingCreationRequest{
members,
evt.Sender.String(),
}
fmt.Println("sending to meetingRequestChannel: ")
fmt.Println(meetingReq)
meetingRequestChannel <- meetingReq
go func() {
for {
val := <-chatResponseChannel
//_ = <-chatResponseChannel
//fmt.Println("val: ")
//fmt.Println(val)
//_, err = client.SendText(ctx, lastRoomID, lastRoomID.String()+val)
content := format.HTMLToContent(val)
_, err := client.SendMessageEvent(ctx, lastRoomID, event.EventMessage, &content)
if err != nil {
return
}
}
}()
//fmt.Println(evt.Timestamp)
//fmt.Println(time.Now().Unix())
//fmt.Println(time.Now().UnixMilli())
//log.Info().
// Str("sender", evt.Sender.String()).
// Str("type", evt.Type.String()).
// Str("id", evt.ID.String()).
// Str("body", evt.Content.AsMessage().Body).
// Msg("Received message")
})
// join room if invited
syncer.OnEventType(event.StateMember, func(ctx context.Context, evt *event.Event) {
if evt.GetStateKey() == client.UserID.String() && evt.Content.AsMember().Membership == event.MembershipInvite {
_, err := client.JoinRoomByID(ctx, evt.RoomID)
if err == nil {
lastRoomID = evt.RoomID
//rl.SetPrompt(fmt.Sprintf("%s> ", lastRoomID))
log.Info().
Str("room_id", evt.RoomID.String()).
Str("inviter", evt.Sender.String()).
Msg("Joined room after invite")
} else {
log.Error().Err(err).
Str("room_id", evt.RoomID.String()).
Str("inviter", evt.Sender.String()).
Msg("Failed to join room after invite")
}
}
})
fmt.Println("create crypto helper")
cryptoHelper, err := cryptohelper.NewCryptoHelper(client, []byte("meow"), Conf.Database)
if err != nil {
panic(err)
}
// You can also store the user/device IDs and access token and put them in the client beforehand instead of using LoginAs.
//client.UserID = "..."
//client.DeviceID = "..."
//client.AccessToken = "..."
// You don't need to set a device ID in LoginAs because the crypto helper will set it for you if necessary.
fmt.Println("cryptohelper login")
cryptoHelper.LoginAs = &mautrix.ReqLogin{
Type: mautrix.AuthTypePassword,
Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: Conf.MatrixUser},
Password: Conf.MatrixPassword,
}
// If you want to use multiple clients with the same DB, you should set a distinct database account ID for each one.
//cryptoHelper.DBAccountID = ""
// todo: replace context.TODO() with real context
fmt.Println("initialize crypto helper")
err = cryptoHelper.Init(context.TODO())
if err != nil {
panic(err)
}
// Set the client crypto helper in order to automatically encrypt outgoing messages
client.Crypto = cryptoHelper
log.Info().Msg("Now running")
syncCtx, cancelSync := context.WithCancel(context.Background())
var syncStopWait sync.WaitGroup
syncStopWait.Add(1)
go func() {
err = client.SyncWithContext(syncCtx)
defer syncStopWait.Done()
if err != nil && !errors.Is(err, context.Canceled) {
panic(err)
}
}()
// listen to terminateChannel and cancel sync and close on signal
// todo: use defer for closing?
for {
//exitCode := <-terminateChannel
select {
case exitCode := <-terminateChannel:
fmt.Println("received exitCode: ")
fmt.Println(exitCode)
cancelSync()
syncStopWait.Wait()
err = cryptoHelper.Close()
os.Exit(exitCode)
}
//fmt.Println("received from terminateChannel: ")
//fmt.Println(exitCode)
//resp, err := client.SendText(context.TODO(), lastRoomID, "Quitting. Bye :)")
//if err != nil {
// log.Error().Err(err).Msg("Failed to send event")
//} else {
// log.Info().Str("event_id", resp.EventID.String()).Msg("Event sent")
//}
//if err != nil {
// log.Error().Err(err).Msg("Error closing database")
//}
//defer os.Exit(exitCode)
}
//for {
// todo remove this
//_, err := rl.Readline()
//if err != nil { // io.EOF
// break
//}
//if lastRoomID == "" {
// log.Error().Msg("Wait for an incoming message before sending messages")
// continue
//}
//resp, err := client.SendText(context.TODO(), lastRoomID, line)
//if err != nil {
// log.Error().Err(err).Msg("Failed to send event")
//} else {
// log.Info().Str("event_id", resp.EventID.String()).Msg("Event sent")
//}
//}
}