Manage T5DE rooms from the client; the AP+ badge on avatar cards
CI / build (push) Successful in 28s
CI / build (push) Successful in 28s
Chat Rooms > Manage (the t5de-room-manage patch) lists the person's T5DE rooms (room_management_info.php?t5de=1) and saves them from IMVU's room dialog (room_management.php: create with t5de=1, edit and delete naming a hosted room) as hub calls: the room, its moderators by name, and a picture from the person's IMVU gallery, uploaded to the hub. The hub's refusals show in the dialog (hub.Error keeps their message). Avatar cards get show_ap_plus again, which IMVU stopped sending, from the person's public IMVU profile (is_ap_plus). Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
6aac769246
commit
8daf91ede3
7 files changed
+608
-2
No files matched your search
@@ -0,0 +1,112 @@
|
||||
// Package cards fixes what IMVU's servers no longer send the client's avatar cards: the AP+ badge. The card shows it
|
||||
// when avatarcard.php's answer has show_ap_plus, which IMVU stopped sending; whether someone has AP+ is public on
|
||||
// their IMVU profile (api.imvu.com/user/user-<id>, is_ap_plus), as the friends list and Find People still show.
|
||||
package cards
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.t5de.dev/t5de/t5de-proxy/internal/proxy"
|
||||
)
|
||||
|
||||
// keep is how long an account's AP+ is remembered.
|
||||
const keep = 10 * time.Minute
|
||||
|
||||
// Cards adds show_ap_plus to avatar cards of people with AP+.
|
||||
type Cards struct {
|
||||
// Client fetches IMVU's public profiles (nil: http.DefaultClient).
|
||||
Client *http.Client
|
||||
// Base is IMVU's API ("" for https://api.imvu.com).
|
||||
Base string
|
||||
|
||||
mu sync.Mutex
|
||||
known map[int64]apPlus
|
||||
}
|
||||
|
||||
type apPlus struct {
|
||||
has bool
|
||||
at time.Time
|
||||
}
|
||||
|
||||
func (c *Cards) Intercept(x *proxy.Exchange) error {
|
||||
if !strings.EqualFold(x.URL.Hostname(), "client-dynamic.imvu.com") || x.URL.Path != "/api/avatarcard.php" {
|
||||
return nil
|
||||
}
|
||||
cid, err := strconv.ParseInt(x.URL.Query().Get("cid"), 10, 64)
|
||||
if err != nil || cid <= 0 {
|
||||
return nil
|
||||
}
|
||||
x.ModifyResponse = func(resp *http.Response, body []byte) ([]byte, error) {
|
||||
var card map[string]json.RawMessage
|
||||
if resp.StatusCode != http.StatusOK || json.Unmarshal(body, &card) != nil || set(card["show_ap_plus"]) {
|
||||
return body, nil
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
if !c.has(ctx, cid) {
|
||||
return body, nil
|
||||
}
|
||||
card["show_ap_plus"] = json.RawMessage("1")
|
||||
return json.Marshal(card)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// set reports whether a JSON value counts as set in the card's JavaScript (truthy).
|
||||
func set(v json.RawMessage) bool {
|
||||
s := strings.TrimSpace(string(v))
|
||||
return s != "" && s != "null" && s != "false" && s != "0" && s != `""` && s != `"0"`
|
||||
}
|
||||
|
||||
// has reports whether an account has AP+ (false if IMVU can't say).
|
||||
func (c *Cards) has(ctx context.Context, id int64) bool {
|
||||
c.mu.Lock()
|
||||
k, ok := c.known[id]
|
||||
c.mu.Unlock()
|
||||
if ok && time.Since(k.at) < keep {
|
||||
return k.has
|
||||
}
|
||||
base := c.Base
|
||||
if base == "" {
|
||||
base = "https://api.imvu.com"
|
||||
}
|
||||
u := fmt.Sprintf("%s/user/user-%d", base, id)
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", u, nil)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
client := c.Client
|
||||
if client == nil {
|
||||
client = http.DefaultClient
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return k.has
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var profile struct {
|
||||
Denormalized map[string]struct {
|
||||
Data struct {
|
||||
IsAPPlus bool `json:"is_ap_plus"`
|
||||
} `json:"data"`
|
||||
} `json:"denormalized"`
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK || json.NewDecoder(resp.Body).Decode(&profile) != nil {
|
||||
return k.has
|
||||
}
|
||||
has := profile.Denormalized[fmt.Sprintf("https://api.imvu.com/user/user-%d", id)].Data.IsAPPlus
|
||||
c.mu.Lock()
|
||||
if c.known == nil {
|
||||
c.known = map[int64]apPlus{}
|
||||
}
|
||||
c.known[id] = apPlus{has: has, at: time.Now()}
|
||||
c.mu.Unlock()
|
||||
return has
|
||||
}
|
||||
Reference in new issue
Block a user