# @chiraitori/hoyolab-core

A lightweight npm package for HoYoLab automation, including daily check-ins and code redemption for HoYoverse games.

## Features

- 🎯 **Daily Check-ins** - Automated daily rewards collection
- 🎮 **Multi-Game Support** - Genshin Impact, Honkai: Star Rail, Zenless Zone Zero
- 🔑 **Code Redemption** - Automatic redemption of promotional codes
- 🔄 **Flexible Code Sources** - Use built-in APIs or your own code sources (Discord bots, custom APIs)
- 🌍 **Region Support** - Game-specific region mapping (NA, EU, SEA, TW/HK/MO)
- 📦 **Zero Dependencies** - Pure Node.js implementation
- 🛡️ **Error Handling** - Robust error handling and retry mechanisms
- ⚡ **Bulk Operations** - Redeem multiple codes at once with rate limiting

## Installation

```bash
npm install @chiraitori/hoyolab-core
```

## Quick Start

```javascript
const { HoyoLabClient, Games } = require('@chiraitori/hoyolab-core');

// Initialize client with your cookie
const client = new HoyoLabClient({
  cookie: 'your_hoyolab_cookie_here'
});

// Daily check-in
const checkinResult = await client.dailyCheckIn(Games.GENSHIN_IMPACT);
console.log('Check-in result:', checkinResult);

// Redeem a single code
const redeemResult = await client.redeemCode(Games.GENSHIN_IMPACT, 'PROMOCODE123');
console.log('Redeem result:', redeemResult);

// Redeem multiple codes (great for Discord bots!)
const codes = ['CODE1', 'CODE2', 'CODE3'];
const results = await client.redeemMultipleCodes(Games.GENSHIN_IMPACT, codes);
console.log('Bulk redeem results:', results);

// Optional: Fetch codes from built-in API or your own source
const availableCodes = await client.fetchAvailableCodes(Games.GENSHIN_IMPACT);
// Or use your own API:
const customCodes = await client.fetchAvailableCodes(Games.GENSHIN_IMPACT, {
  source: 'https://my-discord-bot.com/api/codes'
});
```

## For Discord Bot Developers

Perfect for Discord bots and custom applications:

```javascript
// Discord command example
async function handleRedeemCommand(interaction, game, codes) {
  const client = new HoyoLabClient({ cookie: userCookie });
  
  const results = await client.redeemMultipleCodes(game, codes, null, {
    delay: 1500,        // Delay between redemptions
    stopOnError: false  // Continue even if some codes fail
  });
  
  const successful = results.filter(r => r.success).length;
  return `✅ Redeemed ${successful}/${codes.length} codes successfully!`;
}
```

## API Reference

### HoyoLabClient

#### Constructor

```javascript
new HoyoLabClient(options)
```

- `options.cookie` - Your HoYoLab cookie string
- `options.userAgent` - Custom User-Agent (optional)

#### Methods

##### `dailyCheckIn(game, uid?)`

Performs daily check-in for the specified game.

- `game` - Game identifier from `Games` enum
- `uid` - Specific UID (optional, uses first account if not specified)

Returns: `Promise<CheckInResult>`

##### `redeemCode(game, code, uid?)`

Redeems a promotional code for the specified game.

- `game` - Game identifier from `Games` enum  
- `code` - Promotional code string
- `uid` - Specific UID (optional)

Returns: `Promise<RedeemResult>`

##### `redeemMultipleCodes(game, codes, uid?, options?)`

Redeems multiple promotional codes at once. Perfect for Discord bots!

**⚠️ Important: HoYoLab API has a 6-second rate limit between code redemptions. Using shorter delays may result in cooldown errors.**

