# 📚 XPublishService API Reference

## Class: `App\Services\XPublishService`

### Constructor
```php
public function __construct()
```
Initializes the X API service with credentials from `config/services.x`.
- Auto-refreshes access token if refresh token is available
- Validates API credentials presence
- Throws exception if credentials missing

---

## Public Methods

### Tweet Posting

#### `postSimpleTweet(string $text): string`
Posts a simple text tweet to X.

**Parameters:**
- `$text` (string): Tweet text (max 280 chars)

**Returns:** 
- `string` - Tweet ID

**Example:**
```php
$tweetId = $xService->postSimpleTweet('Hello from Bokeplah! 🎥');
echo "Posted: https://x.com/i/web/status/{$tweetId}";
```

**Throws:**
- `Exception` - On API errors

---

#### `postTweet(string $text, string $mediaId = ''): string`
Posts a tweet with optional media to X.

**Parameters:**
- `$text` (string): Tweet text (max 280 chars)
- `$mediaId` (string, optional): Media ID from uploadMedia()

**Returns:**
- `string` - Tweet ID

**Example:**
```php
// With media
$mediaId = $xService->uploadMedia('https://play.bokeplah.me/storage/thumb.jpg');
$tweetId = $xService->postTweet('Check this out! 🔥', $mediaId);

// Without media (same as postSimpleTweet)
$tweetId = $xService->postTweet('Just text');
```

**Features:**
- Auto-retry on 401 (token refresh)
- Bearer token fallback
- Comprehensive error handling

**Throws:**
- `Exception` - On API errors

---

### Media Management

#### `uploadMedia(string $imageUrl): string`
Uploads an image to X as media.

**Parameters:**
- `$imageUrl` (string): URL or local path to image

**Returns:**
- `string` - Media ID for use in postTweet()

**Supported Formats:**
- JPEG, PNG, GIF, WebP
- Max size: 5MB

**Example:**
```php
// From URL
$mediaId = $xService->uploadMedia('https://example.com/image.jpg');

// From local storage
$mediaId = $xService->uploadMedia('https://play.bokeplah.me/storage/publish/1.jpg');
```

**Features:**
- Automatic format validation
- Size checking
- Local filesystem support for bokeplah.me URLs
- cURL download with SSL verification

**Throws:**
- `Exception` - On invalid format, size, or download errors

---

### Token Management

#### `refreshAccessTokenOAuth2(): bool`
Manually refreshes OAuth 2.0 access token using refresh token.

**Returns:**
- `bool` - True if refresh successful, false otherwise

**Example:**
```php
if ($xService->refreshAccessTokenOAuth2()) {
    Log::info('Token refreshed');
} else {
    Log::error('Token refresh failed');
}
```

