feat(plugin): add SCUM ownership migration foundation
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
package companion
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const semanticEventsSnapshotType = "semantic.events"
|
||||
const semanticEventsSchemaVersion = "1"
|
||||
const semanticEventsRetentionSeconds = 7 * 24 * 60 * 60
|
||||
const semanticEventsMaxRecords = 1000
|
||||
|
||||
type SemanticEvent struct { Type string `json:"type"`; OccurredAt time.Time `json:"occurredAt"`; PlayerID string `json:"playerId,omitempty"`; DisplayName string `json:"displayName,omitempty"`; NetworkCorrelation string `json:"networkCorrelation,omitempty"` }
|
||||
var supportedLoginLine = regexp.MustCompile(`^LOGIN player=([A-Za-z0-9._:-]{1,96}) name=([^\n]{1,80}) at=([0-9TZ:+.-]{20,40})(?: network=([^\s]{1,128}))?$`)
|
||||
var supportedLogoutLine = regexp.MustCompile(`^LOGOUT player=([A-Za-z0-9._:-]{1,96}) at=([0-9TZ:+.-]{20,40})$`)
|
||||
|
||||
// ParseSemanticEvent supports only versioned, allow-listed extension output.
|
||||
// Unknown formats deliberately yield no event and may be reported as a bounded
|
||||
// diagnostic by the caller.
|
||||
func ParseSemanticEvent(line string, serverCorrelationKey []byte) (SemanticEvent, bool) {
|
||||
if match := supportedLoginLine.FindStringSubmatch(strings.TrimSpace(line)); len(match) != 0 { occurred, err := time.Parse(time.RFC3339, match[3]); if err != nil { return SemanticEvent{}, false }; event := SemanticEvent{Type: "scum.login", PlayerID: match[1], DisplayName: match[2], OccurredAt: occurred}; if match[4] != "" { event.NetworkCorrelation = irreversibleServerCorrelation(serverCorrelationKey, match[4]) }; return event, true }
|
||||
if match := supportedLogoutLine.FindStringSubmatch(strings.TrimSpace(line)); len(match) != 0 { occurred, err := time.Parse(time.RFC3339, match[2]); if err != nil { return SemanticEvent{}, false }; return SemanticEvent{Type: "scum.logout", PlayerID: match[1], OccurredAt: occurred}, true }
|
||||
return SemanticEvent{}, false
|
||||
}
|
||||
func irreversibleServerCorrelation(key []byte, source string) string { if len(key) == 0 || source == "" { return "" }; mac := hmac.New(sha256.New, key); _, _ = mac.Write([]byte(source)); return hex.EncodeToString(mac.Sum(nil)) }
|
||||
func (client *Client) UploadSemanticEvents(ctx context.Context, streamKey string, sequence uint64, events []SemanticEvent) (AcceptedSnapshot, error) { if len(events) == 0 || len(events) > 100 { return AcceptedSnapshot{}, fmt.Errorf("semantic event batch is invalid") }; payload := map[string]any{"events": events}; return client.UploadSnapshot(ctx, Snapshot{Type: semanticEventsSnapshotType, SchemaVersion: semanticEventsSchemaVersion, StreamKey: streamKey, Sequence: sequence, ObservedAt: client.now().UTC(), Payload: payload, KeepForSeconds: semanticEventsRetentionSeconds, MaxRecords: semanticEventsMaxRecords}) }
|
||||
Reference in New Issue
Block a user