4bce095964
Mueve duckdb_open, clickhouse_open, postgres_open, matrix_* y keyring_token_store
del paquete monolitico functions/infra a subpaquetes propios
(functions/infra/{duckdb,clickhouse,postgres,matrix,keyring}). El paquete infra ya
no importa los drivers (go-duckdb, clickhouse-go, pgx, mautrix, go-keyring), por lo
que las apps que solo usan funciones ligeras (process, cron, http, sqlite) dejan de
arrastrarlos. Reduccion de binarios: dag_engine 72->10MB, registry_api 70->8.7MB,
services_api 70->9MB, call_monitor 68->6.6MB, sqlite_api 70->8.9MB.
Los IDs del registry se mantienen estables (domain: infra en frontmatter). Se
preservan los build tags goolm/libolm de matrix_crypto_init.
Tambien corrige TestSSEHandler: el test leia el body con un unico Read() que con
HTTP chunked solo capturaba el primer evento; ahora usa io.ReadAll hasta EOF.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
122 lines
4.8 KiB
Go
122 lines
4.8 KiB
Go
package matrix
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/microcosm-cc/bluemonday"
|
|
"github.com/yuin/goldmark"
|
|
"maunium.net/go/mautrix"
|
|
"maunium.net/go/mautrix/event"
|
|
"maunium.net/go/mautrix/id"
|
|
)
|
|
|
|
// matrixMarkdownToHTML convierte Markdown a HTML sanitizado con goldmark + bluemonday.
|
|
// El HTML resultante es seguro para incluir en formatted_body de un evento Matrix.
|
|
// Allowlist: bluemonday UGCPolicy + <details>, <summary>, <code>, <pre>.
|
|
func matrixMarkdownToHTML(markdown string) (string, error) {
|
|
var buf bytes.Buffer
|
|
if err := goldmark.Convert([]byte(markdown), &buf); err != nil {
|
|
return "", fmt.Errorf("matrix_message_send: goldmark convert: %w", err)
|
|
}
|
|
p := bluemonday.UGCPolicy()
|
|
p.AllowElements("details", "summary", "code", "pre")
|
|
sanitized := p.SanitizeBytes(buf.Bytes())
|
|
return string(sanitized), nil
|
|
}
|
|
|
|
// matrixSendEvent es el helper interno que llama a client.SendMessageEvent
|
|
// y devuelve el id.EventID asignado por Synapse.
|
|
func matrixSendEvent(ctx context.Context, client *mautrix.Client, roomID id.RoomID, eventType event.Type, content interface{}) (id.EventID, error) {
|
|
resp, err := client.SendMessageEvent(ctx, roomID, eventType, content)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return resp.EventID, nil
|
|
}
|
|
|
|
// MatrixSendText envía un mensaje de texto plano (m.text) al room indicado.
|
|
// Si el room tiene E2EE activo y client.Crypto != nil, mautrix cifra automáticamente.
|
|
func MatrixSendText(ctx context.Context, client *mautrix.Client, roomID id.RoomID, body string) (id.EventID, error) {
|
|
if client == nil {
|
|
return "", fmt.Errorf("matrix_message_send: client no puede ser nil")
|
|
}
|
|
content := &event.MessageEventContent{
|
|
MsgType: event.MsgText,
|
|
Body: body,
|
|
}
|
|
return matrixSendEvent(ctx, client, roomID, event.EventMessage, content)
|
|
}
|
|
|
|
// MatrixSendMarkdown convierte markdown a HTML con goldmark, lo sanitiza con bluemonday
|
|
// (UGCPolicy + <details>, <summary>, <code>, <pre>) y envía con format=org.matrix.custom.html.
|
|
// El campo Body contiene el markdown original como fallback para clientes sin HTML.
|
|
func MatrixSendMarkdown(ctx context.Context, client *mautrix.Client, roomID id.RoomID, markdown string) (id.EventID, error) {
|
|
if client == nil {
|
|
return "", fmt.Errorf("matrix_message_send: client no puede ser nil")
|
|
}
|
|
htmlBody, err := matrixMarkdownToHTML(markdown)
|
|
if err != nil {
|
|
return "", fmt.Errorf("matrix_message_send.MatrixSendMarkdown: %w", err)
|
|
}
|
|
content := &event.MessageEventContent{
|
|
MsgType: event.MsgText,
|
|
Body: markdown,
|
|
Format: event.FormatHTML,
|
|
FormattedBody: htmlBody,
|
|
}
|
|
return matrixSendEvent(ctx, client, roomID, event.EventMessage, content)
|
|
}
|
|
|
|
// MatrixSendReply envía un mensaje con m.relates_to.m.in_reply_to apuntando a replyTo.
|
|
// El body es el texto de la respuesta. En v0.1.0 el caller construye la cita si la necesita.
|
|
// El cifrado E2EE es automático si client.Crypto está configurado.
|
|
func MatrixSendReply(ctx context.Context, client *mautrix.Client, roomID id.RoomID, replyTo id.EventID, body string) (id.EventID, error) {
|
|
if client == nil {
|
|
return "", fmt.Errorf("matrix_message_send: client no puede ser nil")
|
|
}
|
|
content := &event.MessageEventContent{
|
|
MsgType: event.MsgText,
|
|
Body: body,
|
|
RelatesTo: (&event.RelatesTo{}).SetReplyTo(replyTo),
|
|
}
|
|
return matrixSendEvent(ctx, client, roomID, event.EventMessage, content)
|
|
}
|
|
|
|
// MatrixEditMessage envía un replacement event (m.replace) compatible con Element y la spec Matrix.
|
|
// NewContent contiene el texto nuevo; Body es el fallback "* newBody" para clientes sin soporte de edición.
|
|
// eventID es el evento original a reemplazar.
|
|
func MatrixEditMessage(ctx context.Context, client *mautrix.Client, roomID id.RoomID, eventID id.EventID, newBody string) (id.EventID, error) {
|
|
if client == nil {
|
|
return "", fmt.Errorf("matrix_message_send: client no puede ser nil")
|
|
}
|
|
content := &event.MessageEventContent{
|
|
MsgType: event.MsgText,
|
|
Body: "* " + newBody,
|
|
NewContent: &event.MessageEventContent{
|
|
MsgType: event.MsgText,
|
|
Body: newBody,
|
|
},
|
|
RelatesTo: (&event.RelatesTo{}).SetReplace(eventID),
|
|
}
|
|
return matrixSendEvent(ctx, client, roomID, event.EventMessage, content)
|
|
}
|
|
|
|
// MatrixSendReaction envía un evento m.reaction con m.relates_to.rel_type=m.annotation.
|
|
// key debe ser el emoji unicode raw (ej. "👍"), no shortcode (:thumbsup:).
|
|
// Las reactions no se cifran aunque el room sea E2EE (comportamiento de mautrix-go).
|
|
func MatrixSendReaction(ctx context.Context, client *mautrix.Client, roomID id.RoomID, targetEventID id.EventID, key string) (id.EventID, error) {
|
|
if client == nil {
|
|
return "", fmt.Errorf("matrix_message_send: client no puede ser nil")
|
|
}
|
|
content := &event.ReactionEventContent{
|
|
RelatesTo: event.RelatesTo{
|
|
Type: event.RelAnnotation,
|
|
EventID: targetEventID,
|
|
Key: key,
|
|
},
|
|
}
|
|
return matrixSendEvent(ctx, client, roomID, event.EventReaction, content)
|
|
}
|