Serve T5DE-hosted rooms; a second client instance's own data
CI / build (push) Successful in 36s

Hosted rooms (internal/hosted): room calls for them answered here, their IMQ queues served (a new
proxy.IMQRouter hook takes them out of subscriptions to IMVU), chat relayed through the hub's
WebSocket (hub.Client gains Send, OnRoom and OnConnect), the room list extended, room products
checked against the real owner, and anything else naming a hosted room kept from IMVU.

T5DE_INSTANCE gives a second client's proxy its own data folder and log.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
dhkatzandClaude Opus 5.5 committed 2026-10-30 03:36:17 -07:00
1 parent 455dae1013
commit d8332b21a8
12 files changed
+1492 -12

No files matched your search

+112
View File
@@ -9,6 +9,16 @@ const (
C2gSubscribe = 1309
G2cResult = 3102
G2cSendMessage = 3104
G2cStateChange = 3105
G2cJoinedQueue = 3106
G2cLeftQueue = 3107
G2cCreateMount = 3108
)
// Mount types (G2cCreateMount).
const (
MessageMount = 1
StateMount = 2
)
// SendMessage is a C2gSendMessage: a message the client sends to a queue's mount.
@@ -202,3 +212,105 @@ func (m Delivered) Marshal() []byte {
}
return b
}
// QueueOp is a queue subscribed to with an operation ID, for a G2cResult.
type QueueOp struct {
Queue string
OpID uint64
raw []byte // as the client sent it (with any other fields, such as properties)
}
// Subscription is a C2gSubscribe's or C2gUnsubscribe's data: queues listed plainly, and with operation IDs.
type Subscription struct {
Queues []string
WithResults []QueueOp
}
// ParseSubscriptionOps reads a C2gSubscribe's or C2gUnsubscribe's data, keeping operation IDs.
func ParseSubscriptionOps(data []byte) (Subscription, error) {
fs, err := fields(data)
if err != nil {
return Subscription{}, err
}
var s Subscription
for _, f := range fs {
switch {
case f.num == 1 && f.isLen:
s.Queues = append(s.Queues, string(f.bytes))
case f.num == 2 && f.isLen:
sub, err := fields(f.bytes)
if err != nil {
return Subscription{}, err
}
q := QueueOp{raw: f.bytes}
for _, sf := range sub {
switch {
case sf.num == 1 && sf.isLen:
q.Queue = string(sf.bytes)
case sf.num == 2 && !sf.isLen:
q.OpID = sf.varint
}
}
s.WithResults = append(s.WithResults, q)
default:
return Subscription{}, errors.New("imq: unexpected field in a subscription")
}
}
return s, nil
}
// All is every queue in the subscription.
func (s Subscription) All() []string {
out := append([]string(nil), s.Queues...)
for _, q := range s.WithResults {
out = append(out, q.Queue)
}
return out
}
// Empty reports whether it names no queues.
func (s Subscription) Empty() bool { return len(s.Queues) == 0 && len(s.WithResults) == 0 }
// Marshal encodes the subscription's data.
func (s Subscription) Marshal() []byte {
var b []byte
for _, q := range s.Queues {
b = appendBytesField(b, 1, []byte(q))
}
for _, q := range s.WithResults {
raw := q.raw
if raw == nil {
raw = appendVarintField(appendBytesField(nil, 1, []byte(q.Queue)), 2, q.OpID)
}
b = appendBytesField(b, 2, raw)
}
return b
}
// Property is a mount's property: a key and a value.
type Property struct{ Key, Value string }
func appendProperties(b []byte, num uint64, props []Property) []byte {
for _, p := range props {
b = appendBytesField(b, num, appendBytesField(appendBytesField(nil, 1, []byte(p.Key)), 2, []byte(p.Value)))
}
return b
}
// JoinedQueue encodes a G2cJoinedQueue's (or G2cLeftQueue's) data: a user joined (left) a queue.
func JoinedQueue(userID, queue string, sequence uint64) []byte {
return appendVarintField(appendBytesField(appendBytesField(nil, 1, []byte(userID)), 2, []byte(queue)), 3, sequence)
}
// CreateMount encodes a G2cCreateMount's data: a queue's mount, of type MessageMount or StateMount.
func CreateMount(queue, mount string, mountType uint64, props []Property, sequence uint64) []byte {
b := appendVarintField(appendBytesField(appendBytesField(nil, 1, []byte(queue)), 2, []byte(mount)), 3, mountType)
return appendVarintField(appendProperties(b, 4, props), 5, sequence)
}
// StateChange encodes a G2cStateChange's data: a state mount's properties changed (an empty value removes one).
func StateChange(opID uint64, userID, queue, mount string, props []Property, sequence uint64) []byte {
b := appendVarintField(nil, 1, opID)
b = appendBytesField(appendBytesField(appendBytesField(b, 2, []byte(userID)), 3, []byte(queue)), 4, []byte(mount))
return appendVarintField(appendProperties(b, 5, props), 6, sequence)
}