pygomx/libmxclient/mxclient/client.go

194 lines
5.2 KiB
Go
Raw Normal View History

2026-01-31 08:13:53 +01:00
// Copyright (C) 2026 saces@c-base.org
// SPDX-License-Identifier: AGPL-3.0-only
package mxclient
import (
"context"
"encoding/json"
"errors"
"fmt"
"mxclientlib/determinant/mxpassfile"
_ "github.com/mattn/go-sqlite3"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"go.mau.fi/util/dbutil"
_ "go.mau.fi/util/dbutil/litestream"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/crypto"
"maunium.net/go/mautrix/crypto/cryptohelper"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
"maunium.net/go/mautrix/sqlstatestore"
)
type MXClient struct {
*mautrix.Client
OnEvent func(string)
OnMessage func(string)
}
func (mxc *MXClient) _onEvent(ctx context.Context, evt *event.Event) {
if evt.GetStateKey() == mxc.UserID.String() && evt.Content.AsMember().Membership == event.MembershipInvite {
_, err := mxc.JoinRoomByID(ctx, evt.RoomID)
if err == nil {
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")
}
} else {
fmt.Printf("Got event: %#v\n", evt)
}
}
func (mxc *MXClient) _onMessage(ctx context.Context, evt *event.Event) {
out, err := json.Marshal(map[string]interface{}{"sender": evt.Sender.String(),
"type": evt.Type.String(),
"id": evt.ID.String(),
"roomid": evt.RoomID.String(),
"content": evt.Content.Raw})
if err != nil {
log.Error().Err(err).
Str("id", evt.ID.String()).
Str("inviter", evt.Sender.String()).
Msg("Marshalling error")
return
}
mxc.OnMessage(string(out))
/*
log.Info().
Str("sender", evt.Sender.String()).
Str("type", evt.Type.String()).
Str("id", evt.ID.String()).
Str("roomid", evt.RoomID.String()).
Str("body", evt.Content.AsMessage().Body).
Msg("Received message")
fmt.Printf("Got message: %#v\n", evt)
*/
}
type sendmessage_data_content struct {
Body string `json:"body"`
}
type sendmessage_data struct {
RoomId id.RoomID `json:"roomid"`
Type event.Type `json:"type"`
Content sendmessage_data_content `json:"content"`
}
func (mxc *MXClient) SendRoomMessage(ctx context.Context, data string) (*mautrix.RespSendEvent, error) {
var smd sendmessage_data
err := json.Unmarshal([]byte(data), &smd)
if err != nil {
return nil, err
}
resp, err := mxc.SendMessageEvent(ctx, smd.RoomId, event.EventMessage, smd.Content)
if err != nil {
return nil, err
}
return resp, nil
}
// NewMXClient creates a new Matrix Client ready for syncing
func NewMXClient(homeserverURL string, userID id.UserID, accessToken string) (*MXClient, error) {
client, err := mautrix.NewClient(homeserverURL, userID, accessToken)
if err != nil {
return nil, err
}
// keep this for the import
client.Log = zerolog.Nop()
// client.Log = zerolog.New(os.Stdout)
// client.SyncTraceLog = true
resp, err := client.Whoami(context.Background())
if err != nil {
return nil, err
}
client.DeviceID = resp.DeviceID
//fmt.Printf("Device ID: %s\n", client.DeviceID)
rawdb, err := dbutil.NewWithDialect("smalbot.db", "sqlite3")
if err != nil {
return nil, err
}
//fmt.Println("db is offen.")
syncer, ok := client.Syncer.(*mautrix.DefaultSyncer)
if !ok {
return nil, errors.New("panic: syncer implementation error")
}
//mxclient.StateStore = mautrix.NewMemoryStateStore()
stateStore := sqlstatestore.NewSQLStateStore(rawdb, dbutil.NoopLogger, false)
err = stateStore.Upgrade(context.Background())
if err != nil {
return nil, fmt.Errorf("failed to upgrade state db: %w", err)
}
client.StateStore = stateStore
pickleKey := []byte("pickle")
//cryptoStore := crypto.NewMemoryStore(nil)
cryptoStore := crypto.NewSQLCryptoStore(rawdb, dbutil.ZeroLogger(log.With().Str("db_section", "crypto").Logger()), "", "", pickleKey)
err = cryptoStore.DB.Upgrade(context.Background())
if err != nil {
return nil, fmt.Errorf("failed to upgrade crypto db: %w", err)
}
client.Crypto, err = cryptohelper.NewCryptoHelper(client, pickleKey, cryptoStore)
if err != nil {
return nil, err
}
err = client.Crypto.Init(context.TODO())
if err != nil {
return nil, err
}
client.Store = cryptoStore
mxclient := &MXClient{client, nil, nil}
syncer.ParseEventContent = true
syncer.OnEvent(client.StateStoreSyncHandler)
syncer.OnEventType(event.EventMessage, mxclient._onMessage)
syncer.OnEventType(event.StateMember, mxclient._onEvent)
return mxclient, nil
}
func CreateClient(storage_path string, url string, userID string, accessToken string) (*MXClient, error) {
return nil, fmt.Errorf("nope.")
}
func CreateClientPass(mxpassfile_path string, storage_path string, url string, localpart string, domain string) (*MXClient, error) {
pf, err := mxpassfile.ReadPassfile(mxpassfile_path)
if err != nil {
return nil, err
}
//fmt.Printf("mxpass pf: '%#v'\n", pf)
//fmt.Printf("mxpass find: '%s' '%s' '%s'\n", url, localpart, domain)
e := pf.FindPasswordFill(url, localpart, domain)
if e != nil {
//fmt.Printf("mxpass: %#v\n", e)
return NewMXClient(e.Matrixhost, id.NewUserID(e.Localpart, e.Domain), e.Token)
}
return nil, fmt.Errorf("nope.")
}