2023-05-08 05:22:20 +00:00
// @ts-check
2023-07-04 05:19:17 +00:00
const Ty = require ( "../types" )
2023-09-25 04:36:27 +00:00
const assert = require ( "assert" ) . strict
2023-05-08 11:37:51 +00:00
2023-05-08 05:22:20 +00:00
const passthrough = require ( "../passthrough" )
const { discord , sync , db } = passthrough
/** @type {import("./mreq")} */
const mreq = sync . require ( "./mreq" )
/** @type {import("./file")} */
const file = sync . require ( "./file" )
2023-05-08 12:58:46 +00:00
/** @type {import("./txnid")} */
const makeTxnId = sync . require ( "./txnid" )
2023-05-08 20:03:57 +00:00
/ * *
* @ param { string } p endpoint to access
2023-08-17 04:41:28 +00:00
* @ param { string ? } [ mxid ] optional : user to act as , for the ? user _id parameter
2023-07-28 05:05:13 +00:00
* @ param { { [ x : string ] : any } } [ otherParams ] optional : any other query parameters to add
2023-05-08 20:03:57 +00:00
* @ returns { string } the new endpoint
* /
2023-07-28 05:05:13 +00:00
function path ( p , mxid , otherParams = { } ) {
2023-08-21 09:04:41 +00:00
const u = new URL ( p , "http://localhost" )
2023-11-25 09:55:11 +00:00
if ( mxid ) u . searchParams . set ( "user_id" , mxid )
2023-08-21 09:04:41 +00:00
for ( const entry of Object . entries ( otherParams ) ) {
if ( entry [ 1 ] != undefined ) {
u . searchParams . set ( entry [ 0 ] , entry [ 1 ] )
}
}
2023-11-25 09:55:11 +00:00
let result = u . pathname
const str = u . searchParams . toString ( )
if ( str ) result += "?" + str
return result
2023-05-08 12:58:46 +00:00
}
2023-05-08 05:22:20 +00:00
/ * *
2023-05-08 11:37:51 +00:00
* @ param { string } username
2023-07-04 05:19:17 +00:00
* @ returns { Promise < Ty . R . Registered > }
2023-05-08 05:22:20 +00:00
* /
function register ( username ) {
2023-08-21 09:04:41 +00:00
console . log ( ` [api] register: ${ username } ` )
return mreq . mreq ( "POST" , "/client/v3/register" , {
type : "m.login.application_service" ,
username
} )
2023-05-08 05:22:20 +00:00
}
2023-05-08 11:37:51 +00:00
/ * *
2023-05-08 12:58:46 +00:00
* @ returns { Promise < string > } room ID
2023-05-08 11:37:51 +00:00
* /
2023-05-08 12:58:46 +00:00
async function createRoom ( content ) {
2023-08-21 09:04:41 +00:00
console . log ( ` [api] create room: ` , content )
/** @type {Ty.R.RoomCreated} */
const root = await mreq . mreq ( "POST" , "/client/v3/createRoom" , content )
return root . room _id
2023-05-08 12:58:46 +00:00
}
/ * *
* @ returns { Promise < string > } room ID
* /
async function joinRoom ( roomIDOrAlias , mxid ) {
2023-08-21 09:04:41 +00:00
/** @type {Ty.R.RoomJoined} */
const root = await mreq . mreq ( "POST" , path ( ` /client/v3/join/ ${ roomIDOrAlias } ` , mxid ) )
return root . room _id
2023-05-08 12:58:46 +00:00
}
async function inviteToRoom ( roomID , mxidToInvite , mxid ) {
2023-08-21 09:04:41 +00:00
await mreq . mreq ( "POST" , path ( ` /client/v3/rooms/ ${ roomID } /invite ` , mxid ) , {
user _id : mxidToInvite
} )
2023-05-08 11:37:51 +00:00
}
2023-07-01 13:41:31 +00:00
async function leaveRoom ( roomID , mxid ) {
2023-08-21 09:04:41 +00:00
await mreq . mreq ( "POST" , path ( ` /client/v3/rooms/ ${ roomID } /leave ` , mxid ) , { } )
2023-07-01 13:41:31 +00:00
}
2023-07-11 04:51:30 +00:00
/ * *
* @ param { string } roomID
* @ param { string } eventID
* @ template T
* /
async function getEvent ( roomID , eventID ) {
2023-08-21 09:04:41 +00:00
/** @type {Ty.Event.Outer<T>} */
const root = await mreq . mreq ( "GET" , ` /client/v3/rooms/ ${ roomID } /event/ ${ eventID } ` )
return root
2023-07-11 04:51:30 +00:00
}
2023-11-23 03:11:46 +00:00
/ * *
* @ param { string } roomID
* @ param { number } ts unix silliseconds
* /
async function getEventForTimestamp ( roomID , ts ) {
/** @type {{event_id: string, origin_server_ts: number}} */
2023-11-25 09:56:11 +00:00
const root = await mreq . mreq ( "GET" , path ( ` /client/v1/rooms/ ${ roomID } /timestamp_to_event ` , null , { ts } ) )
2023-11-23 03:11:46 +00:00
return root
}
2023-05-08 11:37:51 +00:00
/ * *
* @ param { string } roomID
2023-07-04 05:19:17 +00:00
* @ returns { Promise < Ty . Event . BaseStateEvent [ ] > }
2023-05-08 11:37:51 +00:00
* /
function getAllState ( roomID ) {
2023-08-21 09:04:41 +00:00
return mreq . mreq ( "GET" , ` /client/v3/rooms/ ${ roomID } /state ` )
}
/ * *
* @ param { string } roomID
* @ param { string } type
* @ param { string } key
* @ returns the * content * of the state event
* /
function getStateEvent ( roomID , type , key ) {
return mreq . mreq ( "GET" , ` /client/v3/rooms/ ${ roomID } /state/ ${ type } / ${ key } ` )
2023-05-08 11:37:51 +00:00
}
2023-07-11 04:51:30 +00:00
/ * *
* "Any of the AS's users must be in the room. This API is primarily for Application Services and should be faster to respond than /members as it can be implemented more efficiently on the server."
* @ param { string } roomID
2024-02-09 04:29:05 +00:00
* @ returns { Promise < { joined : { [ mxid : string ] : { avatar _url : string ? , display _name : string ? } } } > }
2023-07-11 04:51:30 +00:00
* /
function getJoinedMembers ( roomID ) {
2023-08-21 09:04:41 +00:00
return mreq . mreq ( "GET" , ` /client/v3/rooms/ ${ roomID } /joined_members ` )
2023-07-11 04:51:30 +00:00
}
2024-03-25 12:10:29 +00:00
/ * *
* @ param { string } roomID
* @ param { { from ? : string , limit ? : any } } pagination
* @ returns { Promise < Ty . HierarchyPagination < Ty . R . Hierarchy >> }
* /
function getHierarchy ( roomID , pagination ) {
let path = ` /client/v1/rooms/ ${ roomID } /hierarchy `
if ( ! pagination . from ) delete pagination . from
if ( ! pagination . limit ) pagination . limit = 50
path += ` ? ${ new URLSearchParams ( pagination ) } `
return mreq . mreq ( "GET" , path )
}
2023-09-25 04:36:27 +00:00
/ * *
* @ param { string } roomID
* @ param { string } eventID
2023-10-14 10:34:02 +00:00
* @ param { { from ? : string , limit ? : any } } pagination
2023-09-25 04:36:27 +00:00
* @ param { string ? } [ relType ]
* @ returns { Promise < Ty . Pagination < Ty . Event . Outer < any >>> }
* /
2023-10-14 10:34:02 +00:00
function getRelations ( roomID , eventID , pagination , relType ) {
2023-09-25 04:36:27 +00:00
let path = ` /client/v1/rooms/ ${ roomID } /relations/ ${ eventID } `
if ( relType ) path += ` / ${ relType } `
2023-10-14 10:34:02 +00:00
if ( ! pagination . from ) delete pagination . from
if ( ! pagination . limit ) pagination . limit = 50 // get a little more consistency between homeservers
path += ` ? ${ new URLSearchParams ( pagination ) } `
2023-09-25 04:36:27 +00:00
return mreq . mreq ( "GET" , path )
}
2023-05-08 11:37:51 +00:00
/ * *
* @ param { string } roomID
* @ param { string } type
* @ param { string } stateKey
2023-05-08 20:03:57 +00:00
* @ param { string } [ mxid ]
2023-05-08 12:58:46 +00:00
* @ returns { Promise < string > } event ID
2023-05-08 11:37:51 +00:00
* /
2023-05-08 12:58:46 +00:00
async function sendState ( roomID , type , stateKey , content , mxid ) {
2023-08-21 09:04:41 +00:00
console . log ( ` [api] state: ${ roomID } : ${ type } / ${ stateKey } ` )
assert . ok ( type )
assert . ok ( typeof stateKey === "string" )
/** @type {Ty.R.EventSent} */
2023-09-06 23:53:10 +00:00
// encodeURIComponent is necessary because state key can contain some special characters like / but you must encode them so they fit in a single component of the URI
const root = await mreq . mreq ( "PUT" , path ( ` /client/v3/rooms/ ${ roomID } /state/ ${ type } / ${ encodeURIComponent ( stateKey ) } ` , mxid ) , content )
2023-08-21 09:04:41 +00:00
return root . event _id
2023-05-08 12:58:46 +00:00
}
2023-07-28 05:05:13 +00:00
/ * *
* @ param { string } roomID
* @ param { string } type
* @ param { any } content
2023-08-17 04:41:28 +00:00
* @ param { string ? } [ mxid ]
2023-07-28 05:05:13 +00:00
* @ param { number } [ timestamp ] timestamp of the newly created event , in unix milliseconds
* /
async function sendEvent ( roomID , type , content , mxid , timestamp ) {
2023-09-23 06:12:51 +00:00
if ( ! [ "m.room.message" , "m.reaction" , "m.sticker" ] . includes ( type ) ) {
console . log ( ` [api] event ${ type } to ${ roomID } as ${ mxid || "default sim" } ` )
}
2023-08-21 09:04:41 +00:00
/** @type {Ty.R.EventSent} */
const root = await mreq . mreq ( "PUT" , path ( ` /client/v3/rooms/ ${ roomID } /send/ ${ type } / ${ makeTxnId . makeTxnId ( ) } ` , mxid , { ts : timestamp } ) , content )
return root . event _id
2023-05-08 11:37:51 +00:00
}
2023-08-17 04:41:28 +00:00
/ * *
2023-09-25 09:20:23 +00:00
* @ param { string } roomID
* @ param { string } eventID
* @ param { string ? } [ mxid ]
* @ returns { Promise < string > } event ID
2023-08-17 04:41:28 +00:00
* /
async function redactEvent ( roomID , eventID , mxid ) {
2023-08-21 09:04:41 +00:00
/** @type {Ty.R.EventRedacted} */
const root = await mreq . mreq ( "PUT" , path ( ` /client/v3/rooms/ ${ roomID } /redact/ ${ eventID } / ${ makeTxnId . makeTxnId ( ) } ` , mxid ) , { } )
return root . event _id
2023-08-17 04:41:28 +00:00
}
2023-09-17 11:07:33 +00:00
/ * *
* @ param { string } roomID
* @ param { boolean } isTyping
* @ param { string } mxid
* @ param { number } [ duration ] milliseconds
* /
async function sendTyping ( roomID , isTyping , mxid , duration ) {
await mreq . mreq ( "PUT" , path ( ` /client/v3/rooms/ ${ roomID } /typing/ ${ mxid } ` , mxid ) , {
typing : isTyping ,
2023-09-30 13:06:56 +00:00
timeout : duration
2023-09-17 11:07:33 +00:00
} )
}
2023-05-10 11:17:37 +00:00
async function profileSetDisplayname ( mxid , displayname ) {
2023-08-21 09:04:41 +00:00
await mreq . mreq ( "PUT" , path ( ` /client/v3/profile/ ${ mxid } /displayname ` , mxid ) , {
displayname
} )
2023-05-10 11:17:37 +00:00
}
async function profileSetAvatarUrl ( mxid , avatar _url ) {
2023-08-21 09:04:41 +00:00
await mreq . mreq ( "PUT" , path ( ` /client/v3/profile/ ${ mxid } /avatar_url ` , mxid ) , {
avatar _url
} )
}
/ * *
* Set a user ' s power level within a room .
* @ param { string } roomID
* @ param { string } mxid
* @ param { number } power
* /
async function setUserPower ( roomID , mxid , power ) {
2023-08-23 00:39:37 +00:00
assert ( roomID [ 0 ] === "!" )
assert ( mxid [ 0 ] === "@" )
2023-08-23 04:16:36 +00:00
// Yes there's no shortcut https://github.com/matrix-org/matrix-appservice-bridge/blob/2334b0bae28a285a767fe7244dad59f5a5963037/src/components/intent.ts#L352
2023-08-21 09:04:41 +00:00
const powerLevels = await getStateEvent ( roomID , "m.room.power_levels" , "" )
2023-09-02 22:47:01 +00:00
powerLevels . users = powerLevels . users || { }
2023-08-21 09:04:41 +00:00
if ( power != null ) {
2023-09-02 22:47:01 +00:00
powerLevels . users [ mxid ] = power
2023-08-21 09:04:41 +00:00
} else {
2023-09-02 22:47:01 +00:00
delete powerLevels . users [ mxid ]
2023-08-21 09:04:41 +00:00
}
await sendState ( roomID , "m.room.power_levels" , "" , powerLevels )
return powerLevels
2023-05-10 11:17:37 +00:00
}
2023-05-08 12:58:46 +00:00
module . exports . path = path
2023-05-08 11:37:51 +00:00
module . exports . register = register
module . exports . createRoom = createRoom
2023-05-08 12:58:46 +00:00
module . exports . joinRoom = joinRoom
module . exports . inviteToRoom = inviteToRoom
2023-07-01 13:41:31 +00:00
module . exports . leaveRoom = leaveRoom
2023-07-11 04:51:30 +00:00
module . exports . getEvent = getEvent
2023-11-23 03:11:46 +00:00
module . exports . getEventForTimestamp = getEventForTimestamp
2023-05-08 11:37:51 +00:00
module . exports . getAllState = getAllState
2023-08-24 05:09:25 +00:00
module . exports . getStateEvent = getStateEvent
2023-07-11 04:51:30 +00:00
module . exports . getJoinedMembers = getJoinedMembers
2024-03-25 12:10:29 +00:00
module . exports . getHierarchy = getHierarchy
2023-09-25 04:36:27 +00:00
module . exports . getRelations = getRelations
2023-05-08 11:37:51 +00:00
module . exports . sendState = sendState
2023-05-08 12:58:46 +00:00
module . exports . sendEvent = sendEvent
2023-08-17 04:41:28 +00:00
module . exports . redactEvent = redactEvent
2023-09-17 11:07:33 +00:00
module . exports . sendTyping = sendTyping
2023-05-10 11:17:37 +00:00
module . exports . profileSetDisplayname = profileSetDisplayname
module . exports . profileSetAvatarUrl = profileSetAvatarUrl
2023-08-21 09:04:41 +00:00
module . exports . setUserPower = setUserPower