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

+49 -2
View File
@@ -359,6 +359,13 @@ func (p *Products) authorizeProducts2(x *proxy.Exchange, call *xmlrpc.Call) erro
type auth struct {
user, product int64
}
// In a room hosted by T5DE's hub, the client asks about the room's products as user 2100000000 (the first part
// of the room's ID): IMVU is asked about the room owner's instead, and answers go back as 2100000000.
owner := int64(0)
if p.HostedOwner != nil {
owner = p.HostedOwner()
}
hostedAsked := map[int64]bool{}
var community []auth
forward := []xmlrpc.Value{table[0]}
asked := map[auth]bool{}
@@ -372,6 +379,10 @@ func (p *Products) authorizeProducts2(x *proxy.Exchange, call *xmlrpc.Call) erro
if !ok1 || !ok2 {
return nil
}
if user == HostedOwnerID && owner > 0 {
user, hostedAsked[product] = owner, true
row = []xmlrpc.Value{user, product}
}
if !IsCommunity(product) {
if !asked[auth{user, product}] {
forward, asked[auth{user, product}] = append(forward, row), true
@@ -386,9 +397,33 @@ func (p *Products) authorizeProducts2(x *proxy.Exchange, call *xmlrpc.Call) erro
}
}
}
if len(community) == 0 {
if len(community) == 0 && len(hostedAsked) == 0 {
return nil
}
// hostedBack gives rows asked about as 2100000000 that user ID back.
hostedBack := func(columns []xmlrpc.Value, rows []xmlrpc.Value) {
userCol, productCol := -1, -1
for i, c := range columns {
switch c {
case "userId":
userCol = i
case "productId":
productCol = i
}
}
if userCol < 0 || productCol < 0 {
return
}
for _, row := range rows {
r, ok := row.([]xmlrpc.Value)
if !ok || len(r) <= userCol || len(r) <= productCol {
continue
}
if product, _ := r[productCol].(int64); r[userCol] == owner && hostedAsked[product] {
r[userCol] = int64(HostedOwnerID)
}
}
}
ours := func(columns []xmlrpc.Value) []xmlrpc.Value {
var rows []xmlrpc.Value
for _, a := range community {
@@ -421,6 +456,7 @@ func (p *Products) authorizeProducts2(x *proxy.Exchange, call *xmlrpc.Call) erro
columns[i] = c
}
out := append([]xmlrpc.Value{columns}, ours(columns)...)
hostedBack(columns, out[1:])
x.Response = localResponse(200, "text/xml", (&xmlrpc.Response{Params: []xmlrpc.Value{out}}).Marshal())
return nil
}
@@ -447,7 +483,9 @@ func (p *Products) authorizeProducts2(x *proxy.Exchange, call *xmlrpc.Call) erro
if !ok {
return body, errors.New("products: unexpected authorizeProducts2 columns")
}
r.Params[0] = append(rows, ours(columns)...)
all := append(rows, ours(columns)...)
hostedBack(columns, all[1:])
r.Params[0] = all
return r.Marshal(), nil
}
return nil
@@ -523,6 +561,15 @@ func (p *Products) interceptBaseLook(x *proxy.Exchange) error {
// Outgoing implements proxy.IMQFilter: community products are taken out of outfit chat commands (IMVU and other
// people see the defaults), and whole outfits are remembered.
func (p *Products) Outgoing(queue, mount string, message []byte) ([]byte, bool) {
out, drop := p.outgoing(queue, mount, message)
// A hosted room's message goes to the hub (without community products, as to IMVU), never to IMVU.
if p.Hosted != nil && !drop && p.Hosted.Send(queue, mount, out) {
return nil, true
}
return out, drop
}
func (p *Products) outgoing(queue, mount string, message []byte) ([]byte, bool) {
if !strings.HasPrefix(queue, "/chat/") || mount != "messages" {
return message, false
}