T5 badge on room cards and the Manage tab; room_info.php for hosted rooms
CI / build (push) Successful in 44s
CI / build (push) Successful in 44s
room_info.php (the room card's participants, asked for every room) is answered for hosted rooms from the hub (it was refused as naming a hosted room, which the client showed as an error), with max_users; it and getRoomCardInfo get t5de_t5 for hosted and decorated rooms, as do the person's own rooms in room_management_info.php. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
d379d848c1
commit
fc59367ba6
2 files changed
+195
No files matched your search
@@ -441,6 +441,16 @@ func (r *Rooms) Intercept(x *proxy.Exchange) error {
|
||||
case "/api/rooms/rooms_list_paginated.php":
|
||||
r.addToRoomList(x)
|
||||
return nil
|
||||
case "/api/room_management_info.php":
|
||||
r.markManagedRooms(x)
|
||||
return nil
|
||||
case "/api/rooms/room_info.php":
|
||||
if n, ok := RoomNumber(x.URL.Query().Get("room_id")); ok {
|
||||
x.Response = r.roomInfo(n)
|
||||
return nil
|
||||
}
|
||||
r.markRoomInfo(x, x.URL.Query().Get("room_id"))
|
||||
return nil
|
||||
case "/api/rooms/room_rating.php":
|
||||
if _, ok := RoomNumber(x.URL.Query().Get("room_instance_id")); ok {
|
||||
x.Response = proxy.LocalResponse(200, "application/json", []byte(`{"score":0,"result":"success"}`))
|
||||
@@ -514,6 +524,9 @@ func (r *Rooms) interceptXMLRPC(x *proxy.Exchange) error {
|
||||
case "test.getRoomCardInfo", "test.getRoomState", "test.setRoomState":
|
||||
n, ok := RoomNumber(member(args, "room_instance_id"))
|
||||
if !ok {
|
||||
if call.Method == "test.getRoomCardInfo" {
|
||||
r.markCard(x, member(args, "room_instance_id"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
switch call.Method {
|
||||
@@ -601,6 +614,7 @@ func (r *Rooms) roomCard(ctx context.Context, n int64) *http.Response {
|
||||
{Name: "autoBootWhenOwnerLeaves", Value: false}, {Name: "rating", Value: float64(0)},
|
||||
{Name: "permission", Value: ""}, {Name: "themedStatus", Value: ""}, {Name: "themeId", Value: ""},
|
||||
{Name: "eligibleForThemedRoomSubmission", Value: ""}, {Name: "occupants", Value: []xmlrpc.Value{}},
|
||||
{Name: "t5de_t5", Value: true}, // the client's T5 badge (its t5de-rooms patch)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -946,3 +960,140 @@ func swapToken(placement, from, to string) string {
|
||||
}
|
||||
return strings.Join(fields, " ")
|
||||
}
|
||||
|
||||
// decorated is which of the IMVU rooms the hub has decorations for (20 asked at a time).
|
||||
func (r *Rooms) decorated(ctx context.Context, instances []string) map[string]bool {
|
||||
out := map[string]bool{}
|
||||
for len(instances) > 0 {
|
||||
batch := instances[:min(len(instances), 20)]
|
||||
instances = instances[len(batch):]
|
||||
var answer struct {
|
||||
Rooms []struct {
|
||||
Instance string `json:"instance"`
|
||||
Contents []json.RawMessage `json:"contents"`
|
||||
} `json:"rooms"`
|
||||
}
|
||||
if err := r.Hub.Call(ctx, "GET", "/api/v1/decorations?instances="+url.QueryEscape(strings.Join(batch, ",")), nil, &answer); err != nil {
|
||||
break
|
||||
}
|
||||
for _, d := range answer.Rooms {
|
||||
if len(d.Contents) > 0 {
|
||||
out[d.Instance] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// markCard adds t5de_t5 to an IMVU room's card if it has T5 decorations (the client's T5 badge).
|
||||
func (r *Rooms) markCard(x *proxy.Exchange, instance string) {
|
||||
if instance == "" {
|
||||
return
|
||||
}
|
||||
x.ModifyResponse = func(resp *http.Response, body []byte) ([]byte, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
if !r.decorated(ctx, []string{instance})[instance] {
|
||||
return body, nil
|
||||
}
|
||||
answer, err := xmlrpc.ParseResponse(body)
|
||||
if err != nil || answer.Fault != nil || len(answer.Params) != 1 {
|
||||
return body, nil
|
||||
}
|
||||
card, ok := answer.Params[0].(xmlrpc.Struct)
|
||||
if !ok {
|
||||
return body, nil
|
||||
}
|
||||
answer.Params[0] = append(card, xmlrpc.Member{Name: "t5de_t5", Value: true})
|
||||
return answer.Marshal(), nil
|
||||
}
|
||||
}
|
||||
|
||||
// markManagedRooms adds t5de_t5 to the person's own rooms with T5 decorations (room_management_info.php's rooms,
|
||||
// which the chat rooms page's Manage tab shows).
|
||||
func (r *Rooms) markManagedRooms(x *proxy.Exchange) {
|
||||
x.ModifyResponse = func(resp *http.Response, body []byte) ([]byte, error) {
|
||||
var top map[string]json.RawMessage
|
||||
if resp.StatusCode != http.StatusOK || json.Unmarshal(body, &top) != nil {
|
||||
return body, nil
|
||||
}
|
||||
holder, wrapped := top, false
|
||||
if inner, ok := top["result"]; ok {
|
||||
var m map[string]json.RawMessage
|
||||
if json.Unmarshal(inner, &m) == nil && m["rooms"] != nil {
|
||||
holder, wrapped = m, true
|
||||
}
|
||||
}
|
||||
var rooms []map[string]json.RawMessage
|
||||
if json.Unmarshal(holder["rooms"], &rooms) != nil || len(rooms) == 0 {
|
||||
return body, nil
|
||||
}
|
||||
var instances []string
|
||||
for _, room := range rooms {
|
||||
instances = append(instances, rawText(room["room_instance_id"]))
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
marked := r.decorated(ctx, instances)
|
||||
if len(marked) == 0 {
|
||||
return body, nil
|
||||
}
|
||||
for i, room := range rooms {
|
||||
if marked[instances[i]] {
|
||||
room["t5de_t5"] = json.RawMessage("1")
|
||||
}
|
||||
}
|
||||
holder["rooms"], _ = json.Marshal(rooms)
|
||||
if wrapped {
|
||||
top["result"], _ = json.Marshal(holder)
|
||||
}
|
||||
return json.Marshal(top)
|
||||
}
|
||||
}
|
||||
|
||||
// roomInfo answers room_info.php for a hosted room: the people in it, for the room card.
|
||||
func (r *Rooms) roomInfo(n int64) *http.Response {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
var out struct {
|
||||
Participants []struct {
|
||||
ImvuID int64 `json:"imvu_id"`
|
||||
Name string `json:"name"`
|
||||
} `json:"participants"`
|
||||
}
|
||||
people := []map[string]any{}
|
||||
if err := r.Hub.Call(ctx, "GET", fmt.Sprintf("/api/v1/hosted-rooms/%d/participants", n), nil, &out); err == nil {
|
||||
for _, p := range out.Participants {
|
||||
people = append(people, map[string]any{"customers_id": strconv.FormatInt(p.ImvuID, 10), "avatar_name": plain(p.Name),
|
||||
"is_friend": false, "age": "", "gender": "", "country": "", "avatar_download_size": 0})
|
||||
}
|
||||
}
|
||||
maxUsers := 0
|
||||
if rm, _, _, err := r.getRoom(ctx, n); err == nil {
|
||||
maxUsers = rm.MaxUsers
|
||||
}
|
||||
b, _ := json.Marshal(map[string]any{"visitors_count": nil, "boot_count": nil, "participants": people,
|
||||
"max_users": maxUsers, "t5de_t5": 1})
|
||||
return proxy.LocalResponse(200, "application/json", b)
|
||||
}
|
||||
|
||||
// markRoomInfo adds t5de_t5 to an IMVU room's room_info.php answer if it has T5 decorations (the room card's T5
|
||||
// badge; the card always asks it, however it was opened).
|
||||
func (r *Rooms) markRoomInfo(x *proxy.Exchange, instance string) {
|
||||
if instance == "" {
|
||||
return
|
||||
}
|
||||
x.ModifyResponse = func(resp *http.Response, body []byte) ([]byte, error) {
|
||||
var info map[string]json.RawMessage
|
||||
if resp.StatusCode != http.StatusOK || json.Unmarshal(body, &info) != nil {
|
||||
return body, nil
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
if !r.decorated(ctx, []string{instance})[instance] {
|
||||
return body, nil
|
||||
}
|
||||
info["t5de_t5"] = json.RawMessage("1")
|
||||
return json.Marshal(info)
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,8 @@ func (h *fakeHub) Call(ctx context.Context, method, path string, body, out any)
|
||||
"scene": {"contents": [[1, 47477426, "37207-46"], [2, "coatcoat22", "37-11"], [3, "notherenn2", "37-12"]], "hash": "h1"}}`), out)
|
||||
case method == "GET" && strings.HasPrefix(path, "/api/v1/decorations?instances="):
|
||||
return json.Unmarshal([]byte(`{"rooms": [{"instance": "1-644", "contents": [[1, "coatcoat22", "x"]]}, {"instance": "2-17", "contents": []}]}`), out)
|
||||
case method == "GET" && path == "/api/v1/hosted-rooms/3/participants":
|
||||
return json.Unmarshal([]byte(`{"participants": [{"imvu_id": 88, "seat": 1, "name": "Erin<"}]}`), out)
|
||||
case method == "GET" && path == "/api/v1/hosted-rooms":
|
||||
return json.Unmarshal([]byte(`{"rooms": [{"n": 3, "name": "Lounge", "ownerImvuId": 88, "ownerName": "Erin", "maxUsers": 10, "occupancy": 2}]}`), out)
|
||||
case method == "PUT" && path == "/api/v1/hosted-rooms/3/scene":
|
||||
@@ -265,3 +267,45 @@ func TestRoomList(t *testing.T) {
|
||||
t.Fatalf("page 2: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadgesAndRoomInfo(t *testing.T) {
|
||||
r, _, _ := setup()
|
||||
do := func(raw string, body []byte, modified []byte) []byte {
|
||||
t.Helper()
|
||||
u, _ := url.Parse(raw)
|
||||
req, _ := http.NewRequest("POST", u.String(), bytes.NewReader(body))
|
||||
x := &proxy.Exchange{URL: u, Request: req, Body: body}
|
||||
r.Intercept(x)
|
||||
if x.Response != nil {
|
||||
b, _ := io.ReadAll(x.Response.Body)
|
||||
return b
|
||||
}
|
||||
if x.ModifyResponse == nil {
|
||||
t.Fatalf("%s: left alone", raw)
|
||||
}
|
||||
out, _ := x.ModifyResponse(&http.Response{StatusCode: 200}, modified)
|
||||
return out
|
||||
}
|
||||
// A decorated IMVU room's card gets t5de_t5.
|
||||
card := (&xmlrpc.Call{Method: "test.getRoomCardInfo", Params: []xmlrpc.Value{xmlrpc.Struct{{Name: "room_instance_id", Value: "1-644"}}}}).Marshal()
|
||||
answer := (&xmlrpc.Response{Params: []xmlrpc.Value{xmlrpc.Struct{{Name: "name", Value: "A"}}}}).Marshal()
|
||||
if out := do("https://client-dynamic.imvu.com/api/xmlrpc/client.php", card, answer); !strings.Contains(string(out), "t5de_t5") {
|
||||
t.Fatalf("card %s", out)
|
||||
}
|
||||
// The Manage tab's own decorated rooms get it too.
|
||||
managed := []byte(`{"result": {"limit": 1, "rooms": [{"room_instance_id": "1-644"}, {"room_instance_id": "2-17"}]}}`)
|
||||
out := do("https://client-dynamic.imvu.com/api/room_management_info.php", nil, managed)
|
||||
if !strings.Contains(string(out), `{"room_instance_id":"1-644","t5de_t5":1}`) || strings.Count(string(out), "t5de_t5") != 1 {
|
||||
t.Fatalf("managed %s", out)
|
||||
}
|
||||
// A decorated IMVU room's room_info.php gets t5de_t5.
|
||||
if out := do("https://client-dynamic.imvu.com/api/rooms/room_info.php?room_id=1-644", nil, []byte(`{"participants": []}`)); !strings.Contains(string(out), `"t5de_t5":1`) {
|
||||
t.Fatalf("room info %s", out)
|
||||
}
|
||||
// room_info.php for a hosted room: its people, from the hub.
|
||||
out = do("https://client-dynamic.imvu.com/api/rooms/room_info.php?room_id=88-2100000003", nil, nil)
|
||||
if !strings.Contains(string(out), `"avatar_name":"Erin"`) || !strings.Contains(string(out), `"customers_id":"88"`) ||
|
||||
!strings.Contains(string(out), `"max_users":10`) || !strings.Contains(string(out), `"t5de_t5":1`) {
|
||||
t.Fatalf("room info %s", out)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user