api: add some endpoints (&more)

This commit is contained in:
saces 2026-04-17 16:56:32 +02:00
parent e1413fe803
commit 2577732bb4
9 changed files with 386 additions and 34 deletions

26
libmxclient/API.md Normal file
View file

@ -0,0 +1,26 @@
# libmxclient api doc
C API
simple highlevel matrix api intended for bindings to script languages
`everything is a string`
this should make bindings easier.
parameters are strings, either a simple name or json strings
return values are either a strings starting with `ERR:` or a json result
some funktions might return `SUCCESS.` as equivalent for `ret 0`
returned strings must be free'ed y the caller
it's the callers task to keep passed callback pointers alive
## versions
`v0` unstable, devolopment version
all other version are stable, may get additions and security fixes, no other changes

View file

@ -5,8 +5,8 @@ go 1.25.0
require (
github.com/mattn/go-sqlite3 v1.14.42
github.com/rs/zerolog v1.35.0
go.mau.fi/util v0.9.8-0.20260406161447-0300c476893a
maunium.net/go/mautrix v0.26.5-0.20260415212519-aa49a44fe395
go.mau.fi/util v0.9.8
maunium.net/go/mautrix v0.27.0
)
require (

View file

@ -60,6 +60,8 @@ go.mau.fi/util v0.9.7 h1:AWGNbJfz1zRcQOKeOEYhKUG2fT+/26Gy6kyqcH8tnBg=
go.mau.fi/util v0.9.7/go.mod h1:5T2f3ZWZFAGgmFwg3dGw7YK6kIsb9lryDzvynoR98pE=
go.mau.fi/util v0.9.8-0.20260406161447-0300c476893a h1:OQQF3rTJH10l6+dcP0OKnYbNDMBTGoIZZINNJm8QBG8=
go.mau.fi/util v0.9.8-0.20260406161447-0300c476893a/go.mod h1:5T2f3ZWZFAGgmFwg3dGw7YK6kIsb9lryDzvynoR98pE=
go.mau.fi/util v0.9.8 h1:+/jf8eM2dAT2wx9UidmaneH28r/CSCKCniCyby1qWz8=
go.mau.fi/util v0.9.8/go.mod h1:up/5mbzH2M1pSBNXqRxODn8dg/hEKbLJu92W4/SNAX0=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
@ -117,3 +119,5 @@ maunium.net/go/mautrix v0.26.5-0.20260413182302-f3fab8d38470 h1:vehV8Ev2TzpV5DH9
maunium.net/go/mautrix v0.26.5-0.20260413182302-f3fab8d38470/go.mod h1:MX4DQLiBe0c7sI/wizruqdxHinSOWs42/DYsP9GH7Q4=
maunium.net/go/mautrix v0.26.5-0.20260415212519-aa49a44fe395 h1:tJX3I0CSxuZAaAeF4VRiurfsYow8N2qOgr7k5wNbtVo=
maunium.net/go/mautrix v0.26.5-0.20260415212519-aa49a44fe395/go.mod h1:MX4DQLiBe0c7sI/wizruqdxHinSOWs42/DYsP9GH7Q4=
maunium.net/go/mautrix v0.27.0 h1:yfEYwoIluVWkofUgbZl9gP4i5nQTF+QNsxtb+r5bKlM=
maunium.net/go/mautrix v0.27.0/go.mod h1:7QpEQiTy6p4LHkXXaZI+N46tGYy8HMhD0JjzZAFoFWs=

View file

@ -302,6 +302,7 @@ func NewMXClient(homeserverURL string, userID id.UserID, accessToken string) (*M
syncer.OnEventType(event.EventMessage, mxclient._onMessage)
syncer.OnEventType(event.StateMember, mxclient._onEventMember)
syncer.OnEventType(event.AccountDataDirectChats, mxclient._onAccountDataDM)
syncer.OnEventType(event.EventRedaction, mxclient._onMessage)
mxclient._loadDirectMap()

View file

@ -13,6 +13,7 @@ import (
"unsafe"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
@ -37,8 +38,60 @@ static inline void call_c_on_sys_handler(on_message_handler_ptr ptr, char* jsonS
*/
import "C"
/*
error types
*/
var apiCanceled = errors.New("canceled by api call")
/*
type conversion helpers with some basic validation
*/
func c2RoomID(roomid *C.char) (id.RoomID, error) {
rid := C.GoString(roomid)
if rid == "" || rid[0] != '!' {
return "", errors.New("invalid room id")
}
return id.RoomID(rid), nil
}
func c2EventID(eventid *C.char) (id.EventID, error) {
eid := C.GoString(eventid)
if eid == "" || eid[0] != '$' {
return "", errors.New("invalid event id")
}
return id.EventID(eid), nil
}
func c2EventType(eventtype *C.char) (et event.Type, err error) {
err = et.UnmarshalText([]byte(C.GoString(eventtype)))
return
}
func c2ContentJSON(contentjson *C.char) (contentJSON any, err error) {
err = json.Unmarshal([]byte(C.GoString(contentjson)), &contentJSON)
return
}
func returnJSON(out any, err error) *C.char {
if err != nil {
return C.CString(fmt.Sprintf("ERR: %v", err))
}
res, err := json.Marshal(out)
if err != nil {
return C.CString(fmt.Sprintf("ERR: %v", err))
}
return C.CString(string(res))
}
func returnErr(err error) *C.char {
if err == nil {
return C.CString("SUCCESS.")
}
return C.CString(fmt.Sprintf("ERR: %v", err))
}
/*
matrix client with c callback
*/
@ -86,7 +139,7 @@ func (cli *CBClient) Set_on_sys_handler(fn C.on_sys_handler_ptr, pobj unsafe.Poi
cli.on_sys_handler_pobj = pobj
}
// NewCClient creates a new Matrix Client ready for syncing
// NewCBClient creates a new Matrix Client ready for syncing
func NewCBClient(homeserverURL string, userID id.UserID, accessToken string) (*CBClient, error) {
client, err := mxclient.NewMXClient(homeserverURL, userID, accessToken)
if err != nil {
@ -355,35 +408,188 @@ func apiv0_sendmessage(cid C.int, data *C.char) *C.char {
return C.CString(s)
}
//export apiv0_sendmessageevent
func apiv0_sendmessageevent(cid C.int, roomid *C.char, eventtype *C.char, contentjson *C.char) *C.char {
roomID, err := c2RoomID(roomid)
if err != nil {
return returnErr(err)
}
eventType, err := c2EventType(eventtype)
if err != nil {
return returnErr(err)
}
contentJSON, err := c2ContentJSON(contentjson)
if err != nil {
return returnErr(err)
}
cli, err := getClient(int(cid))
if err != nil {
return returnErr(err)
}
return returnJSON(cli.SendMessageEvent(context.Background(), roomID, eventType, contentJSON))
}
//export apiv0_sendstateevent
func apiv0_sendstateevent(cid C.int, roomid *C.char, eventtype *C.char, statekey *C.char, contentjson *C.char) *C.char {
roomID, err := c2RoomID(roomid)
if err != nil {
return returnErr(err)
}
eventType, err := c2EventType(eventtype)
if err != nil {
return returnErr(err)
}
contentJSON, err := c2ContentJSON(contentjson)
if err != nil {
return returnErr(err)
}
cli, err := getClient(int(cid))
if err != nil {
return returnErr(err)
}
return returnJSON(cli.SendStateEvent(context.Background(), roomID, eventType, C.GoString(statekey), contentJSON))
}
//export apiv0_stateevent
func apiv0_stateevent(cid C.int, roomid *C.char, eventtype *C.char, statekey *C.char) *C.char {
roomID, err := c2RoomID(roomid)
if err != nil {
return returnErr(err)
}
eventType, err := c2EventType(eventtype)
if err != nil {
return returnErr(err)
}
cli, err := getClient(int(cid))
if err != nil {
return returnErr(err)
}
var outContent any
err = cli.StateEvent(context.Background(), roomID, eventType, C.GoString(statekey), outContent)
return returnJSON(outContent, err)
}
//export apiv0_getaccountdata
func apiv0_getaccountdata(cid C.int, name *C.char) *C.char {
cli, err := getClient(int(cid))
if err != nil {
return returnErr(err)
}
var outContent any
err = cli.GetAccountData(context.Background(), C.GoString(name), &outContent)
return returnJSON(outContent, err)
}
//export apiv0_setaccountdata
func apiv0_setaccountdata(cid C.int, name *C.char, data *C.char) *C.char {
contentJSON, err := c2ContentJSON(data)
if err != nil {
return returnErr(err)
}
cli, err := getClient(int(cid))
if err != nil {
return returnErr(err)
}
return returnErr(cli.SetAccountData(context.Background(), C.GoString(name), contentJSON))
}
//export apiv0_getroomaccountdata
func apiv0_getroomaccountdata(cid C.int, roomid *C.char, name *C.char) *C.char {
roomID, err := c2RoomID(roomid)
if err != nil {
return returnErr(err)
}
cli, err := getClient(int(cid))
if err != nil {
return returnErr(err)
}
var outContent any
err = cli.GetRoomAccountData(context.Background(), roomID, C.GoString(name), &outContent)
return returnJSON(outContent, err)
}
//export apiv0_setroomaccountdata
func apiv0_setroomaccountdata(cid C.int, roomid *C.char, name *C.char, data *C.char) *C.char {
roomID, err := c2RoomID(roomid)
if err != nil {
return returnErr(err)
}
contentJSON, err := c2ContentJSON(data)
if err != nil {
return returnErr(err)
}
cli, err := getClient(int(cid))
if err != nil {
return returnErr(err)
}
return returnErr(cli.SetRoomAccountData(context.Background(), roomID, C.GoString(name), contentJSON))
}
//export apiv0_redactevent
func apiv0_redactevent(cid C.int, roomid *C.char, eventid *C.char, reason *C.char) *C.char {
roomID, err := c2RoomID(roomid)
if err != nil {
return returnErr(err)
}
eventID, err := c2EventID(eventid)
if err != nil {
return returnErr(err)
}
cli, err := getClient(int(cid))
if err != nil {
return returnErr(err)
}
var req mautrix.ReqRedact
req.Reason = C.GoString(reason)
resp, err := cli.RedactEvent(context.Background(), roomID, eventID, req)
return returnJSON(resp, err)
}
//export apiv0_getevent
func apiv0_getevent(cid C.int, roomid *C.char, eventid *C.char) *C.char {
roomID, err := c2RoomID(roomid)
if err != nil {
return returnErr(err)
}
eventID, err := c2EventID(eventid)
if err != nil {
return returnErr(err)
}
cli, err := getClient(int(cid))
if err != nil {
return returnErr(err)
}
resp, err := cli.GetEvent(context.Background(), roomID, eventID)
return returnJSON(resp, err)
}
//export apiv0_leaveroom
func apiv0_leaveroom(cid C.int, roomid *C.char) *C.char {
roomID, err := c2RoomID(roomid)
if err != nil {
return returnErr(err)
}
cli, err := getClient(int(cid))
if err != nil {
return C.CString(fmt.Sprintf("ERR: %v", err))
}
err = cli.LeaveRoomAndForget(context.Background(), id.RoomID(C.GoString(roomid)))
if err != nil {
return C.CString(fmt.Sprintf("ERR: %v", err))
}
return C.CString("SUCCESS.")
return returnErr(cli.LeaveRoomAndForget(context.Background(), roomID))
}
//export apiv0_joinroom
func apiv0_joinroom(cid C.int, roomid *C.char) *C.char {
roomID, err := c2RoomID(roomid)
if err != nil {
return returnErr(err)
}
cli, err := getClient(int(cid))
if err != nil {
return C.CString(fmt.Sprintf("ERR: %v", err))
}
resp, err := cli.JoinRoomByID(context.Background(), id.RoomID(C.GoString(roomid)))
if err != nil {
return C.CString(fmt.Sprintf("ERR: %v", err))
}
out, err := json.Marshal(resp)
if err != nil {
return C.CString(fmt.Sprintf("ERR: %v", err))
}
s := string(out)
return C.CString(s)
resp, err := cli.JoinRoomByID(context.Background(), roomID)
return returnJSON(resp, err)
}
//export apiv0_joinedrooms
@ -407,12 +613,7 @@ func apiv0_joinedrooms(cid C.int) *C.char {
for _, room := range resp.JoinedRooms {
roomList = append(roomList, roomListItem{RoomId: room, IsDirect: cli.IsDirectRoom(room)})
}
out, err := json.Marshal(roomList)
if err != nil {
return C.CString(fmt.Sprintf("ERR: %v", err))
}
s := string(out)
return C.CString(s)
return returnJSON(roomList, nil)
}
//export apiv0_genericrequest
@ -429,10 +630,7 @@ func apiv0_genericrequest(cid C.int, method *C.char, path *C.char, data *C.char)
urlPath := cli.BuildURLWithFullQuery(mautrix.BaseURLPath(bup), nil)
d := C.GoString(data)
resp, err := cli.MakeFullRequest(context.Background(), mautrix.FullRequest{Method: C.GoString(method), URL: urlPath, RequestBytes: []byte(d), ResponseJSON: nil})
if err != nil {
return C.CString(fmt.Sprintf("ERR: %v", err))
}
return C.CString(string(resp))
return returnJSON(resp, err)
}
//export apiv0_createroom
@ -500,12 +698,7 @@ func apiv0_getuserdm(cid C.int, userid *C.char) *C.char {
return C.CString(fmt.Sprintf("ERR: %v", err))
}
list := cli.GetUserDM(C.GoString(userid))
out, err := json.Marshal(list)
if err != nil {
return C.CString(fmt.Sprintf("ERR: %v", err))
}
s := string(out)
return C.CString(s)
return returnJSON(list, nil)
}
//export apiv0_removeclient

View file

@ -60,6 +60,15 @@ ffibuilder.cdef(
extern char* apiv0_startclient(int cid);
extern char* apiv0_stopclient(int cid);
extern char* apiv0_sendmessage(int cid, char* data);
extern char* apiv0_sendmessageevent(int cid, char* roomid, char* eventtype, char* contentjson);
extern char* apiv0_sendstateevent(int cid, char* roomid, char* eventtype, char* statekey, char* contentjson);
extern char* apiv0_stateevent(int cid, char* roomid, char* eventtype, char* statekey);
extern char* apiv0_getaccountdata(int cid, char* name);
extern char* apiv0_setaccountdata(int cid, char* name, char* data);
extern char* apiv0_getroomaccountdata(int cid, char* roomid, char* name);
extern char* apiv0_setroomaccountdata(int cid, char* roomid, char* name, char* data);
extern char* apiv0_redactevent(int cid, char* roomid, char* eventid, char* reason);
extern char* apiv0_getevent(int cid, char* roomid, char* eventid);
extern char* apiv0_leaveroom(int cid, char* roomid);
extern char* apiv0_joinedrooms(int cid);
extern char* apiv0_joinroom(int cid, char* roomid);

View file

@ -17,6 +17,15 @@ apiv0_listclients
apiv0_login
apiv0_removeclient
apiv0_sendmessage
apiv0_sendmessageevent
apiv0_sendstateevent
apiv0_stateevent
apiv0_getaccountdata
apiv0_setaccountdata
apiv0_getroomaccountdata
apiv0_setroomaccountdata
apiv0_redactevent
apiv0_getevent
apiv0_set_on_event_handler
apiv0_set_on_message_handler
apiv0_set_on_sys_handler

View file

@ -57,6 +57,72 @@ class ApiV0Api:
)
)
@staticmethod
def room_send_message(cid, roomid, eventtype, content):
return _stringresult(
lib.apiv0_sendmessageevent(
cid, _autostring(roomid), _autostring(eventtype), _autodict(content)
)
)
@staticmethod
def room_send_state(cid, roomid, eventtype, statekey, content):
return _stringresult(
lib.apiv0_sendstateevent(
cid,
_autostring(roomid),
_autostring(eventtype),
_autostring(statekey),
_autodict(content),
)
)
@staticmethod
def room_get_state(cid, roomid, eventtype, statekey):
return _stringresult(
lib.apiv0_stateevent(
cid,
_autostring(roomid),
_autostring(eventtype),
_autostring(statekey),
)
)
@staticmethod
def redact_event(cid, roomid, eventid, reason):
return _stringresult(
lib.apiv0_redactevent(
cid,
_autostring(roomid),
_autostring(eventid),
_autostring(reason),
)
)
@staticmethod
def account_getdata(cid, name):
return _stringresult(lib.apiv0_getaccountdata(cid, _autostring(name)))
@staticmethod
def account_setdata(cid, name, data):
return _stringresult(
lib.apiv0_setaccountdata(cid, _autostring(name), _autodict(data))
)
@staticmethod
def room_get_accountdata(cid, roomid, name):
return _stringresult(
lib.apiv0_getroomaccountdata(cid, _autostring(roomid), _autostring(name))
)
@staticmethod
def room_set_accountdata(cid, roomid, name, data):
return _stringresult(
lib.apiv0_setroomaccountdata(
cid, _autostring(roomid), _autostring(name), _autodict(data)
)
)
@staticmethod
def createdm(cid, uid):
return _stringresult(lib.apiv0_createdm(cid, _autostring(uid)))
@ -69,6 +135,12 @@ class ApiV0Api:
def joinroom(cid, roomid):
return _stringresult(lib.apiv0_joinroom(cid, _autostring(roomid)))
@staticmethod
def getevent(cid, roomid, eventid):
return _stringresult(
lib.apiv0_getevent(cid, _autostring(roomid), _autostring(eventid))
)
class ApiV0:
"""ApiV0"""

View file

@ -100,6 +100,44 @@ class _AsyncClient:
r = ApiV0Api.generic(self.client_id, method, path, data)
return CheckApiErrorOnly(r)
async def room_send_message(self, roomid, eventtype, content):
r = ApiV0Api.room_send_message(self.client_id, roomid, eventtype, content)
return CheckApiResult(r)
async def room_send_state(self, roomid, eventtype, statekey, content):
r = ApiV0Api.room_send_state(
self.client_id, roomid, eventtype, statekey, content
)
return CheckApiResult(r)
async def room_get_state(self, roomid, eventtype, statekey):
r = ApiV0Api.room_get_state(self.client_id, roomid, eventtype, statekey)
return CheckApiResult(r)
async def account_get_data(self, name):
r = ApiV0Api.account_getdata(self.client_id, name)
return CheckApiResult(r)
async def account_set_data(self, name, data):
r = ApiV0Api.account_setdata(self.client_id, name, data)
return CheckApiError(r)
async def room_get_accountdata(self, roomid, name):
r = ApiV0Api.room_get_accountdata(self.client_id, roomid, name)
return CheckApiResult(r)
async def room_set_accountdata(self, roomid, name, data):
r = ApiV0Api.room_set_accountdata(self.client_id, roomid, name, data)
return CheckApiError(r)
async def redact_event(self, roomid, eventid, reason):
r = ApiV0Api.redact_event(self.client_id, roomid, eventid, reason)
return CheckApiResult(r)
async def getevent(self, roomid, eventid):
r = ApiV0Api.getevent(self.client_id, roomid, eventid)
return CheckApiResult(r)
async def createdm(self, uid):
r = ApiV0Api.createdm(self.client_id, uid)
return CheckApiResult(r)