feat: add log-backed receive message source

This commit is contained in:
zhaoyilun
2026-07-09 23:00:27 +08:00
parent 4d5b314f83
commit fe2a1947f3
3 changed files with 227 additions and 8 deletions

View File

@@ -0,0 +1,95 @@
package isphere
import (
"context"
"fmt"
"strings"
"isphere-ai-bridge/internal/isphere/logcodec"
"isphere-ai-bridge/internal/isphere/packetlog"
)
type Message struct {
ID string
ConversationID string
ConversationType string
SenderID string
ReceiverID string
Text string
Timestamp string
Subject string
ReceiptID string
Read bool
}
type ReceiveMessagesQuery struct {
Limit int
}
type ReceiveMessagesResult struct {
Messages []Message
}
type EncryptedPacketLogSource struct {
Lines []string
}
func (s EncryptedPacketLogSource) ReceiveMessages(ctx context.Context, query ReceiveMessagesQuery) (ReceiveMessagesResult, error) {
limit := query.Limit
if limit <= 0 || limit > len(s.Lines) {
limit = len(s.Lines)
}
messages := make([]Message, 0, limit)
for index, line := range s.Lines {
if len(messages) == limit {
break
}
select {
case <-ctx.Done():
return ReceiveMessagesResult{}, ctx.Err()
default:
}
plaintext, err := logcodec.DecryptLine(line)
if err != nil {
return ReceiveMessagesResult{}, fmt.Errorf("decrypt packet log line %d: %w", index, err)
}
parsed, err := packetlog.ParseMessageLog(plaintext)
if err != nil {
return ReceiveMessagesResult{}, fmt.Errorf("parse packet log line %d: %w", index, err)
}
messages = append(messages, normalizePacketMessage(parsed))
}
return ReceiveMessagesResult{Messages: messages}, nil
}
func normalizePacketMessage(msg packetlog.Message) Message {
sender := bareJID(msg.From)
receiver := bareJID(msg.To)
return Message{
ID: msg.ID,
ConversationID: conversationID(sender, receiver),
ConversationType: msg.Type,
SenderID: sender,
ReceiverID: receiver,
Text: msg.Body,
Timestamp: msg.SendTime,
Subject: msg.Subject,
ReceiptID: msg.ReceiptID,
Read: msg.Read,
}
}
func bareJID(value string) string {
beforeResource, _, _ := strings.Cut(value, "/")
return beforeResource
}
func conversationID(sender string, receiver string) string {
if sender == "" {
return receiver
}
if receiver == "" {
return sender
}
return sender + "|" + receiver
}

View File

@@ -0,0 +1,65 @@
package isphere
import (
"context"
"crypto/cipher"
"crypto/des"
"encoding/base64"
"testing"
)
func TestEncryptedPacketLogSourceReturnsNormalizedMessages(t *testing.T) {
plaintext := `--------------------------------------------------------------------------------------------------------------------------------------------
2026/7/7 15:30:07
<message id="msg-1" from="sender@imopenfire1-lanzhou/imp_pc_4.1.2.6842" to="receiver@imopenfire1-lanzhou" type="chat">
<body>redacted</body>
<received xmlns="urn:xmpp:receipts" id="receipt-1" type="1" stamp="" />
<subject>FILE_TRANSFER_CANCEL</subject>
<isphere xmlns="isphere.im" type="1001" sendtime="1783423807000" version="1" />
<readed />
</message>`
source := EncryptedPacketLogSource{Lines: []string{encryptPacketLineForTest(t, plaintext)}}
got, err := source.ReceiveMessages(context.Background(), ReceiveMessagesQuery{Limit: 10})
if err != nil {
t.Fatalf("ReceiveMessages returned error: %v", err)
}
if len(got.Messages) != 1 {
t.Fatalf("message count = %d, want 1", len(got.Messages))
}
msg := got.Messages[0]
if msg.ID != "msg-1" || msg.Text != "redacted" || msg.Subject != "FILE_TRANSFER_CANCEL" {
t.Fatalf("unexpected message content: %+v", msg)
}
if msg.ConversationID != "sender@imopenfire1-lanzhou|receiver@imopenfire1-lanzhou" {
t.Fatalf("conversation id = %q", msg.ConversationID)
}
if msg.SenderID != "sender@imopenfire1-lanzhou" || msg.ReceiverID != "receiver@imopenfire1-lanzhou" {
t.Fatalf("sender/receiver = %q/%q", msg.SenderID, msg.ReceiverID)
}
if msg.Timestamp != "1783423807000" || msg.ReceiptID != "receipt-1" || !msg.Read {
t.Fatalf("metadata = %+v", msg)
}
}
func encryptPacketLineForTest(t *testing.T, plaintext string) string {
t.Helper()
block, err := des.NewCipher([]byte("hyhccdtm"))
if err != nil {
t.Fatalf("NewCipher: %v", err)
}
plain := padPacketLineForTest([]byte(plaintext), des.BlockSize)
ciphertext := make([]byte, len(plain))
iv := []byte{0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF}
cipher.NewCBCEncrypter(block, iv).CryptBlocks(ciphertext, plain)
return base64.StdEncoding.EncodeToString(ciphertext)
}
func padPacketLineForTest(data []byte, blockSize int) []byte {
pad := blockSize - len(data)%blockSize
out := append([]byte(nil), data...)
for i := 0; i < pad; i++ {
out = append(out, byte(pad))
}
return out
}