- `game` - Game identifier from `Games` enum
- `codes` - Array of promotional code strings
- `uid` - Specific UID (optional)
- `options` - Redemption options:
  - `delay` - Delay between redemptions in ms (default: 6000 - HoYoLab's rate limit)
  - `stopOnError` - Stop on first error (default: false)
  - `autoRetryOnCooldown` - Automatically retry when cooldown detected (default: true)
  - `maxRetries` - Maximum retries per code (default: 3)

Returns: `Promise<BulkRedeemResult[]>`

```javascript
// Recommended: Use default 6-second delay
const results = await client.redeemMultipleCodes(Games.GENSHIN_IMPACT, codes);

// Aggressive timing with auto-retry (slower overall, but handles cooldowns)
const results = await client.redeemMultipleCodes(Games.GENSHIN_IMPACT, codes, uid, {
  delay: 1000,              // Fast attempts
  autoRetryOnCooldown: true, // Auto-retry on cooldown
  maxRetries: 2             // Limit retries
});
```

##### `redeemCodeOnly(game, code, uid?)`

Alias for `redeemCode()` - for developers who want to be explicit about not using built-in code fetching.

##### `fetchAvailableCodes(game, options?)`

Fetches currently available promotional codes from built-in API or custom source.

- `game` - Game identifier from `Games` enum
- `options` - Fetch options:
  - `source` - Custom API URL (optional, uses built-in if not provided)

Returns: `Promise<CodeInfo[]>`

**Note:** This method is optional. Many developers prefer to use their own code sources (Discord bots, databases, custom APIs) and directly call `redeemCode()` or `redeemMultipleCodes()`.

##### `getAccounts(game?)`

Gets account information.

- `game` - Game identifier (optional, returns all if not specified)

Returns: `Promise<AccountInfo[]>`

### Games Enum

```javascript
const { Games } = require('hoyolab-core');

Games.GENSHIN_IMPACT
Games.HONKAI_STAR_RAIL  
Games.ZENLESS_ZONE_ZERO
```

### Regions Enum

```javascript
const { Regions } = require('hoyolab-core');

Regions.NORTH_AMERICA  // NA
Regions.EUROPE         // EU  
Regions.ASIA           // SEA
Regions.TAIWAN_HK_MO   // TW/HK/MO
```

### Region Mapping

Each game uses different region codes. The package provides a helper function to map game-specific region codes to human-readable names:

```javascript
const { getRegionName, Games } = require('hoyolab-core');

// Genshin Impact regions
getRegionName(Games.GENSHIN_IMPACT, 'os_usa')   // 'NA'
getRegionName(Games.GENSHIN_IMPACT, 'os_euro')  // 'EU'
getRegionName(Games.GENSHIN_IMPACT, 'os_asia')  // 'SEA'
getRegionName(Games.GENSHIN_IMPACT, 'os_cht')   // 'TW/HK/MO'

// Honkai Star Rail regions  
getRegionName(Games.HONKAI_STAR_RAIL, 'prod_official_usa')  // 'NA'
getRegionName(Games.HONKAI_STAR_RAIL, 'prod_official_eur')  // 'EU'
getRegionName(Games.HONKAI_STAR_RAIL, 'prod_official_asia') // 'SEA'
getRegionName(Games.HONKAI_STAR_RAIL, 'prod_official_cht')  // 'TW/HK/MO'

// Zenless Zone Zero regions
getRegionName(Games.ZENLESS_ZONE_ZERO, 'prod_gf_us')  // 'NA'
getRegionName(Games.ZENLESS_ZONE_ZERO, 'prod_gf_eu')  // 'EU'
getRegionName(Games.ZENLESS_ZONE_ZERO, 'prod_gf_jp')  // 'SEA'
getRegionName(Games.ZENLESS_ZONE_ZERO, 'prod_gf_sg')  // 'TW/HK/MO'
```

## Cookie Setup

You need to obtain your HoYoLab cookie from your browser:

1. Go to [HoYoLab](https://www.hoyolab.com/) and log in
2. Open browser DevTools (F12)
3. Go to Application/Storage -> Cookies
4. Copy the cookie values for: `ltoken_v2`, `ltuid_v2`, `ltmid_v2`
5. For code redemption, also need: `cookie_token_v2`, `account_mid_v2`, `account_id_v2`

Format: `ltoken_v2=value; ltuid_v2=value; ltmid_v2=value; cookie_token_v2=value; account_mid_v2=value; account_id_v2=value`

## Error Handling

The package includes comprehensive error handling:

```javascript
try {
  const result = await client.dailyCheckIn(Games.GENSHIN_IMPACT);
} catch (error) {
  if (error.code === 'ALREADY_CHECKED_IN') {
    console.log('Already checked in today');
  } else if (error.code === 'INVALID_COOKIE') {
    console.log('Cookie expired or invalid');
  } else {
    console.error('Unexpected error:', error.message);
  }
}
```

## License

MIT
