172 lines
4.1 KiB
Go
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
|
|
}
|