egs-api 0.10.2

Interface to the Epic Games API
Documentation
<p align="center">
<img alt="GitHub" src="https://img.shields.io/github/license/AchetaGames/egs-api-rs">
<a href="https://crates.io/crates/egs-api">
    <img alt="Crates.io" src="https://img.shields.io/crates/v/egs-api"></a>
<a href="https://docs.rs/egs-api/latest/egs_api/">
    <img alt="docs.rs" src="https://img.shields.io/docsrs/egs-api"></a>
<a href="https://discord.gg/C2S8eGfZ6n">
    <img alt="Discord" src="https://img.shields.io/discord/332629362094374913"></a>

</p>

# egs-api

Async Rust client for the Epic Games Store API. Handles authentication, asset
management, download manifest parsing (binary + JSON), and
[Fab](https://www.fab.com/) marketplace integration.

Built on `reqwest` / `tokio`.

## Features

- **Authentication** — OAuth login via authorization code, exchange token, or
  refresh token. Session resume and invalidation.
- **Assets** — List owned assets, fetch catalog metadata (including DLC trees),
  retrieve asset manifests with CDN download URLs.
- **Download Manifests** — Parse Epic's binary and JSON manifest formats.
  Exposes file lists, chunk hashes, and custom fields needed to reconstruct
  downloads.
- **Fab Marketplace** — List Fab library items, fetch Fab asset manifests with
  signed distribution points, and download Fab manifests.
- **Account** — Account details, bulk account ID lookup, friends list
  (including pending requests).
- **Entitlements** — Query all user entitlements (games, DLC, subscriptions).
- **Library** — Paginated library listing with optional metadata.
- **Tokens** — Game exchange tokens and per-asset ownership tokens (JWT).
- **Cloud Saves** — List, query, and delete cloud save files.
- **Uplay Integration** — Query and redeem Ubisoft activation codes via
  Epic's GraphQL store API.

## Quick Start

Add to `Cargo.toml`:

```toml
[dependencies]
egs-api = "0.9"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
```

```rust
use egs_api::EpicGames;

#[tokio::main]
async fn main() {
    let mut egs = EpicGames::new();

    // Authenticate with an authorization code obtained from:
    // https://www.epicgames.com/id/api/redirect?clientId=34a02cf8f4414e29b15921876da36f9a&responseType=code
    let code = "your_authorization_code".to_string();
    if egs.auth_code(None, Some(code)).await {
        println!("Logged in as {}", egs.user_details().display_name.unwrap_or_default());
    }

    // List all owned assets
    let assets = egs.list_assets(None, None).await;
    println!("You own {} assets", assets.len());

    // Get detailed info for the first asset
    if let Some(asset) = assets.first() {
        if let Some(info) = egs.asset_info(asset).await {
            println!("{}: {}", info.id, info.title.unwrap_or_default());
        }
    }
}
```

## Authentication Flow

Epic uses OAuth2 with a launcher client ID. The typical flow is:

1. Open the authorization URL in a browser — the user logs in and is redirected
   to a JSON page containing an `authorizationCode`.
2. Pass that code to `egs.auth_code(None, Some(code))`.
3. On success, `egs.user_details()` contains the session tokens.

To persist the session across runs, serialize `egs.user_details()` (it
implements `Serialize` / `Deserialize`) and restore it later with
`egs.set_user_details(saved)` followed by `egs.login()` which will use the
refresh token to re-authenticate.

**Authorization URL:**
```
https://www.epicgames.com/id/login?redirectUrl=https%3A%2F%2Fwww.epicgames.com%2Fid%2Fapi%2Fredirect%3FclientId%3D34a02cf8f4414e29b15921876da36f9a%26responseType%3Dcode
```

See the [`auth`](examples/auth.rs) example for a complete interactive flow with
token persistence.

## Examples

The crate ships with examples covering every endpoint. Run them with:

```bash
# First: authenticate and save a token
cargo run --example auth

# Then run any of these (they reuse the saved token):
cargo run --example account              # Account details, ID lookup, friends, external auths, SSO
cargo run --example entitlements         # List all entitlements
cargo run --example library              # Paginated library listing
cargo run --example assets               # Full pipeline: list → info → manifest → download manifest
cargo run --example game_token           # Exchange code + ownership token
cargo run --example fab                  # Fab library → asset manifest → download manifest
cargo run --example catalog              # Catalog items, offers, bulk lookup
cargo run --example commerce             # Currencies, prices, billing, quick purchase
cargo run --example status               # Service status (lightswitch API)
cargo run --example presence             # Update online presence
cargo run --example client_credentials   # App-level auth + library state tokens
cargo run --example cloud_saves          # Cloud save file listing + management
cargo run --example uplay                # Ubisoft activation code queries
```

## API Overview

The public API is the [`EpicGames`](https://docs.rs/egs-api/latest/egs_api/struct.EpicGames.html)
struct. It wraps an internal HTTP client with cookie storage and bearer token
management. Most methods return `Option<T>` or `Vec<T>` (swallowing transport
errors); Fab methods return `Result<T, EpicAPIError>` to expose timeout/error
distinctions.

### Authentication

| Method | Description |
|--------|-------------|
| `auth_code(exchange_token, authorization_code)` | Start a new session |
| `auth_sid(sid)` | Authenticate via SID cookie (web-based exchange code flow) |
| `auth_client_credentials()` | App-level auth (no user context) |
| `login()` | Resume session using saved refresh token |
| `logout()` | Invalidate current session |
| `is_logged_in()` | Check if access token is still valid (>600s remaining) |
| `user_details()` / `set_user_details(data)` | Get/set session state for persistence |

### Epic Games Store

| Method | Description |
|--------|-------------|
| `list_assets(platform, label)` | List all owned assets (default: Windows/Live) |
| `asset_info(asset)` | Catalog metadata for an asset (includes DLC list) |
| `asset_manifest(platform, label, namespace, item_id, app)` | CDN manifest with download URLs |
| `asset_download_manifests(manifest)` | Parse binary/JSON download manifests from all CDN mirrors |
| `catalog_items(namespace, start, count)` | Paginated catalog items for a namespace |
| `catalog_offers(namespace, start, count)` | Paginated catalog offers for a namespace |
| `bulk_catalog_items(items)` | Bulk fetch catalog items across namespaces |
| `currencies(start, count)` | Available currencies with symbols and decimals |
| `game_token()` | Short-lived exchange code for game launches |
| `ownership_token(asset)` | JWT proving asset ownership |
| `library_state_token_status(token_id)` | Check library state token validity |
| `artifact_service_ticket(platform, label, namespace, item_id, app)` | Artifact service download ticket |
| `game_manifest_by_ticket(platform, label, namespace, item_id, app, ticket)` | Game manifest via artifact service ticket |
| `launcher_manifests(platform, label, namespace, item_id, app)` | Launcher asset manifests |
| `delta_manifest(manifest, old_build_id, new_build_id)` | Delta/patch manifest between two builds |

### Cloud Saves

| Method | Description |
|--------|-------------|
| `cloud_save_list()` | List all cloud save files for the current user |
| `cloud_save_query(filename)` | Query metadata for a specific cloud save file |
| `cloud_save_delete(filename)` | Delete a cloud save file |

### Uplay / Store Integration

| Method | Description |
|--------|-------------|
| `store_get_uplay_codes(namespace, offer_id)` | Query Ubisoft activation codes for a game |
| `store_claim_uplay_code(namespace, offer_id)` | Claim a Ubisoft activation code |
| `store_redeem_uplay_codes(uplay_codes)` | Redeem Ubisoft activation codes |

### Fab Marketplace

| Method | Description |
|--------|-------------|
| `fab_library_items(account_id)` | List all Fab library items (paginated) |
| `fab_asset_manifest(artifact_id, namespace, asset_id, platform)` | Signed download info with distribution points |
| `fab_download_manifest(download_info, distribution_point_url)` | Parse download manifest from a specific CDN |
| `fab_file_download_info(listing_id, format_id, file_id)` | Download info for a specific Fab file |

### Account & Social

| Method | Description |
|--------|-------------|
| `account_details()` | Email, display name, country, 2FA status |
| `account_ids_details(ids)` | Bulk lookup of account IDs to display names |
| `account_friends(include_pending)` | Friends list with pending request status |
| `external_auths(account_id)` | Linked platform accounts (Steam, PSN, Xbox, etc.) |
| `sso_domains()` | SSO domain list for cookie sharing |
| `user_entitlements()` | All entitlements (games, DLC, subscriptions) |
| `library_items(include_metadata)` | Library records with optional metadata |

### Commerce

| Method | Description |
|--------|-------------|
| `offer_prices(namespace, offer_ids, country)` | Offer pricing with formatted strings |
| `quick_purchase(namespace, offer_id)` | Quick purchase (free game claims) |
| `billing_account()` | Default billing account and country |

### Status & Presence

| Method | Description |
|--------|-------------|
| `service_status(service_id)` | Service operational status (lightswitch API) |
| `update_presence(session_id, body)` | Update user online presence |

## Download Manifest Format

The [`DownloadManifest`](https://docs.rs/egs-api/latest/egs_api/api/types/download_manifest/struct.DownloadManifest.html)
struct handles Epic's binary manifest format (with JSON fallback). The binary
format is little-endian, optionally zlib-compressed:

```
Header (41 bytes):
  magic(u32) → header_size(u32) → size_uncompressed(u32) →
  size_compressed(u32) → sha_hash(20 bytes) → compressed(u8) → version(u32)

Body (zlib-compressed):
  Meta:   feature_level, is_file_data, app_id, app_name, build_version, launch_exe, ...
  Chunks: guid(16 bytes) × N, hash(u64) × N, sha(20 bytes) × N, group(u8) × N, ...
  Files:  filename × N, symlink_target × N, sha_hash(20 bytes) × N, install_tags × N, chunk_parts × N
  Custom: key-value pairs (e.g., BaseUrl, CatalogItemId, BuildLabel)
```

Use `DownloadManifest::parse(data)` to parse either format. Access file lists
via `file_manifest_list`, chunk info via `chunk_hash_list`, and custom fields
via `custom_field(key)`.

## Architecture

```
EpicGames (public facade, src/lib.rs)
  └── EpicAPI (internal, src/api/mod.rs)
        ├── login.rs    — OAuth: start, resume, invalidate
        ├── egs.rs      — Assets, manifests, library, cloud saves, tokens
        ├── account.rs  — Account details, friends, entitlements
        ├── fab.rs      — Fab marketplace integration
        └── store.rs    — Uplay/Ubisoft code redemption (GraphQL)
```

`EpicGames` is the consumer-facing struct. It delegates to `EpicAPI` which
holds the `reqwest::Client` (with cookie store) and `UserData` (session state).
API methods are split across files via `impl EpicAPI` blocks.

## License

MIT