Answer IMVU's product REST API for community products
CI / build (push) Successful in 27s

Trying a product on in the shop asks api.imvu.com/product/product-<id>/uml_products for related
products; the guard refused it for T5 products, and the client showed an error. Community products
now get an empty list (and not found for anything else) from the proxy.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
dhkatzandClaude Opus 5.5 committed 2026-10-30 01:26:04 -07:00
1 parent 2280ad6314
commit af9158513b
2 files changed
+30

No files matched your search

+24
View File
@@ -6,6 +6,7 @@ import (
"net/http"
"net/url"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
@@ -33,6 +34,14 @@ func (p *Products) Intercept(x *proxy.Exchange) error {
if host == "api.imvu.com" && participantPath.MatchString(path) {
return p.interceptParticipant(x)
}
if host == "api.imvu.com" {
if m := productPath.FindStringSubmatch(path); m != nil {
if id, ok := parseID(m[1]); ok && IsCommunity(id) {
x.Response = productAPIAnswer(path, m[2])
return nil
}
}
}
switch path {
case "/api/xmlrpc/chat.php":
return p.interceptChat(x)
@@ -59,6 +68,21 @@ func (p *Products) Intercept(x *proxy.Exchange) error {
return nil
}
// productPath is IMVU's REST API for a product (api.imvu.com/product/product-<id>[/<relation>]).
var productPath = regexp.MustCompile(`^/product/product-([0-9.]+)(/[a-z_]+)?$`)
// productAPIAnswer answers the REST API about a community product: no related products (uml_products, which the
// shop's recommender asks for when a product is tried on), and nothing else.
func productAPIAnswer(path, relation string) *http.Response {
id := "https://api.imvu.com" + path
if relation == "/uml_products" {
b, _ := json.Marshal(map[string]any{"status": "success", "id": id, "denormalized": map[string]any{
id: map[string]any{"data": map[string]any{"total_count": 0, "items": []any{}}, "relations": map[string]any{}}}})
return localResponse(200, "application/json", b)
}
return localResponse(404, "application/json", []byte(`{"status":"failure","error":"NOT_FOUND"}`))
}
func (p *Products) interceptXMLRPC(x *proxy.Exchange) error {
call, err := xmlrpc.ParseCall(x.Body)
if err != nil {
+6
View File
@@ -112,6 +112,12 @@ func TestShop(t *testing.T) {
}
h.do("POST", "https://secure.imvu.com/api/xmlrpc/client.php", call("test.avatarInfoForLogin2"), false, false)
// Trying one on asks IMVU's REST API for related products: answered here, with none.
status, b := h.do("GET", fmt.Sprintf("https://api.imvu.com/product/product-%d/uml_products?limit=6", coatLocal), nil, false, false)
if status != 200 || !strings.Contains(string(b), `"total_count":0`) {
t.Fatalf("uml_products: %d %s", status, b)
}
// Buying a T5 product adds it to the library; IMVU hears only of its own products.
// Product details: community ones answered here, IMVU asked only for its own.
info := func(pids ...any) []map[string]any {