Files
isphere-ai-bridge/internal/isphere/source.go
2026-07-10 01:27:27 +08:00

172 lines
4.1 KiB
Go

package isphere
import (
"context"
"fmt"
"strconv"
"strings"
"time"
"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 {
ConversationID string
Query string
Since string
Limit int
}
type ReceiveMessagesResult struct {
Messages []Message
}
type EncryptedPacketLogSource struct {
Lines []string
}
func (s EncryptedPacketLogSource) ReceiveMessages(ctx context.Context, query ReceiveMessagesQuery) (ReceiveMessagesResult, error) {
since, hasSince, err := parseReceiveMessagesSince(query.Since)
if err != nil {
return ReceiveMessagesResult{}, err
}
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 {
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)
}
message := normalizePacketMessage(parsed)
if !receiveMessageMatchesQuery(message, query) || !receiveMessageMatchesSince(message, since, hasSince) {
continue
}
messages = append(messages, message)
if len(messages) == limit {
break
}
}
return ReceiveMessagesResult{Messages: messages}, nil
}
func parseReceiveMessagesSince(value string) (time.Time, bool, error) {
trimmed := strings.TrimSpace(value)
if trimmed == "" {
return time.Time{}, false, nil
}
parsed, err := time.Parse(time.RFC3339Nano, trimmed)
if err != nil {
return time.Time{}, false, fmt.Errorf("parse receive messages since %q: %w", value, err)
}
return parsed.UTC(), true, nil
}
func receiveMessageMatchesQuery(message Message, query ReceiveMessagesQuery) bool {
if strings.TrimSpace(query.ConversationID) != "" && message.ConversationID != strings.TrimSpace(query.ConversationID) {
return false
}
keyword := strings.ToLower(strings.TrimSpace(query.Query))
if keyword == "" {
return true
}
values := []string{
message.ID,
message.ConversationID,
message.SenderID,
message.ReceiverID,
message.Text,
message.Subject,
message.ReceiptID,
}
for _, value := range values {
if strings.Contains(strings.ToLower(value), keyword) {
return true
}
}
return false
}
func receiveMessageMatchesSince(message Message, since time.Time, hasSince bool) bool {
if !hasSince {
return true
}
messageTime, ok := packetMessageTimestamp(message.Timestamp)
if !ok {
return false
}
return !messageTime.Before(since)
}
func packetMessageTimestamp(value string) (time.Time, bool) {
trimmed := strings.TrimSpace(value)
if trimmed == "" {
return time.Time{}, false
}
epoch, err := strconv.ParseInt(trimmed, 10, 64)
if err != nil {
return time.Time{}, false
}
if epoch > 1_000_000_000_000 {
return time.UnixMilli(epoch).UTC(), true
}
return time.Unix(epoch, 0).UTC(), true
}
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
}