KI-AGENT: Anhänge im Chat über Matrix unterstützen
This commit is contained in:
@@ -20,6 +20,11 @@ type MatrixRoomEvent = {
|
||||
content?: {
|
||||
body?: string
|
||||
msgtype?: string
|
||||
url?: string
|
||||
info?: {
|
||||
mimetype?: string
|
||||
size?: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +64,13 @@ type MatrixTenantRoomOptions = {
|
||||
inviteUserIds?: string[]
|
||||
}
|
||||
|
||||
type MatrixAttachmentInput = {
|
||||
buffer: Buffer
|
||||
filename: string
|
||||
mimeType: string
|
||||
size: number
|
||||
}
|
||||
|
||||
type MatrixCachedValue<T = any> = {
|
||||
exists: true
|
||||
cachedUntil: number
|
||||
@@ -404,6 +416,41 @@ export function matrixService(server: FastifyInstance) {
|
||||
})
|
||||
}
|
||||
|
||||
const mxcToMediaPath = (mxcUri: string) => {
|
||||
const match = mxcUri.match(/^mxc:\/\/([^/]+)\/(.+)$/)
|
||||
if (!match) {
|
||||
throw Object.assign(
|
||||
new Error("Ungültige Matrix-Media-URI"),
|
||||
{ statusCode: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
return `/_matrix/media/v3/download/${encodeURIComponent(match[1])}/${encodeURIComponent(match[2])}`
|
||||
}
|
||||
|
||||
const matrixMediaUrl = (mxcUri: string) => `${homeserverUrl()}${mxcToMediaPath(mxcUri)}`
|
||||
|
||||
const attachmentFromEvent = (event: MatrixRoomEvent) => {
|
||||
const msgtype = event.content?.msgtype || "m.text"
|
||||
|
||||
if (!["m.file", "m.image"].includes(msgtype) || !event.content?.url) {
|
||||
return null
|
||||
}
|
||||
|
||||
const mimeType = event.content.info?.mimetype || "application/octet-stream"
|
||||
|
||||
return {
|
||||
type: msgtype === "m.image" ? "image" : "file",
|
||||
url: event.content.url,
|
||||
fileName: event.content.body || "Anhang",
|
||||
mimeType,
|
||||
size: event.content.info?.size || 0,
|
||||
previewUrl: msgtype === "m.image" ? matrixMediaUrl(event.content.url) : null,
|
||||
downloadUrl: matrixMediaUrl(event.content.url),
|
||||
isImage: msgtype === "m.image" || mimeType.startsWith("image/"),
|
||||
}
|
||||
}
|
||||
|
||||
const createAccessTokenForUser = async (userId: string, tenantId: number | null) => {
|
||||
const matrixUserId = await matrixUserIdForUser(userId, tenantId)
|
||||
const cacheKey = `${tenantId || "global"}:${userId}`
|
||||
@@ -1118,12 +1165,16 @@ export function matrixService(server: FastifyInstance) {
|
||||
name: room.name,
|
||||
matrixUserId: session.matrixUserId,
|
||||
messages: response.chunk
|
||||
.filter((event) => event.type === "m.room.message" && event.content?.msgtype === "m.text")
|
||||
.filter((event) =>
|
||||
event.type === "m.room.message" &&
|
||||
["m.text", "m.file", "m.image"].includes(event.content?.msgtype || "")
|
||||
)
|
||||
.map((event) => ({
|
||||
id: event.event_id,
|
||||
sender: event.sender,
|
||||
senderDisplayName: members.joined[event.sender]?.display_name || event.sender,
|
||||
body: event.content?.body || "",
|
||||
attachment: attachmentFromEvent(event),
|
||||
timestamp: event.origin_server_ts,
|
||||
own: event.sender === session.matrixUserId,
|
||||
}))
|
||||
@@ -1210,6 +1261,103 @@ export function matrixService(server: FastifyInstance) {
|
||||
}
|
||||
}
|
||||
|
||||
const sendTenantRoomAttachment = async (
|
||||
userId: string,
|
||||
tenantId: number | null,
|
||||
options: MatrixTenantRoomOptions = {},
|
||||
attachment: MatrixAttachmentInput
|
||||
) => {
|
||||
if (!attachment.buffer?.length) {
|
||||
throw Object.assign(
|
||||
new Error("Attachment file is required"),
|
||||
{ statusCode: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const room = await provisionTenantRoom(userId, tenantId, options)
|
||||
const session = await ensureCurrentUserJoinedRoom(userId, tenantId, {
|
||||
roomId: room.roomId,
|
||||
alias: room.alias,
|
||||
})
|
||||
const upload = await requestMatrixJson<{ content_uri: string }>(
|
||||
`/_matrix/media/v3/upload?filename=${encodeURIComponent(attachment.filename)}`,
|
||||
session.accessToken,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": attachment.mimeType || "application/octet-stream" },
|
||||
body: attachment.buffer as any,
|
||||
}
|
||||
)
|
||||
const txnId = `${Date.now()}-${randomBytes(8).toString("hex")}`
|
||||
const isImage = (attachment.mimeType || "").startsWith("image/")
|
||||
const messageContent = {
|
||||
msgtype: isImage ? "m.image" : "m.file",
|
||||
body: attachment.filename,
|
||||
url: upload.content_uri,
|
||||
info: {
|
||||
mimetype: attachment.mimeType || "application/octet-stream",
|
||||
size: attachment.size,
|
||||
},
|
||||
}
|
||||
const response = await requestMatrixJson<{ event_id: string }>(
|
||||
`/_matrix/client/v3/rooms/${encodeURIComponent(room.roomId)}/send/m.room.message/${encodeURIComponent(txnId)}`,
|
||||
session.accessToken,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(messageContent),
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
id: response.event_id,
|
||||
sender: session.matrixUserId,
|
||||
senderDisplayName: await getCurrentUserDisplayName(userId, tenantId),
|
||||
body: attachment.filename,
|
||||
attachment: {
|
||||
type: isImage ? "image" : "file",
|
||||
url: upload.content_uri,
|
||||
fileName: attachment.filename,
|
||||
mimeType: attachment.mimeType || "application/octet-stream",
|
||||
size: attachment.size,
|
||||
previewUrl: isImage ? matrixMediaUrl(upload.content_uri) : null,
|
||||
downloadUrl: matrixMediaUrl(upload.content_uri),
|
||||
isImage,
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
own: true,
|
||||
roomId: room.roomId,
|
||||
alias: room.alias,
|
||||
key: room.key,
|
||||
}
|
||||
}
|
||||
|
||||
const getMediaContent = async (
|
||||
userId: string,
|
||||
tenantId: number | null,
|
||||
mxcUri: string
|
||||
) => {
|
||||
const session = await createAccessTokenForUser(userId, tenantId)
|
||||
const response = await fetch(matrixMediaUrl(mxcUri), {
|
||||
headers: {
|
||||
Authorization: `Bearer ${session.accessToken}`,
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw Object.assign(
|
||||
new Error(`Matrix media request failed with ${response.status}`),
|
||||
{ statusCode: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
buffer: Buffer.from(await response.arrayBuffer()),
|
||||
contentType: response.headers.get("content-type") || "application/octet-stream",
|
||||
contentLength: response.headers.get("content-length"),
|
||||
}
|
||||
}
|
||||
|
||||
const createElementRoomSession = async (
|
||||
userId: string,
|
||||
tenantId: number | null,
|
||||
@@ -1442,6 +1590,17 @@ export function matrixService(server: FastifyInstance) {
|
||||
text
|
||||
)
|
||||
|
||||
const sendGeneralRoomAttachment = (userId: string, tenantId: number | null, attachment: MatrixAttachmentInput) =>
|
||||
sendTenantRoomAttachment(
|
||||
userId,
|
||||
tenantId,
|
||||
{
|
||||
key: "allgemein",
|
||||
name: "Allgemeiner Chat",
|
||||
},
|
||||
attachment
|
||||
)
|
||||
|
||||
return {
|
||||
getStatus,
|
||||
matrixUserIdForUser,
|
||||
@@ -1456,11 +1615,14 @@ export function matrixService(server: FastifyInstance) {
|
||||
getTenantRoomMessages,
|
||||
getTenantRoomMembers,
|
||||
sendTenantRoomMessage,
|
||||
sendTenantRoomAttachment,
|
||||
getMediaContent,
|
||||
createElementRoomSession,
|
||||
createLiveKitRoomSession,
|
||||
syncTenantRoomMembers,
|
||||
getGeneralRoomMessages,
|
||||
getGeneralRoomMembers,
|
||||
sendGeneralRoomMessage,
|
||||
sendGeneralRoomAttachment,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user