import * as matrix from "matrix-bot-sdk" ; import { LogService } from "matrix-bot-sdk" ; import { RegManager } from "./internal/RegManager" ; import { Config } from "../Config" ; import type { Main } from "../Main" ; import { Bridge } from "../bridging" ; import { CmdManager } from "./internal/CmdManager" ; import { MsgProcessor } from "./internal/MsgProcessor" ; import type { MxEvents , MCEvents } from "../minecraft" ; export type MxMessage = { sender : string ; room : string ; body ? : string ; event : MxEvents . Event ; } ; /**
 * The MatrixManager class has everything to do with Matrix's appservice
 * API and a couple utilities such as checking if a room exists. It's
 * responsible for keeping an appservice user in sync with their
 * corresponding Minecraft player by communicating with the PlayerManager.
 * It also stores new room messages in the newMxMessages for the Minecraft
 * server to retrieve periodically.
 */ export class MatrixInterface { public readonly appservice : matrix . Appservice ; private readonly main : Main ; private readonly newMxMessages : Map < string , MxEvents . Event [ ] > ; private readonly cmdManager : CmdManager ; private readonly msgProcessor : MsgProcessor ; constructor ( config , marco ) { let registration = RegManager . getRegistration ( config ) ; this . main = marco ; this . msgProcessor = new MsgProcessor ( this ) ; this . appservice = new matrix . Appservice ( { registration , bindAddress : config . appservice . bindAddress , homeserverName : config . appservice . homeserverName , homeserverUrl : config . appservice . homeserverURL , port : config . appservice . port } ) ; this . cmdManager = new CmdManager ( this . appservice , this . main , config ) ; this . newMxMessages = new Map ( ) ; } returns { Promise < void > } public async start ( ) { matrix . AutojoinRoomsMixin . setupOnAppservice ( this . appservice ) ; this . appservice . on ( 'room.message' , this . onMxMessage . bind ( this ) ) ; this . appservice . on ( 'room.leave' , this . onMxLeave . bind ( this ) ) ; this . appservice . on ( 'query.user' , this . onQueryUser . bind ( this ) ) ; await this . appservice . begin ( ) ; } param { Bridge } bridge param { MCEvents . Message } mcMessage returns { Promise < void > } public async sendMessage ( bridge , mcMessage ) { const uuid = await mcMessage . player . getUUID ( ) ; const intent = this . appservice . getIntentForSuffix ( uuid ) ; try { await intent . ensureRegistered ( ) ; await this . main . players . sync ( intent , mcMessage . player ) ; } catch ( err ) { let errMessage = 'An error occurred while sending a message to a bridged room.\n' ; if ( err instanceof Error ) { errMessage += ` - message: ${ err . message }\n` ; errMessage += ` - stack:\n${ err . stack }` ; } else { errMessage += ' - error: ' + err ; } LogService . error ( 'marco-matrix' , errMessage ) ; } finally { await intent . sendText ( bridge . room , mcMessage . message ) ; } } param { Bridge } bridge param { MCEvents . Join } mcJoin returns { Promise < void > } public async sendJoin ( bridge , mcJoin ) { const uuid = await mcJoin . player . getUUID ( ) ; const intent = this . appservice . getIntentForSuffix ( uuid ) ; try { await intent . ensureRegistered ( ) ; await this . main . players . sync ( intent , mcJoin . player ) ; } catch ( err ) { let errMessage = 'An error occurred while joining a bridged room.\n' ; if ( err instanceof Error ) { errMessage += ` - message: ${ err . message }\n` ; errMessage += ` - stack:\n${ err . stack }` ; } else { errMessage += ' - error: ' + err ; } LogService . error ( 'marco-matrix' , errMessage ) ; } finally { await intent . joinRoom ( bridge . room ) ; } } param { Bridge } bridge param { MCEvents . Quit } mcQuit returns { Promise < void > } public async sendQuit ( bridge , mcQuit ) { const uuid = await mcQuit . player . getUUID ( ) ; const intent = this . appservice . getIntentForSuffix ( uuid ) ; try { await intent . ensureRegistered ( ) ; } catch ( err ) { let errMessage = 'An error occurred while leaving a bridged room.\n' ; if ( err instanceof Error ) { errMessage += ` - message: ${ err . message }\n` ; errMessage += ` - stack:\n${ err . stack }` ; } else { errMessage += ' - error: ' + err ; } LogService . error ( 'marco-matrix' , errMessage ) ; } finally { await intent . leaveRoom ( bridge . room ) ; } } param { Bridge } bridge param { MCEvents . Kick } mcKick returns { Promise < void > } public async sendKick ( bridge , mcKick ) { const uuid = await mcKick . player . getUUID ( ) ; const client = this . appservice . botClient ; client . kickUser ( this . appservice . getUserIdForSuffix ( uuid ) , bridge . room , mcKick . reason ) ; } param { Bridge } bridge returns { MxEvents . Event [ ] } public getNewMxMessages ( bridge ) { let newMxMessages = this . newMxMessages . get ( bridge . room ) ; if ( ! newMxMessages ) { newMxMessages = [ ] ; this . newMxMessages . set ( bridge . room , newMxMessages ) ; } return newMxMessages . splice ( 0 , newMxMessages . length ) ; } param { MxMessage } message public addNewMxMessage ( message ) { const newMxMessages = this . newMxMessages . get ( message . room ) || [ ] ; if ( ! this . appservice . isNamespacedUser ( message . sender ) ) newMxMessages . push ( message . event ) ; this . newMxMessages . set ( message . room , newMxMessages ) ; } param { string } room param { string } user returns { Promise < any > } public async getRoomMember ( room , user ) { return this . appservice . botClient . getRoomStateEvent ( room , 'm.room.member' , user ) ; } param { string } user returns { string } public getPlayerUUID ( user ) { return this . appservice . getSuffixForUserId ( user ) ; } param { string } room param { any } event private async onMxMessage ( room , event ) { const content = event [ 'content' ] ; if ( ! content || ! content [ 'body' ] ) return ; const body = content [ 'body' ] ; const msgtype = content [ 'msgtype' ] ; const isBridged = this . main . bridges . isRoomBridged ( room ) ; if ( msgtype == 'm.text' && body . startsWith ( CmdManager . prefix ) ) { await this . cmdManager . onMxMessage ( room , event [ 'sender' ] , content [ 'body' ] ) ; return ; } if ( ! isBridged ) return ; if ( msgtype == 'm.text' ) { let message = await this . msgProcessor . buildTextMsg ( room , event ) ; this . main . sendToMinecraft ( message ) ; } else if ( msgtype == 'm.emote' ) { let message = await this . msgProcessor . buildEmoteMsg ( room , event ) ; this . main . sendToMinecraft ( message ) ; } } param { string } room param { any } event private async onMxLeave ( room , event ) { const isBridged = this . main . bridges . isRoomBridged ( room ) ; if ( ! isBridged ) return ; const stateKey = event [ 'state_key' ] ; if ( ! this . appservice . isNamespacedUser ( stateKey ) ) return ; const content = event [ 'content' ] ; const oldContent = event [ 'prev_content' ] || { } ; const membership = content [ 'membership' ] ; const oldMembership = oldContent [ 'membership' ] || "leave" ; const sender = event [ 'sender' ] ; if ( membership == 'ban' ) { let message = await this . msgProcessor . buildBanMsg ( room , event ) ; this . main . sendToMinecraft ( message ) ; } else if ( oldMembership == 'ban' ) { let message = await this . msgProcessor . buildUnbanMsg ( room , event ) ; this . main . sendToMinecraft ( message ) ; } else if ( membership == 'leave' && oldMembership == 'join' ) { let message = await this . msgProcessor . buildKickMsg ( room , event ) ; this . main . sendToMinecraft ( message ) ; } } param { string } user param { createUser } Function private async onQueryUser ( user , createUser ) { createUser ( false ) ; } }