Files
t5de-hub/docs/imports.md
T
dhkatzandClaude Opus 5.5 76497c9b41
Pull request / title (pull_request) Successful in 1s
CI / test (pull_request) Successful in 58s
CI / image (apps/api/Dockerfile, t5de-hub-api) (pull_request) Skipped
CI / image (apps/web/Dockerfile, t5de-hub-web) (pull_request) Skipped
docs: permissions, console and imported IMVU products
Design for managing products, catalogs and importers: permissions as
resource:action strings bundled into roles at a scope, one console scoped
by permission, a tagged audit log, cases, notifications, API tokens and
charts (console.md); catalogs importing IMVU products with their
creators' grants (imports.md); and the contract changes (products.md).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 22:56:15 -08:00

168 lines
11 KiB
Markdown

# Imported IMVU products
Creators who sell on IMVU can let T5DE users wear their IMVU products for free: a catalog imports the products from
IMVU, hosts them as ordinary T5 products, and serves them to its members. Decided 2026-11-04 (the user's
discussion). The contract with catalogs and proxies is in `products.md`; permissions and the console are in
`console.md`.
## Why the catalog imports
- **Responsibility stays with catalogs**, as for uploads: the catalog that imports a product stores and serves its
files, and its takedown contact answers for them. End users only download from catalogs, as for any T5 product.
- **Nothing changes for proxies**: an imported product is a hub product with revisions. No aliasing of IMVU IDs, no
re-signed IMVU requests.
- **Only consenting creators**: nothing is imported without a grant from the creator (or one an admin records on
their behalf, with evidence).
## Words
- **Grant**: a creator's consent for one catalog to import their products: the whole shop, or listed products.
- **Importer** (`t5de-importer`): a program that fetches products from IMVU and uploads them to its catalog. Run by
the catalog's operator beside an ordinary `t5de-catalog`. T5DE runs one for its own import catalog; any catalog
may enable imports.
- **Import job**: one product to import or re-check, queued at the hub.
- **Request**: someone asking for a creator's product, or whole shop, to be on T5DE.
- **Unclaimed creator**: a placeholder creator for IMVU creators without a hub account, whose consent an admin
recorded. Claimed when they join and link that IMVU account.
## What's imported
- **The product's own files**: its CFL's entries, with the 16x16 placeholder textures replaced by the full-size
originals its `_contents.json` lists (fetched from IMVU's CDN beside the CFL), and without `_contents.json` (packs
don't use it). Its `index.xml` keeps its `__DATAIMPORT` of the parent.
- **The parent stays on IMVU** (**Decided**, for now): the revision's `parent` is the IMVU product it derives from,
as for uploaded products, and the client loads it from IMVU as it does for anyone's outfit. Option B
(**Decided**): any product the creator made may be imported, whatever it derives from; deriving from a derivable
product is what IMVU's derive permission allows. If keeping the parent on IMVU becomes a problem, imports can be
flattened later (the whole chain in one product, parent 0).
- **Info**: name and rating from IMVU's public API (`api.imvu.com/product/product-<id>`: `product_name`, `rating`,
`creator_cid`), the higher of IMVU's rating and the files' `ProductRating`; a description saying it's imported
with the creator's permission, linking its IMVU page. Creators edit it afterwards like any product.
- **Revisions**: each IMVU revision imported becomes a hub revision ("Imported from IMVU revision 12"). Revision
history and staying on a revision work as for any product.
- One hub product per IMVU product (`product.imvu_product`, unique). Two catalogs importing the same product mirror
the same revisions.
## Grants
```
import_grant(id, catalog, creator person, imvu_id, scope ("shop" | "products"), products bigint[], exclude bigint[],
listed, consent_version, consented_at, consent_kind ("verified" | "recorded"), evidence, recorded_by,
proof_checked_at, accepted_at, accepted_by, withdrawn_at, status ("pending" | "active" | "suspended" | "withdrawn"))
```
- **Made by the creator**: on the hub website, "Bring my IMVU shop": choose one of their linked IMVU accounts, a
catalog with imports on, the scope (whole shop with exclusions, or listed products), listed or unlisted, and agree
to the consent text. The text is versioned; the version and time are stored.
- **Accepted by the catalog**: the catalog's team (`import:accept`) accepts or declines it, so nobody pushes imports
onto a catalog without its operator agreeing. Until then it's `pending`.
- **Recorded by an admin** (`import:grant-on-behalf`, hub-wide; for creators who won't make an account): the IMVU
creator (ID or name), scope, catalog, and **evidence** (required: how they agreed, e.g. "Discord DM, 2026-11-04",
optionally a screenshot). Optional **proof without an account**: the console gives a code to send the creator;
they put it in their IMVU tagline, and the hub checks it as for linking. Shown as "consent verified" or "consent
recorded by admin". The products belong to an unclaimed creator.
- **Scope "shop"**: any product whose `creator_cid` is the grant's IMVU account, including new ones, except
exclusions. **Scope "products"**: only those listed; the creator can add more (requests show as suggestions).
- **Changes**: the creator (or an admin, for recorded grants) changes the scope, list and exclusions any time.
Removing a product from a grant withdraws it (below).
- **Ending**: the creator withdraws the grant, or the catalog's team ends it, or the IMVU account's link moves to
another person (the grant is `suspended` until the new owner consents again), or the account is unlinked
(`suspended`). Admins can withdraw any grant.
### Unclaimed creators and claiming
An unclaimed creator is a hub person without credentials (can't sign in), named after the IMVU account ("Bar, on
IMVU"), marked unclaimed. When someone links that IMVU account, the hub offers them the claim: the products, grants,
requests and stats move to them, and the placeholder is deleted. Until then, withdrawal goes through admins
(contacting T5DE, or the catalog's takedown contact).
## Requests
```
import_request(person, imvu_creator, imvu_product (0: the whole shop), created_at) -- unique per person and target
```
- Anyone signed in can request a product (by IMVU ID or from its shop page in the client) or a creator's whole shop.
At most 50 a day per person; one per person and target, so counts can't be inflated.
- The hub looks up the product's creator (public API, cached in `imvu_product_info`).
- **Covered by an active grant**: queued for import at once; the person gets the product in their library when it's
done (pending until then; nothing on screen waits for it).
- **Not covered, grant by products**: a suggestion for the creator ("12 people asked for X"), added with one click.
- **No grant**: counted. The creator sees the demand when they bring their shop; the hub's creator page for a shop
that hasn't joined can show "requested by 47 people". The hub never contacts creators through IMVU.
- Admins see request counts per IMVU creator in the console (whom it's worth asking).
## Import jobs
```
import_job(id, catalog, grant, imvu_product, kind ("import" | "recheck"), status ("queued" | "claimed" | "done" | "failed"),
imvu_revision, reason, attempts, claimed_at, next_at, created_at, updated_at)
```
- **Queued by the hub**: a request covered by a grant, a product added to a grant, a "shop" grant accepted (the
products people requested so far, and all the shop's products if a way to list them is found), and a daily
**recheck** of each imported product.
- **The hub checks IMVU's public API** before queuing and at each recheck (rate-limited, as for parents): the
product exists, its creator is the grant's account, its rating. Gone from IMVU: withdrawn. Rating changed to AP:
adult at once. Retired (`is_purchasable` false): kept unless the creator excludes it.
- **The importer claims jobs**, fetches, converts, uploads with a `t5de-import` token (products.md), and reports the
outcome. Rechecks probe IMVU's CDN for revisions after the last imported one; a new one is imported.
- Failures retry with backoff (1 min, 10 min, 1 h, then daily; failed for good after 5); the reason is shown in the
console. The catalog's team can retry or re-import one product, and pause the importer (`import:pause`): the hub
hands out no jobs while paused.
## Withdrawal
A withdrawn imported product (grant withdrawn or narrowed, gone from IMVU, account link moved) is removed as
moderation removes products: it leaves libraries, outfits and rooms; its page says it was withdrawn; the catalog
deletes its files (the catalog changes feed, products.md). Unlike deleting an uploaded product, people who had it
don't keep it: the consent is gone.
## `t5de-importer`
A Go program (its own repository) beside a `t5de-catalog`; it stores nothing lasting but its working files.
- Configuration: `HUB_URL`, `CATALOG_ID`, `IMPORTER_KEY` (from enabling imports on the catalog), `IMPORTER_RATE`
(requests a second to IMVU, default 2), optionally an IMVU account for exact revisions (`IMVU_USERNAME`,
`IMVU_PASSWORD`; the operator's choice and risk; the hub never holds IMVU credentials).
- Revisions: without an IMVU account, the CDN is probed (`HEAD productdata/<id>/<n>`) once per product; afterwards a
recheck tries the next few numbers. With one, `authorizeProducts` gives exact revisions.
- Reads CFLs with t5de-api's `cfl` package; the conversion follows `@imvu/chkn` (dhkatz/imvu.js) as a reference.
- Validates with t5de-api's `pack` rules before uploading; a product breaking them fails with the reason. (The spike
checks whether typical IMVU products fit; imports may need their own limits.)
## Hub routes
| | |
| --- | --- |
| `GET /api/v1/imports/catalogs` | approved catalogs with imports on, accepting grants |
| `POST /api/v1/imports/grants` | `{catalog, imvuId, scope, products?, exclude?, listed, consentVersion}` → 201 `{grant}` (pending). With `onBehalf: {imvuId, evidence, proof?}` instead of a linked `imvuId`: an admin-recorded grant. |
| `GET /api/v1/imports/grants` | the person's grants; with `catalog=`, a catalog's (its team) |
| `PATCH /api/v1/imports/grants/:id` | `{scope?, products?, exclude?, listed?}` |
| `POST /api/v1/imports/grants/:id/accept`, `.../decline` | the catalog's team |
| `DELETE /api/v1/imports/grants/:id` | withdraw |
| `POST /api/v1/imports/proofs` | (admins) `{imvuId}` → `{proof, code}`; `POST .../proofs/:id/check` |
| `POST /api/v1/imports/requests` | `{imvuProduct}` or `{imvuCreator}` → `{status: "queued" \| "suggested" \| "counted", product?}` |
| `GET /api/v1/imports/requests?imvuCreator=` | counts (the creator, admins) |
| `GET /api/v1/imports/available?imvuProducts=a,b` | for the proxy's shop button: which can the person get (and the hub product ID), already have, or request |
| `GET /api/v1/imports/jobs?catalog=&status=` | the catalog's team: the queue; `POST .../jobs/:id/retry` |
| `POST /api/v1/imports/claim` | `{imvuId}`: claim an unclaimed creator's products for a linked account |
For importers (`Authorization: Importer <catalog ID>:<key>`) in products.md.
## Spike (before building the importer)
1. Import one product by hand as a local pack (full-size textures, no `_contents.json`, parent kept). Does the
client wear it as a T5 product, with the parent loading from IMVU? (Never committed: it's IMVU's files.)
2. Do typical IMVU products fit the `pack` limits?
3. How many HEAD requests does probing take for real shops; does the CDN limit them?
4. Is there a public way to list a creator's products (for importing a whole shop at once)?
## Later
- Flattened imports (parent included), if keeping the parent on IMVU causes problems.
- A dry run: preview an import (and anything over the pack limits) before it's published.
- "Get on T5DE" on IMVU's shop and product pages in the client (a t5de patch, using
`/api/v1/imports/available`).
- Creators who don't want their products as parents of imports (an opt-out hiding imports that derive from theirs).