feat: extract files from message logs

This commit is contained in:
zhaoyilun
2026-07-10 00:45:08 +08:00
parent 3d68a18097
commit fe242145bb
5 changed files with 249 additions and 7 deletions

142
internal/isphere/files.go Normal file
View File

@@ -0,0 +1,142 @@
package isphere
import (
"path/filepath"
"regexp"
"sort"
"strings"
)
type File struct {
FileID string
MessageID string
ConversationID string
FileName string
SizeBytes *int64
MimeType string
CreatedAt string
DownloadRef string
SavedPath string
SHA256 string
Source string
RawRef string
}
type ListFilesQuery struct {
ConversationID string
MessageID string
FileID string
NameContains string
Limit int
}
type ListFilesResult struct {
Files []File
}
var fileNamePattern = regexp.MustCompile(`(?i)[^\s<>:"|?*]+\.(?:docx?|xlsx?|pptx?|pdf|zip|rar|png|jpe?g|txt|csv)\b`)
func ListFilesFromMessages(messages []Message, query ListFilesQuery) ListFilesResult {
files := make([]File, 0)
for _, message := range messages {
if !messageLooksFileBacked(message) {
continue
}
fileName := extractMessageFileName(message.Text)
if fileName == "" {
continue
}
file := File{
FileID: fileID(message.ID, fileName),
MessageID: message.ID,
ConversationID: message.ConversationID,
FileName: fileName,
MimeType: mimeTypeForFileName(fileName),
CreatedAt: message.Timestamp,
Source: "local_readonly",
RawRef: "packetlog_file_transfer",
}
if fileMatchesQuery(file, query) {
files = append(files, file)
}
}
sort.Slice(files, func(i, j int) bool {
if files[i].FileID == files[j].FileID {
return files[i].MessageID < files[j].MessageID
}
return files[i].FileID < files[j].FileID
})
limit := query.Limit
if limit <= 0 || limit > len(files) {
limit = len(files)
}
return ListFilesResult{Files: files[:limit]}
}
func messageLooksFileBacked(message Message) bool {
return strings.Contains(strings.ToUpper(message.Subject), "FILE_TRANSFER") || extractMessageFileName(message.Text) != ""
}
func extractMessageFileName(text string) string {
match := fileNamePattern.FindString(strings.TrimSpace(text))
return strings.Trim(match, " .,;,。;")
}
func fileID(messageID string, fileName string) string {
if messageID == "" {
return fileName
}
return messageID + ":" + fileName
}
func fileMatchesQuery(file File, query ListFilesQuery) bool {
if query.ConversationID != "" && file.ConversationID != query.ConversationID {
return false
}
if query.MessageID != "" && file.MessageID != query.MessageID {
return false
}
if query.FileID != "" && file.FileID != query.FileID {
return false
}
nameQuery := strings.ToLower(strings.TrimSpace(query.NameContains))
if nameQuery != "" && !strings.Contains(strings.ToLower(file.FileName), nameQuery) {
return false
}
return true
}
func mimeTypeForFileName(name string) string {
switch strings.ToLower(filepath.Ext(name)) {
case ".doc":
return "application/msword"
case ".docx":
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
case ".xls":
return "application/vnd.ms-excel"
case ".xlsx":
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
case ".ppt":
return "application/vnd.ms-powerpoint"
case ".pptx":
return "application/vnd.openxmlformats-officedocument.presentationml.presentation"
case ".pdf":
return "application/pdf"
case ".zip":
return "application/zip"
case ".rar":
return "application/vnd.rar"
case ".png":
return "image/png"
case ".jpg", ".jpeg":
return "image/jpeg"
case ".txt":
return "text/plain"
case ".csv":
return "text/csv"
default:
return ""
}
}

View File

@@ -0,0 +1,31 @@
package isphere
import (
"reflect"
"testing"
)
func TestListFilesFromMessagesExtractsFileTransferCandidates(t *testing.T) {
messages := []Message{
{ID: "msg-1", ConversationID: "alice@imopenfire1-lanzhou|bob@imopenfire1-lanzhou", Text: "redacted-report.docx", Timestamp: "1783423807000", Subject: "FILE_TRANSFER_CANCEL"},
{ID: "msg-2", ConversationID: "alice@imopenfire1-lanzhou|bob@imopenfire1-lanzhou", Text: "plain chat", Timestamp: "1783423808000"},
{ID: "msg-3", ConversationID: "room@conference.imopenfire1-lanzhou", Text: "redacted-screenshot.png", Timestamp: "1783423809000"},
}
got := ListFilesFromMessages(messages, ListFilesQuery{NameContains: "report", Limit: 10})
want := ListFilesResult{Files: []File{
{
FileID: "msg-1:redacted-report.docx",
MessageID: "msg-1",
ConversationID: "alice@imopenfire1-lanzhou|bob@imopenfire1-lanzhou",
FileName: "redacted-report.docx",
MimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
CreatedAt: "1783423807000",
Source: "local_readonly",
RawRef: "packetlog_file_transfer",
},
}}
if !reflect.DeepEqual(got, want) {
t.Fatalf("ListFilesFromMessages() = %#v, want %#v", got, want)
}
}