Skip to content

Commit

Permalink
feat(matrix): add some api (#138)
Browse files Browse the repository at this point in the history
  • Loading branch information
Anillc authored Aug 5, 2023
1 parent 953f1c6 commit 7a03fce
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 23 deletions.
44 changes: 38 additions & 6 deletions adapters/matrix/src/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,19 @@ export class MatrixBot extends Bot<MatrixBot.Config> {
const sync = await this.syncRooms()
const joined = sync?.rooms?.join
if (!joined) return []
const result: string[] = []
for (const [roomId, room] of Object.entries(joined)) {
const create = room.state?.events?.find(event => event.type === 'm.room.create')
const space = (create?.content as Matrix.M_ROOM_CREATE)?.type === 'm.space'
if (space) result.push(roomId)
const result: Universal.Guild[] = []
for (const roomId of Object.keys(joined)) {
const state = await this.internal.getState(roomId)
const create = state.find(state => state.type === 'm.room.create')
const name = state.find(state => state.type === 'm.room.name')?.content as Matrix.M_ROOM_NAME
if (!create) continue
if (create.content['type'] !== 'm.space') continue
result.push({
guildId: roomId,
guildName: name?.name,
})
}
return await Promise.all(result.map(this.getGuild.bind(this)))
return result
}

async getChannelList(guildId: string): Promise<Universal.Channel[]> {
Expand All @@ -120,6 +126,32 @@ export class MatrixBot extends Bot<MatrixBot.Config> {
return await Promise.all(children.map(this.getChannel.bind(this)))
}

async getGuildMemberList(guildId: string): Promise<Universal.GuildMember[]> {
const state = await this.internal.getState(guildId)
const levels = state.find(event => event.type === 'm.room.power_levels').content as Matrix.M_ROOM_POWER_LEVELS
return state
.filter(event => event.type === 'm.room.member')
.map(event => {
const content = event.content as Matrix.M_ROOM_MEMBER
return {
userId: event.state_key,
username: event.state_key,
nickname: content.displayname,
avatar: this.internal.getAssetUrl(content.avatar_url),
isBot: !!this.ctx.bots.find(bot => bot.userId === event.state_key),
roles: [levels.users[event.state_key].toString()],
}
})
}

async getGuildMember(guildId: string, userId: string): Promise<Universal.GuildMember> {
return (await this.getGuildMemberList(guildId)).find(user => user.userId === userId)
}

async createReaction(channelId: string, messageId: string, emoji: string): Promise<void> {
await this.internal.sendReaction(channelId, messageId, emoji)
}

async handleFriendRequest(): Promise<void> { }

// as utils.ts commented, messageId is roomId
Expand Down
4 changes: 2 additions & 2 deletions adapters/matrix/src/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class MatrixMessageEncoder extends MessageEncoder<MatrixBot> {
const session = this.bot.session(this.session)
const { data, filename, mime } = await this.bot.ctx.http.file(url)
const id = await this.bot.internal.sendMediaMessage(
this.channelId, this.bot.userId, type, Buffer.from(data), this.reply?.messageId, mime, filename,
this.channelId, type, Buffer.from(data), this.reply?.messageId, mime, filename,
)
session.messageId = id
this.results.push(session)
Expand All @@ -28,7 +28,7 @@ export class MatrixMessageEncoder extends MessageEncoder<MatrixBot> {
this.buffer = `> <${this.reply.userId}> ${this.reply.content}\n\n` + this.buffer
}
const id = await this.bot.internal.sendTextMessage(
this.channelId, this.bot.userId, this.buffer, this.reply?.messageId,
this.channelId, this.buffer, this.reply?.messageId,
)
session.messageId = id
this.results.push(session)
Expand Down
89 changes: 76 additions & 13 deletions adapters/matrix/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,6 @@ export interface User {
user_id?: string
}

export interface RoomId {
room_id: string
}

export interface Sync {
account_data?: AccountData
// device_lists? // end to end
Expand Down Expand Up @@ -211,6 +207,34 @@ export interface LeftRoom {
timeline?: Timeline
}

export interface Invite3pid {
address: string
id_access_token: string
id_server: string
medium: string
}

export interface StateEvent {
content: EventContent
state_key?: string
type: string
}

export interface RoomCreation {
creation_content?: Partial<M_ROOM_CREATE>
initial_state?: StateEvent[]
invite?: string[]
invite_3pid?: Invite3pid
is_direct?: boolean
name: string
power_level_content_override?: M_ROOM_POWER_LEVELS
preset?: 'private_chat' | 'public_chat' | 'trusted_private_chat'
room_alias_name?: string
room_version?: string
topic?: string
visibility?: 'public' | 'private'
}

export interface EventContent {}

export interface Relation {
Expand Down Expand Up @@ -241,7 +265,7 @@ export interface M_ROOM_JOIN_RULES extends EventContent {

export interface M_ROOM_MEMBER extends EventContent {
avatar_url?: string
displayname?: string[]
displayname?: string
is_direct?: boolean
join_authorised_via_users_server?: string
membership?: 'invite' | 'join' | 'knock' | 'leave' | 'ban'
Expand Down Expand Up @@ -445,6 +469,15 @@ export interface M_SPACE_PARENT extends EventContent {
via?: string[]
}

export interface M_ANNOTATION extends Relation {
rel_type: 'm.annotation'
key: string
}

export interface M_REACTION extends EventContent {
'm.relates_to'?: M_ANNOTATION
}

export class Internal {
private txnId = Math.round(Math.random() * 1000)

Expand All @@ -456,19 +489,19 @@ export class Internal {
return (await this.bot.http.post(`/media/v3/upload?filename=${filename}`, buffer, { headers })).content_uri
}

async sendTextMessage(roomId: string, userId: string, content: string, reply?: string): Promise<string> {
async sendTextMessage(roomId: string, content: string, reply?: string): Promise<string> {
const eventContent: M_TEXT = {
msgtype: 'm.text',
body: content,
}
if (reply) eventContent['m.relates_to'] = { 'm.in_reply_to': { 'event_id': reply } }
const response = await this.bot.http.put(
`/client/v3/rooms/${roomId}/send/m.room.message/${this.txnId++}?user_id=${userId}`, eventContent)
`/client/v3/rooms/${roomId}/send/m.room.message/${this.txnId++}`, eventContent)
return response.event_id
}

async sendMediaMessage(
roomId: string, userId: string, type: 'file' | 'image' | 'video' | 'audio',
roomId: string, type: 'file' | 'image' | 'video' | 'audio',
buffer: Buffer, reply?: string, mimetype?: string, filename: string = 'file',
): Promise<string> {
const uri = await this.uploadFile(filename, buffer, mimetype)
Expand All @@ -490,7 +523,20 @@ export class Internal {
}
if (reply) eventContent['m.relates_to'] = { 'm.in_reply_to': { 'event_id': reply } }
const response = await this.bot.http.put(
`/client/v3/rooms/${roomId}/send/m.room.message/${this.txnId++}?user_id=${userId}`, eventContent)
`/client/v3/rooms/${roomId}/send/m.room.message/${this.txnId++}`, eventContent)
return response.event_id
}

async sendReaction(roomId: string, messageId: string, key: string): Promise<string> {
const eventContent: M_REACTION = {
'm.relates_to': {
rel_type: 'm.annotation',
event_id: messageId,
key,
},
}
const response = await this.bot.http.put(
`/client/v3/rooms/${roomId}/send/m.reaction/${this.txnId++}`, eventContent)
return response.event_id
}

Expand All @@ -516,12 +562,23 @@ export class Internal {
await this.bot.http.put(`/client/v3/profile/${userId}/avatar_url`, { avatar_url: uri })
}

async joinRoom(roomId: string, reason?: string): Promise<RoomId> {
return await this.bot.http.post(`/client/v3/join/${roomId}`, { reason })
async createRoom(creation: RoomCreation): Promise<string> {
const response = await this.bot.http.post('/client/v3/createRoom', creation)
return response.room_id
}

async joinRoom(roomId: string, reason?: string): Promise<string> {
const response = await this.bot.http.post(`/client/v3/join/${roomId}`, { reason })
return response.room_id
}

async leaveRoom(roomId: string, reason?: string): Promise<string> {
const response = await this.bot.http.post(`/client/v3/rooms/${roomId}/leave`, { reason })
return response.room_id
}

async leaveRoom(roomId: string, reason?: string): Promise<RoomId> {
return await this.bot.http.post(`/client/v3/rooms/${roomId}/leave`, { reason })
async invite(roomId: string, userId: string, reason?: string): Promise<void> {
await this.bot.http.post(`/client/v3/rooms/${roomId}/invite`, { user_id: userId, reason })
}

async sync(fullSstate: boolean = false): Promise<Sync> {
Expand All @@ -534,6 +591,12 @@ export class Internal {
return await this.bot.http.get(`/client/v3/rooms/${roomId}/state`)
}

async setState(roomId: string, eventType: string, event: EventContent, state?: string): Promise<string> {
const statePath = state ? `/${state}` : ''
const response = await this.bot.http.put(`/client/v3/rooms/${roomId}/state/${eventType}${statePath}`, event)
return response.event_id
}

async getJoinedRooms(): Promise<string[]> {
return await this.bot.http.get('/client/v3/joined_rooms')
}
Expand Down
14 changes: 12 additions & 2 deletions adapters/matrix/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,20 @@ export async function adaptSession(bot: MatrixBot, event: Matrix.ClientEvent): P
session.timestamp = event.origin_server_ts
session.author = adaptAuthor(bot, event)
switch (event.type) {
case 'm.room.redaction':
session.type = 'message-delete'
case 'm.room.redaction': {
session.type = 'message-deleted'
session.subtype = 'group'
session.messageId = event.redacts
break
}
case 'm.reaction': {
const content = event.content as Matrix.M_REACTION
session.type = 'reaction-added'
session.subtype = 'group'
session.content = content['m.relates_to'].key
session.messageId = content['m.relates_to'].event_id
break
}
case 'm.room.member': {
bot.syncRooms()
const memberEvent = event.content as Matrix.M_ROOM_MEMBER
Expand Down

0 comments on commit 7a03fce

Please sign in to comment.