import cuid from 'cuid' ; import type { ReadStream } from 'fs' ; import { Bridge , MatrixUser , RoomBridgeStore , UserBridgeStore , MatrixRoom , AppServiceRegistration , FileUploadOpts , } from 'matrix-appservice-bridge' ; import { Message } from 'wechaty' ; import { log , } from './config.js' ; import { Manager , Managers , } from './manager.js' ; import type { Registration } from './registration.js' ; export class AppserviceManager extends Manager { public bridge ! : Bridge ; public roomStore ! : RoomBridgeStore ; public userStore ! : UserBridgeStore ; public domain ! : string ; public localpart ! : string ; constructor ( ) { super ( ) ; log . verbose ( 'Appservicemanager' , 'constructor()' ) ; } teamManager ( managers ) { log . verbose ( 'Appservicemanager' , 'setManager(%s)' , managers ) ; } public setBridge ( matrixBridge ) { log . verbose ( 'Appservicemanager' , 'setBridge(bridge)' ) ; this . bridge = matrixBridge ; this . domain = matrixBridge . opts . domain ; const registration = matrixBridge . opts . registration ; if ( registration instanceof AppServiceRegistration ) { this . localpart = ( registration as AppServiceRegistration ) . getSenderLocalpart ( ) ! ; } else if ( typeof registration === 'string' ) { this . localpart = matrixBridge . getBot ( ) . getUserId ( ) . split ( ':' ) [ 0 ] ! . replace ( '@' , '' ) ; } else { this . localpart = ( registration as unknown as Registration ) . senderLocalpart ! ; } const userBridgeStore = matrixBridge . getUserStore ( ) ; const roomBridgeStore = matrixBridge . getRoomStore ( ) ; if ( ! userBridgeStore ) { throw new Error ( 'can not get UserBridgeStore' ) ; } if ( ! roomBridgeStore ) { throw new Error ( 'can not get RoomBridgeStore' ) ; } this . roomStore = roomBridgeStore ; this . userStore = userBridgeStore ; } public appserviceUserId ( ) { return [ '@' , this . localpart , ':' , this . domain , ] . join ( '' ) ; } public async appserviceUser ( ) { const matrixUserId = this . appserviceUserId ( ) ; const matrixUser = await this . userStore . getMatrixUser ( matrixUserId ) ; if ( ! matrixUser ) { throw new Error ( 'no matrix user from store for id ' + matrixUserId ) ; } return matrixUser ; } /**
     * Huan(202002) - To be confirmed: isVirtual is not include isBot
     */ public isVirtual ( matrixUserId ) { return this . bridge . getBot ( ) . isRemoteUser ( matrixUserId ) ; } public isBot ( matrixUserId ) { const appserviceUserId = this . appserviceUserId ( ) ; return appserviceUserId === matrixUserId ; } public isUser ( matrixUserId ) { return ! ( this . isBot ( matrixUserId ) || this . isVirtual ( matrixUserId ) ) ; } public async sendMessage ( message , inRoom , fromUser ? ) { const text = typeof ( message ) === 'string' ? message : message . text ( ) ; log . verbose ( 'Appservicemanager' , 'sendMessage(%s%s%s)' , text . substr ( 0 , 100 ) , inRoom ? ', ' + inRoom . getId ( ) : '' , fromUser ? ', ' + fromUser . getId ( ) : '' ) ; const intent = this . bridge . getIntent ( fromUser && fromUser . getId ( ) ) ; if ( typeof ( message ) !== 'string' ) { switch ( message . type ( ) ) { case Message . Type . Unknown : break ; case Message . Type . Audio : break ; case Message . Type . Contact : break ; case Message . Type . Emoticon : case Message . Type . Image : case Message . Type . Attachment : try { const file = await message . toFileBox ( ) ; const buffer = await file . toBuffer ( ) ; const url = await intent . uploadContent ( buffer , { name : file . name , type : file . mimeType === 'emoticon' ? 'image/gif' : file . mimeType , } ) ; await intent . sendMessage ( inRoom . getId ( ) , { body : file . name , info : { } , msgtype : message . type ( ) === Message . Type . Attachment ? 'm.file' : 'm.image' , url : url , } ) ; } catch ( e ) { log . error ( `Appservicemanager', 'sendMessage() rejection from ${ fromUser ? fromUser . getId ( ) : 'BOT' } to room ${ inRoom . getId ( ) }` ) ; throw e ; } return ; } } try { await intent . sendText ( inRoom . getId ( ) , text ) ; } catch ( e ) { log . error ( `Appservicemanager', 'sendMessage() rejection from ${ fromUser ? fromUser . getId ( ) : 'BOT' } to room ${ inRoom . getId ( ) }` ) ; throw e ; } } public generateVirtualUserId ( ) { return [ '@' , this . localpart , '_' , cuid ( ) , ':' , this . domain , ] . join ( '' ) ; } public storeQuery ( dataKey , filterData ) { log . verbose ( 'Appservicemanager' , 'storeQuery(%s, "%s")' , dataKey , JSON . stringify ( filterData ) ) ; const query = { } as { [ key ] : string ; } ; for ( const [ key , value ] of Object . entries ( filterData ) ) { query [ `${ dataKey }.${ key }` ] = value ; } return query ; } /**
     * The matrix room will be created by the specified creater.
     */ public async createRoom ( userIdList , args = { } ) { log . verbose ( 'Appservicemanager' , 'createRoom(["%s"], "%s")' , userIdList . join ( '","' ) , JSON . stringify ( args ) ) ; const intent = this . bridge . getIntent ( args . creatorId ) ; /**
         * See:
         *  Issue #4 - https://github.com/wechaty/matrix-appservice-wechaty/issues/4
         *  Client Server API Spec - https://matrix.org/docs/spec/client_server/r0.6.0#id140
         *  https://github.com/matrix-org/matrix-js-sdk/issues/653#issuecomment-393371939
         */ const roomInfo = await intent . createRoom ( { createAsClient : true , options : { invite : userIdList , is_direct : userIdList . length <= 2 , name : args . name , preset : 'trusted_private_chat' , topic : args . topic , visibility : 'private' , } , } ) ; const matrixRoom = new MatrixRoom ( roomInfo . room_id ) ; for await ( const userId of userIdList . slice ( 1 ) ) { await this . bridge . getIntent ( userId ) . join ( matrixRoom . getId ( ) ) ; } return matrixRoom ; } public async roomMembers ( roomId ) { const client = this . bridge . getClientFactory ( ) . getClientAs ( ) ; const result = await client . getJoinedRoomMembers ( roomId ) as { joined : { [ id ] : { avatar_url : null | string ; display_name : string ; } ; } ; } ; return Object . keys ( result . joined ) ; } public async setProfile ( userId , avataUrl , displayName ) { const intent = this . bridge . getIntent ( userId ) ; await intent . setAvatarUrl ( avataUrl ) ; await intent . setDisplayName ( displayName ) ; } public async uploadContent ( content , userId ? , opts ? ) { return this . bridge . getIntent ( userId ) . uploadContent ( content , opts ) ; } public async mxcUrlToHttp ( mxcUrl ) { return this . bridge . getIntent ( ) . client . mxcUrlToHttp ( mxcUrl ) ; } }