282 lines
7.1 KiB
Go
282 lines
7.1 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"github.com/rs/zerolog"
|
|
"github.com/rs/zerolog/log"
|
|
"go.mau.fi/util/exzerolog"
|
|
"io"
|
|
"maunium.net/go/mautrix"
|
|
"maunium.net/go/mautrix/event"
|
|
"maunium.net/go/mautrix/id"
|
|
"os"
|
|
"os/signal"
|
|
"regexp"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const StateDataDir = "data"
|
|
const StateLogDir = "log"
|
|
const StateFilePath = StateDataDir + "/state.json"
|
|
const StateFiltersPath = StateDataDir + "/filters.json"
|
|
|
|
func syncFunc(client *mautrix.Client, syncCtx context.Context, syncStopWait *sync.WaitGroup) {
|
|
log.Info().Msg(fmt.Sprintf("Syncing try"))
|
|
err := client.SyncWithContext(syncCtx)
|
|
if err != nil && !errors.Is(err, context.Canceled) {
|
|
log.Error().Err(err).
|
|
Msg("Failed to sync")
|
|
}
|
|
defer syncStopWait.Done()
|
|
log.Info().Msg(fmt.Sprintf("Syncing try finished"))
|
|
}
|
|
|
|
type FileStore struct {
|
|
filterIds map[id.UserID]string
|
|
loadedFilterIds bool
|
|
nextBatchTokens map[id.UserID]string
|
|
loadedBatchTokens bool
|
|
}
|
|
|
|
func (f FileStore) SaveFilterID(_ context.Context, userID id.UserID, filterID string) error {
|
|
f.filterIds[userID] = filterID
|
|
err := saveValueToFile(StateFiltersPath, f.filterIds)
|
|
if err == nil {
|
|
f.loadedFilterIds = true
|
|
} else {
|
|
f.loadedFilterIds = false
|
|
}
|
|
return err
|
|
}
|
|
|
|
func saveValueToFile(fileName string, f map[id.UserID]string) error {
|
|
file, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
writer := io.Writer(file)
|
|
encoder := json.NewEncoder(writer)
|
|
return encoder.Encode(f)
|
|
}
|
|
|
|
func (f FileStore) LoadFilterID(_ context.Context, userID id.UserID) (string, error) {
|
|
if !f.loadedFilterIds {
|
|
_, err := os.Stat(StateFiltersPath)
|
|
if err == nil {
|
|
err = loadStateFromFile(StateFiltersPath, &f.filterIds)
|
|
if err != nil {
|
|
log.Error().
|
|
Err(err).
|
|
Msg("Got an eror while reading filters")
|
|
}
|
|
}
|
|
f.loadedBatchTokens = true
|
|
}
|
|
|
|
s := f.filterIds[userID]
|
|
return s, nil
|
|
}
|
|
|
|
func (f FileStore) SaveNextBatch(ctx context.Context, userID id.UserID, nextBatchToken string) error {
|
|
f.nextBatchTokens[userID] = nextBatchToken
|
|
err := saveValueToFile(StateFilePath, f.nextBatchTokens)
|
|
if err == nil {
|
|
f.loadedBatchTokens = true
|
|
} else {
|
|
f.loadedBatchTokens = false
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (f FileStore) LoadNextBatch(ctx context.Context, userID id.UserID) (string, error) {
|
|
if !f.loadedBatchTokens {
|
|
_, err := os.Stat(StateFilePath)
|
|
if err == nil {
|
|
err := loadStateFromFile(StateFilePath, &f.nextBatchTokens)
|
|
if err != nil {
|
|
log.Error().
|
|
Err(err).
|
|
Msg("Got an eror while reading state")
|
|
}
|
|
}
|
|
f.loadedBatchTokens = true
|
|
}
|
|
|
|
s := f.nextBatchTokens[userID]
|
|
return s, nil
|
|
}
|
|
|
|
func loadStateFromFile(path string, ref *map[id.UserID]string) error {
|
|
file, err := os.OpenFile(path, os.O_RDONLY, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
decoder := json.NewDecoder(bufio.NewReader(file))
|
|
err = decoder.Decode(ref)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer func(f2 *os.File) {
|
|
_ = f2.Close()
|
|
}(file)
|
|
return nil
|
|
}
|
|
|
|
func main() {
|
|
ce, cerr := regexp.Compile("[^a-zA-Z0-9 -_]+")
|
|
if cerr != nil {
|
|
panic("Error in compiling regexp " + cerr.Error())
|
|
}
|
|
|
|
var homeserver = flag.String("homeserver", "", "homeserver")
|
|
var userId = flag.String("userId", "", "userId")
|
|
var token = flag.String("token", "", "token")
|
|
var debug = flag.Bool("debug", false, "debug")
|
|
|
|
flag.Parse()
|
|
|
|
c := make(chan os.Signal, 1)
|
|
signal.Notify(c, os.Interrupt)
|
|
signal.Notify(c, os.Kill)
|
|
|
|
var user = id.NewUserID(*userId, *homeserver)
|
|
var client, err = mautrix.NewClient(*homeserver, user, *token)
|
|
if err != nil {
|
|
panic("Couldn't connect to homeserver " + err.Error())
|
|
}
|
|
|
|
log := zerolog.New(zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) {
|
|
w.Out = os.Stdout
|
|
w.TimeFormat = time.Stamp
|
|
})).With().Timestamp().Logger()
|
|
if !*debug {
|
|
log = log.Level(zerolog.DebugLevel)
|
|
}
|
|
exzerolog.SetupDefaults(&log)
|
|
client.Log = log
|
|
|
|
// var lastRoomId id.RoomID
|
|
var lastRoomName string
|
|
|
|
syncer := client.Syncer.(*mautrix.DefaultSyncer)
|
|
client.Store = FileStore{
|
|
loadedFilterIds: false,
|
|
filterIds: make(map[id.UserID]string),
|
|
loadedBatchTokens: false,
|
|
nextBatchTokens: make(map[id.UserID]string)}
|
|
|
|
roomNames := make(map[string]string)
|
|
filesNames := make(map[string]*os.File)
|
|
syncer.OnEventType(event.StateRoomName, func(ctx context.Context, evt *event.Event) {
|
|
roomNames[evt.RoomID.String()] = evt.Content.AsRoomName().Name
|
|
log.Info().
|
|
Str("sender", evt.Sender.String()).
|
|
Str("type", evt.Type.String()).
|
|
Str("id", evt.ID.String()).
|
|
Msg(fmt.Sprintf("Received sync state room name mapping %s -> %s",
|
|
evt.RoomID.String(),
|
|
evt.Content.AsRoomName().Name))
|
|
})
|
|
|
|
var lastRoomId id.RoomID
|
|
_ = os.MkdirAll(StateLogDir, 0644)
|
|
_ = os.MkdirAll(StateDataDir, 0644)
|
|
syncer.OnEventType(
|
|
event.EventMessage,
|
|
func(ctx context.Context, evt *event.Event) {
|
|
lastRoomId = evt.RoomID
|
|
lastRoomName = roomNames[lastRoomId.String()]
|
|
if lastRoomName == "" {
|
|
lastRoomName = lastRoomId.String()
|
|
}
|
|
|
|
var file *os.File
|
|
file = filesNames[lastRoomName]
|
|
if file == nil {
|
|
lastRoomName = string(ce.ReplaceAll([]byte(lastRoomName), []byte("_")))
|
|
channelLogPath := StateLogDir + "/" + lastRoomName + ".log"
|
|
file, err := os.OpenFile(channelLogPath, os.O_RDWR|os.O_CREATE, 0644)
|
|
if err != nil {
|
|
panic("Failed to open file " + lastRoomName)
|
|
}
|
|
|
|
_, err = file.Seek(0, io.SeekEnd)
|
|
|
|
if err != nil {
|
|
panic("Failed to seek file " + lastRoomName)
|
|
}
|
|
filesNames[lastRoomName] = file
|
|
if err != nil {
|
|
panic("Failed to create file " + lastRoomName)
|
|
}
|
|
}
|
|
|
|
file = filesNames[lastRoomName]
|
|
|
|
timestamp := evt.Timestamp
|
|
t := time.Unix(timestamp/1000, timestamp%1000*1000)
|
|
msg := evt.Content.AsMessage()
|
|
log.Info().
|
|
Str("sender", evt.Sender.String()).
|
|
Str("type", evt.Type.String()).
|
|
Str("id", evt.ID.String()).
|
|
Str("body", msg.Body).
|
|
Msg(fmt.Sprintf("Received sync message on channel %s (%s)",
|
|
lastRoomName, lastRoomId))
|
|
|
|
_, err := file.WriteString(t.String() + " " + "[" + evt.Sender.String() + "] " + msg.Body + "\n")
|
|
if err != nil {
|
|
panic("Failed to write to the file " + lastRoomName)
|
|
}
|
|
|
|
err = file.Sync()
|
|
if err != nil {
|
|
return
|
|
}
|
|
},
|
|
)
|
|
|
|
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 {
|
|
log.Error().Err(err).
|
|
Str("room_id", evt.RoomID.String()).
|
|
Str("inviter", evt.Sender.String()).
|
|
Msg("Failed to join " + evt.RoomID.String() +
|
|
" + room after invite by " + evt.Sender.String())
|
|
} else {
|
|
log.Info().
|
|
Str("room_id", evt.RoomID.String()).
|
|
Str("inviter", evt.Sender.String()).
|
|
Msg("Succeeded to join " + evt.RoomID.String() +
|
|
" + room after invite by " + evt.Sender.String())
|
|
}
|
|
}
|
|
})
|
|
|
|
log.Info().Msg("Now running")
|
|
syncCtx, cancelSync := context.WithCancel(context.Background())
|
|
var syncStopWait sync.WaitGroup
|
|
syncStopWait.Add(1)
|
|
|
|
go syncFunc(client, syncCtx, &syncStopWait)
|
|
|
|
for sig := range c {
|
|
if sig == os.Interrupt {
|
|
break
|
|
}
|
|
}
|
|
|
|
cancelSync()
|
|
syncStopWait.Wait()
|
|
}
|