Recently added, shop sorting, and outfit cards with T5 products
CI / build (push) Successful in 34s

- The inventory's Recently Added puts T5 products added to the library in the last 30 days first.
- The shop's T5 listing follows its sort menu: Best Match ranks by name, Newest by upload date
  (oldest first ascending); ratings and prices (T5 products have neither) sort by newest.
- Outfit cards: outfit_products.php gets the outfit's community products added (not for sale),
  find_bundle.php is asked about IMVU's products only, and removing one in the card (remove_pids)
  keeps it from IMVU and forgets it in the outfit.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
dhkatzandClaude Opus 5.5 committed 2026-10-30 02:45:02 -07:00
1 parent af9158513b
commit 171e8173af
11 files changed
+463 -7

No files matched your search

+78
View File
@@ -0,0 +1,78 @@
package products
import (
"encoding/json"
"net/http"
"sort"
"strconv"
"time"
"git.t5de.dev/t5de/t5de-proxy/internal/hub"
"git.t5de.dev/t5de/t5de-proxy/internal/proxy"
)
// The inventory's Recently Added (api/recently_added_inventory.php?type=clothing|furni|scenes) is a list of product
// IDs from IMVU; T5 products the person added to their library lately go first, newest first.
// recentFor is how long a T5 product counts as recently added.
const recentFor = 30 * 24 * time.Hour
// IMVU's categories for furniture and rooms (the inventory's FURNI_CATEGORIES and ROOM_CATEGORIES, and rooms' 107).
var (
furniCategories = map[int64]bool{1027: true, 110: true}
roomCategories = map[int64]bool{107: true, 109: true, 366: true, 626: true, 2386: true}
)
// inventoryType is which Recently Added list a product belongs in: clothing (anything worn), furni or scenes.
func inventoryType(categories []hub.Category) string {
for _, c := range categories {
if furniCategories[c.ID] {
return "furni"
}
}
for _, c := range categories {
if roomCategories[c.ID] {
return "scenes"
}
}
return "clothing"
}
func (p *Products) interceptRecent(x *proxy.Exchange) error {
if p.Library == nil {
return nil
}
kind := x.URL.Query().Get("type")
x.ModifyResponse = func(resp *http.Response, body []byte) ([]byte, error) {
var ids []json.RawMessage
if resp.StatusCode != http.StatusOK || json.Unmarshal(body, &ids) != nil {
return body, nil
}
ours := p.recentLocals(kind, time.Now())
if len(ours) == 0 {
return body, nil
}
out := make([]json.RawMessage, 0, len(ours)+len(ids))
for _, local := range ours {
out = append(out, json.RawMessage(strconv.FormatInt(local, 10)))
}
return json.Marshal(append(out, ids...))
}
return nil
}
// recentLocals is the local IDs of T5 products of that kind added to the library within recentFor, newest first.
func (p *Products) recentLocals(kind string, now time.Time) []int64 {
lib := p.Library()
sort.SliceStable(lib, func(i, j int) bool { return lib[i].AddedAt.After(lib[j].AddedAt) })
var out []int64
for _, e := range lib {
if now.Sub(e.AddedAt) > recentFor || inventoryType(e.Product.Categories) != kind {
continue
}
if s := p.byProductID(e.Product.ID); s != nil && p.inHubDir(e.Product.ID) {
out = append(out, s.local)
}
}
return out
}