KI-AGENT: Live-Sync und Nachrichtenaktionen im Chat ergänzen
This commit is contained in:
@@ -17,6 +17,7 @@ type MatrixRoomEvent = {
|
||||
sender: string
|
||||
origin_server_ts: number
|
||||
type: string
|
||||
redacts?: string
|
||||
content?: {
|
||||
body?: string
|
||||
msgtype?: string
|
||||
@@ -60,6 +61,20 @@ type MatrixRoomSearchResponse = {
|
||||
}
|
||||
}
|
||||
|
||||
type MatrixSyncResponse = {
|
||||
next_batch?: string
|
||||
rooms?: {
|
||||
join?: Record<string, {
|
||||
timeline?: {
|
||||
events?: MatrixRoomEvent[]
|
||||
}
|
||||
state?: {
|
||||
events?: MatrixRoomEvent[]
|
||||
}
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
type MatrixUserSession = {
|
||||
accessToken: string
|
||||
matrixUserId: string
|
||||
@@ -1370,6 +1385,133 @@ export function matrixService(server: FastifyInstance) {
|
||||
}
|
||||
}
|
||||
|
||||
const syncTenantRoomEvents = async (
|
||||
userId: string,
|
||||
tenantId: number | null,
|
||||
options: MatrixTenantRoomOptions = {},
|
||||
since?: string,
|
||||
initial = false
|
||||
) => {
|
||||
const room = await provisionTenantRoom(userId, tenantId, options)
|
||||
const session = await ensureCurrentUserJoinedRoom(userId, tenantId, {
|
||||
roomId: room.roomId,
|
||||
alias: room.alias,
|
||||
})
|
||||
const filter = {
|
||||
room: {
|
||||
rooms: [room.roomId],
|
||||
timeline: {
|
||||
limit: 30,
|
||||
},
|
||||
},
|
||||
presence: {
|
||||
types: [],
|
||||
},
|
||||
account_data: {
|
||||
types: [],
|
||||
},
|
||||
}
|
||||
const params = new URLSearchParams({
|
||||
timeout: since && !initial ? "25000" : "0",
|
||||
filter: JSON.stringify(filter),
|
||||
})
|
||||
|
||||
if (since) params.set("since", since)
|
||||
|
||||
const response = await requestMatrixJson<MatrixSyncResponse>(
|
||||
`/_matrix/client/v3/sync?${params.toString()}`,
|
||||
session.accessToken
|
||||
)
|
||||
const joinedRoom = response.rooms?.join?.[room.roomId]
|
||||
const timelineEvents = joinedRoom?.timeline?.events || []
|
||||
const stateEvents = joinedRoom?.state?.events || []
|
||||
|
||||
if (initial) {
|
||||
return {
|
||||
roomId: room.roomId,
|
||||
alias: room.alias,
|
||||
key: room.key,
|
||||
name: room.name,
|
||||
nextBatch: response.next_batch || since || "",
|
||||
messages: [],
|
||||
replacements: [],
|
||||
reactions: [],
|
||||
redactions: [],
|
||||
membersChanged: false,
|
||||
}
|
||||
}
|
||||
|
||||
const messages = timelineEvents
|
||||
.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) => ({
|
||||
id: event.event_id,
|
||||
sender: event.sender,
|
||||
senderDisplayName: event.sender,
|
||||
body: event.content?.body || "",
|
||||
attachment: attachmentFromEvent(event),
|
||||
timestamp: event.origin_server_ts,
|
||||
own: event.sender === session.matrixUserId,
|
||||
replyToEventId: event.content?.["m.relates_to"]?.["m.in_reply_to"]?.event_id || null,
|
||||
reactions: [],
|
||||
}))
|
||||
|
||||
const replacements = timelineEvents
|
||||
.filter((event) =>
|
||||
event.type === "m.room.message" &&
|
||||
event.content?.["m.relates_to"]?.rel_type === "m.replace" &&
|
||||
Boolean(event.content?.["m.relates_to"]?.event_id)
|
||||
)
|
||||
.map((event) => ({
|
||||
id: event.event_id,
|
||||
targetEventId: event.content?.["m.relates_to"]?.event_id,
|
||||
body: event.content?.["m.new_content"]?.body || event.content?.body || "",
|
||||
timestamp: event.origin_server_ts,
|
||||
sender: event.sender,
|
||||
own: event.sender === session.matrixUserId,
|
||||
}))
|
||||
|
||||
const reactions = timelineEvents
|
||||
.filter((event) =>
|
||||
event.type === "m.reaction" &&
|
||||
event.content?.["m.relates_to"]?.rel_type === "m.annotation" &&
|
||||
Boolean(event.content?.["m.relates_to"]?.event_id) &&
|
||||
Boolean(event.content?.["m.relates_to"]?.key)
|
||||
)
|
||||
.map((event) => ({
|
||||
id: event.event_id,
|
||||
targetEventId: event.content?.["m.relates_to"]?.event_id,
|
||||
key: event.content?.["m.relates_to"]?.key,
|
||||
sender: event.sender,
|
||||
own: event.sender === session.matrixUserId,
|
||||
}))
|
||||
|
||||
const redactions = timelineEvents
|
||||
.filter((event) => event.type === "m.room.redaction" && Boolean(event.redacts))
|
||||
.map((event) => ({
|
||||
id: event.event_id,
|
||||
targetEventId: event.redacts,
|
||||
sender: event.sender,
|
||||
timestamp: event.origin_server_ts,
|
||||
}))
|
||||
|
||||
return {
|
||||
roomId: room.roomId,
|
||||
alias: room.alias,
|
||||
key: room.key,
|
||||
name: room.name,
|
||||
nextBatch: response.next_batch || since || "",
|
||||
messages,
|
||||
replacements,
|
||||
reactions,
|
||||
redactions,
|
||||
membersChanged: [...timelineEvents, ...stateEvents].some((event) => event.type === "m.room.member"),
|
||||
}
|
||||
}
|
||||
|
||||
const sendTenantRoomMessage = async (
|
||||
userId: string,
|
||||
tenantId: number | null,
|
||||
@@ -1472,6 +1614,91 @@ export function matrixService(server: FastifyInstance) {
|
||||
return { success: true, eventId, key: reactionKey }
|
||||
}
|
||||
|
||||
const editTenantRoomMessage = async (
|
||||
userId: string,
|
||||
tenantId: number | null,
|
||||
options: MatrixTenantRoomOptions = {},
|
||||
eventId: string,
|
||||
text: string
|
||||
) => {
|
||||
const message = text.trim()
|
||||
if (!eventId || !message) {
|
||||
throw Object.assign(
|
||||
new Error("Nachricht und Zielnachricht sind erforderlich"),
|
||||
{ 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")}`
|
||||
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}`,
|
||||
"m.new_content": {
|
||||
msgtype: "m.text",
|
||||
body: message,
|
||||
},
|
||||
"m.relates_to": {
|
||||
rel_type: "m.replace",
|
||||
event_id: eventId,
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
id: response.event_id,
|
||||
targetEventId: eventId,
|
||||
body: message,
|
||||
timestamp: Date.now(),
|
||||
own: true,
|
||||
}
|
||||
}
|
||||
|
||||
const redactTenantRoomMessage = async (
|
||||
userId: string,
|
||||
tenantId: number | null,
|
||||
options: MatrixTenantRoomOptions = {},
|
||||
eventId: string
|
||||
) => {
|
||||
if (!eventId) {
|
||||
throw Object.assign(
|
||||
new Error("Zielnachricht ist erforderlich"),
|
||||
{ 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(
|
||||
`/_matrix/client/v3/rooms/${encodeURIComponent(room.roomId)}/redact/${encodeURIComponent(eventId)}/${encodeURIComponent(txnId)}`,
|
||||
session.accessToken,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
reason: "Nachricht in FEDEO gelöscht",
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
return { success: true, eventId }
|
||||
}
|
||||
|
||||
const markTenantRoomRead = async (
|
||||
userId: string,
|
||||
tenantId: number | null,
|
||||
@@ -1961,8 +2188,11 @@ export function matrixService(server: FastifyInstance) {
|
||||
getTenantRoomMessages,
|
||||
getTenantRoomMembers,
|
||||
searchTenantRoomMessages,
|
||||
syncTenantRoomEvents,
|
||||
sendTenantRoomMessage,
|
||||
sendTenantRoomReaction,
|
||||
editTenantRoomMessage,
|
||||
redactTenantRoomMessage,
|
||||
markTenantRoomRead,
|
||||
sendTenantRoomAttachment,
|
||||
getMediaContent,
|
||||
|
||||
@@ -736,6 +736,21 @@ export default async function communicationRoutes(server: FastifyInstance) {
|
||||
}
|
||||
})
|
||||
|
||||
server.get("/communication/matrix/rooms/:roomKey/sync", async (req, reply) => {
|
||||
try {
|
||||
const query = req.query as { since?: string; initial?: string }
|
||||
return await matrix.syncTenantRoomEvents(
|
||||
req.user.user_id,
|
||||
req.user.tenant_id,
|
||||
roomOptionsFromRequest(req),
|
||||
query.since,
|
||||
query.initial === "1"
|
||||
)
|
||||
} catch (err: any) {
|
||||
return handleMatrixError(req, reply, err, "Matrix sync failed")
|
||||
}
|
||||
})
|
||||
|
||||
server.get("/communication/matrix/rooms/:roomKey/members", async (req, reply) => {
|
||||
try {
|
||||
return await matrix.getTenantRoomMembers(
|
||||
@@ -851,6 +866,36 @@ export default async function communicationRoutes(server: FastifyInstance) {
|
||||
}
|
||||
})
|
||||
|
||||
server.put("/communication/matrix/rooms/:roomKey/messages/:eventId", async (req, reply) => {
|
||||
try {
|
||||
const params = req.params as { eventId: string }
|
||||
const body = req.body as { text?: string }
|
||||
return await matrix.editTenantRoomMessage(
|
||||
req.user.user_id,
|
||||
req.user.tenant_id,
|
||||
roomOptionsFromRequest(req),
|
||||
params.eventId,
|
||||
body.text || ""
|
||||
)
|
||||
} catch (err: any) {
|
||||
return handleMatrixError(req, reply, err, "Matrix message edit failed")
|
||||
}
|
||||
})
|
||||
|
||||
server.delete("/communication/matrix/rooms/:roomKey/messages/:eventId", async (req, reply) => {
|
||||
try {
|
||||
const params = req.params as { eventId: string }
|
||||
return await matrix.redactTenantRoomMessage(
|
||||
req.user.user_id,
|
||||
req.user.tenant_id,
|
||||
roomOptionsFromRequest(req),
|
||||
params.eventId
|
||||
)
|
||||
} catch (err: any) {
|
||||
return handleMatrixError(req, reply, err, "Matrix message delete failed")
|
||||
}
|
||||
})
|
||||
|
||||
server.post("/communication/matrix/rooms/:roomKey/attachments", async (req, reply) => {
|
||||
try {
|
||||
const attachment = await uploadedAttachmentFromRequest(req)
|
||||
|
||||
Reference in New Issue
Block a user