**Features:**
- HTTP error detection
- Automatic .env update
- Detailed error logging
- Returns false on failure (doesn't throw)

---

#### `validateCredentials(): array`
Validates all X API credentials configuration.

**Returns:**
- `array` - Status with keys:
  - `valid` (bool): Are all credentials valid
  - `errors` (array): List of errors
  - `warnings` (array): List of warnings
  - `info` (array): Configuration info

**Example:**
```php
$status = $xService->validateCredentials();

if (!$status['valid']) {
    foreach ($status['errors'] as $error) {
        echo "❌ {$error}\n";
    }
}

foreach ($status['info'] as $info) {
    echo "✓ {$info}\n";
}
```

**Checks:**
- API Key and Secret presence
- Authentication token availability
- Configuration completeness

---

#### `getAuthInfo(): array`
Gets current authentication status.

**Returns:**
- `array` - Status with keys:
  - `has_bearer_token` (bool)
  - `has_access_token` (bool)
  - `has_refresh_token` (bool)
  - `has_api_credentials` (bool)
  - `access_token_length` (int)
  - `bearer_token_length` (int)

**Example:**
```php
$auth = $xService->getAuthInfo();

echo "Access Token: " . ($auth['has_access_token'] ? "✓" : "✗");
echo "Bearer Token: " . ($auth['has_bearer_token'] ? "✓" : "✗");
```

---

### User Information

#### `getUserInfo(): array`
Gets current authenticated X user information.

**Returns:**
- `array` - User data with keys:
  - `id` (string): User ID
  - `username` (string): Username
  - `name` (string): Display name

**Example:**
```php
$user = $xService->getUserInfo();
echo "Posting as: @{$user['username']} ({$user['name']})";
```

**Features:**
- Uses bearer token
- Comprehensive error handling
- Useful for validation

**Throws:**
- `Exception` - If bearer token invalid or API error

---

## Protected Methods

These are for internal use but documented here:

#### `generateOAuthHeader(string $httpMethod, string $url, array $params): string`
Generates OAuth 1.0a Authorization header (for v1.1 endpoints).

#### `updateEnvTokens(array $tokens): void`
Updates multiple tokens in `.env` file.

#### `updateEnvToken(string $token): void`
Updates single access token in `.env` (deprecated).

#### `downloadImageByCurl(string $url): string|false`
Downloads image from URL or local filesystem.

---

## Configuration

### Required in `config/services.php`
```php
'x' => [
    'bearer_token' => env('X_BEARER_TOKEN'),
    'api_key' => env('X_API_KEY'),
    'api_secret' => env('X_API_SECRET'),
    'access_token' => env('X_ACCESS_TOKEN'),
    'access_token_secret' => env('X_ACCESS_TOKEN_SECRET'),
    'refresh_token' => env('X_REFRESH_TOKEN'),
],
```

### Required in `.env`
```env
# Minimum for OAuth 2.0
X_API_KEY=your_api_key
X_API_SECRET=your_api_secret
X_REFRESH_TOKEN=your_refresh_token

# Optional (auto-populated from refresh)
X_ACCESS_TOKEN=
X_ACCESS_TOKEN_SECRET=

# Optional (for fallback)
X_BEARER_TOKEN=
```

---

## Exception Handling

### Common Exceptions

```php
try {
    $tweetId = $xService->postSimpleTweet($text);
} catch (\Exception $e) {
    $message = $e->getMessage();
    
    // Check error type
    if (str_contains($message, '401')) {
        Log::error('Authentication failed - token invalid');
    } elseif (str_contains($message, 'HTTP 429')) {
        Log::error('Rate limit exceeded');
    } elseif (str_contains($message, 'Tweet text is empty')) {
        Log::error('Invalid input - empty text');
    } else {
        Log::error('X API error: ' . $message);
    }
}
```

### HTTP Status Codes

| Code | Meaning | Solution |
|------|---------|----------|
| 200 | OK | Success |
| 400 | Bad Request | Check parameters |
| 401 | Unauthorized | Refresh token |
| 403 | Forbidden | Check app permissions |
| 429 | Too Many Requests | Wait and retry |
| 500 | Server Error | Retry later |

---

## Usage Patterns

### Pattern 1: Simple Posting
```php
$xService = app(\App\Services\XPublishService::class);
$tweetId = $xService->postSimpleTweet('Hello World!');
```

### Pattern 2: With Error Handling
```php
try {
    $xService = app(\App\Services\XPublishService::class);
    $tweetId = $xService->postSimpleTweet('Tweet text');
    Log::info("Tweet posted: {$tweetId}");
} catch (\Throwable $e) {
    Log::error("Failed to post tweet: " . $e->getMessage());
}
```

### Pattern 3: With Media
```php
$xService = app(\App\Services\XPublishService::class);

$mediaId = $xService->uploadMedia($imageUrl);
$tweetId = $xService->postTweet('Check this!', $mediaId);
```

### Pattern 4: Validation
```php
$xService = app(\App\Services\XPublishService::class);
$status = $xService->validateCredentials();

if (!$status['valid']) {
    throw new \Exception("X credentials not configured");
}

// Proceed with publishing
$tweetId = $xService->postSimpleTweet('Tweet');
```

### Pattern 5: Queue Job
```php
class XPublishJob implements ShouldQueue {
    public function handle(XPublishService $xService): void {
        $tweetId = $xService->postTweet(
            text: $this->publishLog->publish_title,
        );
        
        $this->publishLog->update([
            'status' => 'published',
            'platform_message_id' => $tweetId,
        ]);
    }
}
```

---

## Performance Considerations

| Operation | Time | Notes |
|-----------|------|-------|
| postSimpleTweet() | 1-2s | Network I/O |
| uploadMedia() | 3-5s | File size dependent |
| refreshAccessTokenOAuth2() | 0.5s | API call only |
| validateCredentials() | <0.1s | Local checks only |
| getUserInfo() | 1-2s | Network I/O |

### Optimization Tips
- Use queued jobs for batch operations
- Refresh token during off-peak hours
- Cache user info (1 hour TTL)
- Batch media uploads
- Monitor rate limits

---

## Debugging

### Enable Debug Logging
```php
// In .env
LOG_LEVEL=debug

// Monitor logs
tail -f storage/logs/laravel.log | grep "X\|OAuth"
```

### Check Status
```bash
php artisan tinker
```

```php
$x = app(\App\Services\XPublishService::class);
dd($x->validateCredentials());
dd($x->getAuthInfo());
```

### Test Command
```bash
php artisan x:test-oauth2 --validate
php artisan x:test-oauth2 --user-info
php artisan x:test-oauth2 --tweet="Test"
```

---

## Security

✅ **Credentials Security:**
- Stored in `.env` (not in code)
- Auto-refresh without manual intervention
- Error messages don't leak tokens

⚠️ **Best Practices:**
- Keep `.env` out of version control
- Rotate tokens regularly
- Monitor logs for auth failures
- Use different credentials for testing/production

---

## Version

- **Current Version:** 2.0 (OAuth 2.0)
- **Last Updated:** June 12, 2026
- **Status:** ✅ Stable

---

## Related Files

- [X_OAUTH2_SETUP.md](X_OAUTH2_SETUP.md) - Setup guide
- [X_QUICK_START.md](X_QUICK_START.md) - Quick start
- [app/Services/XPublishService.php](app/Services/XPublishService.php) - Source code
- [app/Console/Commands/TestXOAuth2Command.php](app/Console/Commands/TestXOAuth2Command.php) - Test command
