KI-AGENT: Chat Anhänge und Nachrichteninteraktionen abrunden
This commit is contained in:
@@ -25,6 +25,18 @@ type MatrixRoomEvent = {
|
||||
mimetype?: string
|
||||
size?: number
|
||||
}
|
||||
"m.new_content"?: {
|
||||
body?: string
|
||||
msgtype?: string
|
||||
}
|
||||
"m.relates_to"?: {
|
||||
event_id?: string
|
||||
key?: string
|
||||
rel_type?: string
|
||||
"m.in_reply_to"?: {
|
||||
event_id?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +83,10 @@ type MatrixAttachmentInput = {
|
||||
size: number
|
||||
}
|
||||
|
||||
type MatrixMessageOptions = {
|
||||
replyToEventId?: string
|
||||
}
|
||||
|
||||
type MatrixCachedValue<T = any> = {
|
||||
exists: true
|
||||
cachedUntil: number
|
||||
@@ -1158,27 +1174,72 @@ export function matrixService(server: FastifyInstance) {
|
||||
session.accessToken
|
||||
)
|
||||
|
||||
const replacementByEventId = new Map<string, MatrixRoomEvent>()
|
||||
const reactionsByEventId = new Map<string, Map<string, { key: string; count: number; own: boolean }>>()
|
||||
|
||||
for (const event of response.chunk) {
|
||||
const relation = event.content?.["m.relates_to"]
|
||||
|
||||
if (
|
||||
event.type === "m.room.message" &&
|
||||
relation?.rel_type === "m.replace" &&
|
||||
relation.event_id
|
||||
) {
|
||||
replacementByEventId.set(relation.event_id, event)
|
||||
}
|
||||
|
||||
if (
|
||||
event.type === "m.reaction" &&
|
||||
relation?.rel_type === "m.annotation" &&
|
||||
relation.event_id &&
|
||||
relation.key
|
||||
) {
|
||||
const eventReactions = reactionsByEventId.get(relation.event_id) || new Map()
|
||||
const reaction = eventReactions.get(relation.key) || {
|
||||
key: relation.key,
|
||||
count: 0,
|
||||
own: false,
|
||||
}
|
||||
|
||||
reaction.count += 1
|
||||
reaction.own = reaction.own || event.sender === session.matrixUserId
|
||||
eventReactions.set(relation.key, reaction)
|
||||
reactionsByEventId.set(relation.event_id, eventReactions)
|
||||
}
|
||||
}
|
||||
|
||||
const messages = response.chunk
|
||||
.filter((event) =>
|
||||
event.type === "m.room.message" &&
|
||||
["m.text", "m.file", "m.image"].includes(event.content?.msgtype || "") &&
|
||||
event.content?.["m.relates_to"]?.rel_type !== "m.replace"
|
||||
)
|
||||
.map((event) => {
|
||||
const replacement = replacementByEventId.get(event.event_id)
|
||||
const content = replacement?.content?.["m.new_content"] || replacement?.content || event.content
|
||||
|
||||
return {
|
||||
id: event.event_id,
|
||||
sender: event.sender,
|
||||
senderDisplayName: members.joined[event.sender]?.display_name || event.sender,
|
||||
body: content?.body || "",
|
||||
attachment: attachmentFromEvent({ ...event, content }),
|
||||
timestamp: replacement?.origin_server_ts || event.origin_server_ts,
|
||||
own: event.sender === session.matrixUserId,
|
||||
edited: Boolean(replacement),
|
||||
replyToEventId: event.content?.["m.relates_to"]?.["m.in_reply_to"]?.event_id || null,
|
||||
reactions: Array.from(reactionsByEventId.get(event.event_id)?.values() || []),
|
||||
}
|
||||
})
|
||||
.reverse()
|
||||
|
||||
return {
|
||||
roomId: room.roomId,
|
||||
alias: room.alias,
|
||||
key: room.key,
|
||||
name: room.name,
|
||||
matrixUserId: session.matrixUserId,
|
||||
messages: response.chunk
|
||||
.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,
|
||||
}))
|
||||
.reverse(),
|
||||
messages,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1216,7 +1277,8 @@ export function matrixService(server: FastifyInstance) {
|
||||
userId: string,
|
||||
tenantId: number | null,
|
||||
options: MatrixTenantRoomOptions = {},
|
||||
text: string
|
||||
text: string,
|
||||
messageOptions: MatrixMessageOptions = {}
|
||||
) => {
|
||||
const message = text.trim()
|
||||
|
||||
@@ -1235,16 +1297,26 @@ export function matrixService(server: FastifyInstance) {
|
||||
})
|
||||
const txnId = `${Date.now()}-${randomBytes(8).toString("hex")}`
|
||||
|
||||
const content: Record<string, any> = {
|
||||
msgtype: "m.text",
|
||||
body: message,
|
||||
}
|
||||
|
||||
if (messageOptions.replyToEventId) {
|
||||
content["m.relates_to"] = {
|
||||
"m.in_reply_to": {
|
||||
event_id: messageOptions.replyToEventId,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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({
|
||||
msgtype: "m.text",
|
||||
body: message,
|
||||
}),
|
||||
body: JSON.stringify(content),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1258,9 +1330,78 @@ export function matrixService(server: FastifyInstance) {
|
||||
roomId: room.roomId,
|
||||
alias: room.alias,
|
||||
key: room.key,
|
||||
replyToEventId: messageOptions.replyToEventId || null,
|
||||
reactions: [],
|
||||
}
|
||||
}
|
||||
|
||||
const sendTenantRoomReaction = async (
|
||||
userId: string,
|
||||
tenantId: number | null,
|
||||
options: MatrixTenantRoomOptions = {},
|
||||
eventId: string,
|
||||
key: string
|
||||
) => {
|
||||
const reactionKey = key.trim()
|
||||
if (!eventId || !reactionKey) {
|
||||
throw Object.assign(
|
||||
new Error("Reaction target and key are required"),
|
||||
{ statusCode: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const room = await provisionTenantRoom(userId, tenantId, options)
|
||||
const session = await ensureCurrentUserJoinedRoom(userId, tenantId, {
|
||||
roomId: room.roomId,
|
||||
alias: room.alias,
|
||||
})
|
||||
const txnId = `${Date.now()}-${randomBytes(8).toString("hex")}`
|
||||
await requestMatrixJson<{ event_id: string }>(
|
||||
`/_matrix/client/v3/rooms/${encodeURIComponent(room.roomId)}/send/m.reaction/${encodeURIComponent(txnId)}`,
|
||||
session.accessToken,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
"m.relates_to": {
|
||||
rel_type: "m.annotation",
|
||||
event_id: eventId,
|
||||
key: reactionKey,
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
return { success: true, eventId, key: reactionKey }
|
||||
}
|
||||
|
||||
const markTenantRoomRead = async (
|
||||
userId: string,
|
||||
tenantId: number | null,
|
||||
options: MatrixTenantRoomOptions = {},
|
||||
eventId: string
|
||||
) => {
|
||||
if (!eventId) return { success: true, skipped: true }
|
||||
|
||||
const room = await provisionTenantRoom(userId, tenantId, options)
|
||||
const session = await ensureCurrentUserJoinedRoom(userId, tenantId, {
|
||||
roomId: room.roomId,
|
||||
alias: room.alias,
|
||||
})
|
||||
|
||||
await requestMatrixJson(
|
||||
`/_matrix/client/v3/rooms/${encodeURIComponent(room.roomId)}/receipt/m.read/${encodeURIComponent(eventId)}`,
|
||||
session.accessToken,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({}),
|
||||
}
|
||||
)
|
||||
|
||||
return { success: true, eventId }
|
||||
}
|
||||
|
||||
const sendTenantRoomAttachment = async (
|
||||
userId: string,
|
||||
tenantId: number | null,
|
||||
@@ -1579,7 +1720,12 @@ export function matrixService(server: FastifyInstance) {
|
||||
name: "Allgemeiner Chat",
|
||||
})
|
||||
|
||||
const sendGeneralRoomMessage = (userId: string, tenantId: number | null, text: string) =>
|
||||
const sendGeneralRoomMessage = (
|
||||
userId: string,
|
||||
tenantId: number | null,
|
||||
text: string,
|
||||
messageOptions: MatrixMessageOptions = {}
|
||||
) =>
|
||||
sendTenantRoomMessage(
|
||||
userId,
|
||||
tenantId,
|
||||
@@ -1587,7 +1733,8 @@ export function matrixService(server: FastifyInstance) {
|
||||
key: "allgemein",
|
||||
name: "Allgemeiner Chat",
|
||||
},
|
||||
text
|
||||
text,
|
||||
messageOptions
|
||||
)
|
||||
|
||||
const sendGeneralRoomAttachment = (userId: string, tenantId: number | null, attachment: MatrixAttachmentInput) =>
|
||||
@@ -1615,6 +1762,8 @@ export function matrixService(server: FastifyInstance) {
|
||||
getTenantRoomMessages,
|
||||
getTenantRoomMembers,
|
||||
sendTenantRoomMessage,
|
||||
sendTenantRoomReaction,
|
||||
markTenantRoomRead,
|
||||
sendTenantRoomAttachment,
|
||||
getMediaContent,
|
||||
createElementRoomSession,
|
||||
|
||||
@@ -641,8 +641,10 @@ export default async function communicationRoutes(server: FastifyInstance) {
|
||||
|
||||
server.post("/communication/matrix/rooms/general/messages", async (req, reply) => {
|
||||
try {
|
||||
const body = req.body as { text?: string }
|
||||
const message = await matrix.sendGeneralRoomMessage(req.user.user_id, req.user.tenant_id, body.text || "")
|
||||
const body = req.body as { text?: string; replyToEventId?: string }
|
||||
const message = await matrix.sendGeneralRoomMessage(req.user.user_id, req.user.tenant_id, body.text || "", {
|
||||
replyToEventId: body.replyToEventId,
|
||||
})
|
||||
const room = await matrix.getTenantRoomStatus(req.user.tenant_id, "allgemein", "Allgemeiner Chat")
|
||||
await notifyUsersAboutChatMessage(req, room, message, body.text || "")
|
||||
return message
|
||||
@@ -688,7 +690,12 @@ export default async function communicationRoutes(server: FastifyInstance) {
|
||||
try {
|
||||
if (!req.user.tenant_id) return reply.code(400).send({ error: "Kein aktiver Mandant" })
|
||||
const params = req.params as { roomKey: string }
|
||||
return await markRoomNotificationsRead(req.user.tenant_id, req.user.user_id, params.roomKey)
|
||||
const body = (req.body || {}) as { eventId?: string }
|
||||
const result = await markRoomNotificationsRead(req.user.tenant_id, req.user.user_id, params.roomKey)
|
||||
if (body.eventId) {
|
||||
await matrix.markTenantRoomRead(req.user.user_id, req.user.tenant_id, roomOptionsFromRequest(req), body.eventId)
|
||||
}
|
||||
return result
|
||||
} catch (err: any) {
|
||||
return handleMatrixError(req, reply, err, "Matrix room read state failed")
|
||||
}
|
||||
@@ -759,12 +766,15 @@ export default async function communicationRoutes(server: FastifyInstance) {
|
||||
|
||||
server.post("/communication/matrix/rooms/:roomKey/messages", async (req, reply) => {
|
||||
try {
|
||||
const body = req.body as { text?: string }
|
||||
const body = req.body as { text?: string; replyToEventId?: string }
|
||||
const message = await matrix.sendTenantRoomMessage(
|
||||
req.user.user_id,
|
||||
req.user.tenant_id,
|
||||
roomOptionsFromRequest(req),
|
||||
body.text || ""
|
||||
body.text || "",
|
||||
{
|
||||
replyToEventId: body.replyToEventId,
|
||||
}
|
||||
)
|
||||
const room = await matrix.getTenantRoomStatus(req.user.tenant_id, message.key)
|
||||
await notifyUsersAboutChatMessage(req, room, message, body.text || "")
|
||||
@@ -774,6 +784,22 @@ export default async function communicationRoutes(server: FastifyInstance) {
|
||||
}
|
||||
})
|
||||
|
||||
server.post("/communication/matrix/rooms/:roomKey/messages/:eventId/reactions", async (req, reply) => {
|
||||
try {
|
||||
const params = req.params as { eventId: string }
|
||||
const body = req.body as { key?: string }
|
||||
return await matrix.sendTenantRoomReaction(
|
||||
req.user.user_id,
|
||||
req.user.tenant_id,
|
||||
roomOptionsFromRequest(req),
|
||||
params.eventId,
|
||||
body.key || ""
|
||||
)
|
||||
} catch (err: any) {
|
||||
return handleMatrixError(req, reply, err, "Matrix reaction send failed")
|
||||
}
|
||||
})
|
||||
|
||||
server.post("/communication/matrix/rooms/:roomKey/attachments", async (req, reply) => {
|
||||
try {
|
||||
const attachment = await uploadedAttachmentFromRequest(req)
|
||||
|
||||
Reference in New Issue
Block a user