From f62e4cd769a0f8d7e0bd7466274f074592e9aef2 Mon Sep 17 00:00:00 2001 From: wisecolt Date: Wed, 4 Feb 2026 17:49:12 +0300 Subject: [PATCH] first commit --- .env.example | 19 + .gitignore | 10 + DESIGN.md | 824 ++++++++++ README.md | 225 +++ REQUIREMENTS.md | 210 +++ poster-bash | 1460 +++++++++++++++++ .../Crisis VRigade 2/Crisis VRigade 2.iso | 0 test-games/Crisis VRigade 2/poster.png | Bin 0 -> 10807 bytes test-games/Cyberpunk 2077/Cyberpunk 2077.iso | 0 test-games/Cyberpunk 2077/poster.png | Bin 0 -> 22329 bytes test-games/Elden Ring/Elden Ring.iso | 0 test-games/Elden Ring/poster.png | Bin 0 -> 16188 bytes .../Guns n Stories Bulletproof.iso | 0 .../Guns n Stories Bulletproof/poster.png | Bin 0 -> 26357 bytes .../Richies Plank Experience.iso | 0 .../Richies Plank Experience/poster.png | Bin 0 -> 23363 bytes 16 files changed, 2748 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 DESIGN.md create mode 100644 README.md create mode 100644 REQUIREMENTS.md create mode 100644 poster-bash create mode 100644 test-games/Crisis VRigade 2/Crisis VRigade 2.iso create mode 100644 test-games/Crisis VRigade 2/poster.png create mode 100644 test-games/Cyberpunk 2077/Cyberpunk 2077.iso create mode 100644 test-games/Cyberpunk 2077/poster.png create mode 100644 test-games/Elden Ring/Elden Ring.iso create mode 100644 test-games/Elden Ring/poster.png create mode 100644 test-games/Guns n Stories Bulletproof/Guns n Stories Bulletproof.iso create mode 100644 test-games/Guns n Stories Bulletproof/poster.png create mode 100644 test-games/Richies Plank Experience/Richies Plank Experience.iso create mode 100644 test-games/Richies Plank Experience/poster.png diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..abf5371 --- /dev/null +++ b/.env.example @@ -0,0 +1,19 @@ +# .env - Environment Configuration for poster-bash +# Copy this file to .env and fill in your values + +# IGDB API Credentials +# Get your credentials from: https://api-docs.igdb.com/#account-creation +IGDB_CLIENT_ID="your_client_id_here" +IGDB_CLIENT_SECRET="your_client_secret_here" + +# Scan Directories +# SCAN_DIR: Directory to scan in normal mode +# TEST_DIR: Directory to scan in test mode (--test-run) + +SCAN_DIR="/media/Games" +TEST_DIR="/test-games" + +# Optional: Override default settings +# PROGRESS_MODE="both" +# LOG_LEVEL="info" +# RETRY_COUNT=3 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c579f2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Credentials ve sensitive dosyalar +.env +*.conf +*.log + +# IgDB tokens ve cache +.token-cache + +# Test dosyalarฤฑ +*.tmp diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..e5c9361 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,824 @@ +# ๐Ÿ—๏ธ POSTER-BASH - MฤฐMARฤฐ TASARIM BELGESฤฐ v1.0 + +## Dokรผman Versiyonu +- **Versiyon**: 1.0 +- **Tarih**: 2025-02-04 +- **Durum**: Taslak (Onay Bekliyor) + +--- + +## 1. SฤฐSTEM GENEL BAKIลž + +### 1.1 Mimari Tarz +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ POSTER-BASH SYSTEM โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ SCANNER โ”‚โ”€โ”€โ”€โ–ถโ”‚ IGDB โ”‚โ”€โ”€โ”€โ–ถโ”‚ DOWNLOADER โ”‚ โ”‚ +โ”‚ โ”‚ Module โ”‚ โ”‚ Module โ”‚ โ”‚ Module โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ–ผ โ–ผ โ–ผ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ UI โ”‚ โ”‚ CONFIG โ”‚ โ”‚ LOG โ”‚ โ”‚ +โ”‚ โ”‚ Module โ”‚ โ”‚ Module โ”‚ โ”‚ Module โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### 1.2 Teknoloji YฤฑฤŸฤฑnฤฑ +| Katman | Teknoloji | Aรงฤฑklama | +|--------|-----------|----------| +| **Script** | Bash 4.0+ | POSIX uyumlu | +| **HTTP Client** | curl | API istekleri | +| **JSON Parser** | jq | JSON iลŸleme | +| **ASCII Art** | figlet/toilet | 3D logo | +| **Colors** | ANSI Escape Codes | Renkli terminal | +| **Config** | bash variable export | Ayar yรถnetimi | + +--- + +## 2. MODรœLER MฤฐMARฤฐ + +### 2.1 Modรผl HiyerarลŸisi +``` +poster-bash (Main Entry Point) +โ”‚ +โ”œโ”€โ”€ lib/core.sh # Core utilities & shared functions +โ”œโ”€โ”€ lib/config.sh # Config management +โ”œโ”€โ”€ lib/logger.sh # Logging system +โ”œโ”€โ”€ lib/ui.sh # Terminal UI & colors +โ”œโ”€โ”€ lib/scanner.sh # Directory & ISO scanner +โ”œโ”€โ”€ lib/igdb.sh # IGDB API integration +โ”œโ”€โ”€ lib/downloader.sh # Poster download & retry logic +โ””โ”€โ”€ lib/auth.sh # OAuth token management +``` + +### 2.2 Modรผl Sorumluluklarฤฑ + +#### 2.2.1 Main Entry Point (`poster-bash`) +```bash +Sorumluluklar: +- BaลŸlangฤฑรง kontrolรผ (dependencies, config) +- 3D logo gรถsterimi +- Ana iลŸ akฤฑลŸฤฑ koordinasyonu +- Hata yakalama ve graceful exit + +Interface: +main() โ†’ init โ†’ scan โ†’ process โ†’ download โ†’ report โ†’ cleanup +``` + +#### 2.2.2 Core Module (`lib/core.sh`) +```bash +Sorumluluklar: +- Global deฤŸiลŸken tanฤฑmlarฤฑ +- Shared utility functions +- Error codes ve exit handling +- Network connectivity check + +Public Functions: +- check_dependencies() +- check_internet() +- exit_with_code() +- sanitize_filename() + +Exported Variables: +- SCRIPT_VERSION="1.0.0" +- RETRY_MAX=3 +- TIMEOUT_SECONDS=30 +``` + +#### 2.2.3 Config Module (`lib/config.sh`) +```bash +Sorumluluklar: +- Config dosyasฤฑ okuma/yazma +- Environment variable loading +- Validation ve defaults + +Public Functions: +- config_load() +- config_save() +- config_get(key) +- config_set(key, value) + +Config Structure: +~/.game-bash.conf: + IGDB_CLIENT_ID="..." + IGDB_CLIENT_SECRET="..." + IGDB_ACCESS_TOKEN="" # Auto-filled + IGDB_TOKEN_EXPIRES="" # Auto-filled + PROGRESS_MODE="both" + LOG_LEVEL="info" + RETRY_COUNT=3 +``` + +#### 2.2.4 Logger Module (`lib/logger.sh`) +```bash +Sorumluluklar: +- Log dosyasฤฑna yazma +- Terminal รงฤฑktฤฑsฤฑ formatlama +- Log level filtering + +Public Functions: +- log_info(message) +- log_warn(message) +- log_error(message) +- log_success(message) + +Log Format: +[YYYY-MM-DD HH:MM:SS] LEVEL Message + +Output: +- Terminal: Colored output +- File: ~/.game-bash.log +``` + +#### 2.2.5 UI Module (`lib/ui.sh`) +```bash +Sorumluluklar: +- ANSI renk yรถnetimi +- 3D ASCII logo gรถsterimi +- Progress bar รงizimi +- Retro efektler + +Public Functions: +- ui_show_logo() +- ui_print_header(text) +- ui_show_progress(current, total, text) +- ui_color_print(color, text) + +Colors: +- RED: Errors +- GREEN: Success +- YELLOW: Warnings +- BLUE: Info +- MAGENTA: Headers +- CYAN: Progress + +3D Logo: +- Font: "standard" (figlet) or "future" (toilet) +- Color: Cyan with bold +``` + +#### 2.2.6 Scanner Module (`lib/scanner.sh`) +```bash +Sorumluluklar: +- Recursive directory scanning +- .iso dosyasฤฑ tespiti +- Oyun adฤฑ รงฤฑkarฤฑmฤฑ + +Public Functions: +- scanner_scan_directory(path) +- scanner_find_iso(directory) +- scanner_extract_game_name(iso_path) + +Data Structure: +ISO Entry: +{ + path: "/full/path/to/game.iso", + directory: "/full/path/to", + name: "Game Name", + has_poster: false +} + +Return: Array of ISO entries (space-separated) +``` + +#### 2.2.7 IGDB Module (`lib/igdb.sh`) +```bash +Sorumluluklar: +- OAuth token yรถnetimi +- Oyun arama +- Cover URL retrieval + +Public Functions: +- igdb_authenticate() # Get/refresh token +- igdb_search_game(name) # Search by name +- igdb_search_with_vr(name) # Search with VR suffix +- igdb_get_cover_url(game_id) # Get cover image URL + +API Endpoints: +POST https://id.twitch.tv/oauth2/token + Request: {"client_id":"...", "client_secret":"...", "grant_type":"client_credentials"} + Response: {"access_token":"...", "expires_in":5045603, "token_type":"bearer"} + +POST https://api.igdb.com/v4/games + Headers: {"Authorization":"Bearer ${token}", "Client-ID":"${client_id}"} + Body: search "${name}"; fields name,cover; + Response: [{"id":123, "name":"Game", "cover":456}] + +POST https://api.igdb.com/v4/covers + Headers: {"Authorization":"Bearer ${token}", "Client-ID":"${client_id}"} + Body: fields url,image_id; where id=${cover_id}; + Response: [{"id":456, "image_id":"abc123", "url":"..."}] + +Image URL: https://images.igdb.com/igdb/image/upload/t_cover_big/${image_id}.jpg +``` + +#### 2.2.8 Downloader Module (`lib/downloader.sh`) +```bash +Sorumluluklar: +- Poster indirme +- Retry logic +- Dosya kaydetme + +Public Functions: +- downloader_download_poster(url, output_path) +- downloader_retry_with_backoff(url, output_path, max_retries) +- downloader_check_if_exists(output_path) + +Flow: +1. Check if poster.png exists โ†’ Skip if true +2. Download with curl +3. Validate file size (> 0) +4. On failure: Retry with exponential backoff (1s, 2s, 4s) +5. After max retries: Log and skip +``` + +#### 2.2.9 Auth Module (`lib/auth.sh`) +```bash +Sorumluluklar: +- Token lifecycle management +- Token refresh logic +- Token validation + +Public Functions: +- auth_get_token() # Returns valid token (refresh if needed) +- auth_is_expired() # Check if token expired +- auth_refresh() # Refresh from API +- auth_save_token() # Save to config + +Token Flow: +1. Check config for cached token +2. Check expiration +3. If expired or missing โ†’ refresh +4. Save new token to config +``` + +--- + +## 3. VERฤฐ AKIลžI + +### 3.1 Ana AkฤฑลŸ Diyagramฤฑ +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ START โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ INITIALIZATION โ”‚ +โ”‚ โ”œโ”€ Check dependencies (curl, jq, figlet) โ”‚ +โ”‚ โ”œโ”€ Load config from ~/.game-bash.conf โ”‚ +โ”‚ โ”œโ”€ Check internet connectivity โ”‚ +โ”‚ โ”œโ”€ Initialize logger โ”‚ +โ”‚ โ””โ”€ Show 3D logo โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ AUTHENTICATION โ”‚ +โ”‚ โ”œโ”€ Check cached token โ”‚ +โ”‚ โ”œโ”€ Validate expiration โ”‚ +โ”‚ โ””โ”€ Refresh if needed โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ SCANNING โ”‚ +โ”‚ โ”œโ”€ Scan current directory recursively โ”‚ +โ”‚ โ”œโ”€ Find all .iso files โ”‚ +โ”‚ โ”œโ”€ Extract game names โ”‚ +โ”‚ โ””โ”€ Build processing list โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ PROCESSING LOOP (for each ISO) โ”‚ +โ”‚ โ”œโ”€ Show progress: [1/15] Game Name โ”‚ +โ”‚ โ”œโ”€ Check if poster.png exists โ†’ Skip if yes โ”‚ +โ”‚ โ”œโ”€ Search IGDB for game โ”‚ +โ”‚ โ”œโ”€ Not found? โ†’ Retry with " VR" suffix โ”‚ +โ”‚ โ”œโ”€ Get cover URL from IGDB โ”‚ +โ”‚ โ”œโ”€ Download poster with retry (3x) โ”‚ +โ”‚ โ””โ”€ Log result (success/fail) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ REPORT โ”‚ +โ”‚ โ”œโ”€ Show summary โ”‚ +โ”‚ โ”‚ โ”œโ”€ Total games found โ”‚ +โ”‚ โ”‚ โ”œโ”€ Posters downloaded โ”‚ +โ”‚ โ”‚ โ”œโ”€ Skipped (already had poster) โ”‚ +โ”‚ โ”‚ โ””โ”€ Failed โ”‚ +โ”‚ โ””โ”€ Final message โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ END โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### 3.2 Detaylฤฑ AkฤฑลŸ (Oyun ฤฐลŸleme) +``` +For each ISO file: +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ scanner_find_iso() โ†’ Returns: /path/to/Game/Game.iso โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ scanner_extract_game_name() โ†’ Returns: "Game" โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ downloader_check_if_exists("/path/to/Game/poster.png") โ”‚ +โ”‚ โ”œโ”€ Exists? โ†’ Skip, log "Already has poster" โ”‚ +โ”‚ โ””โ”€ Not exists? โ†’ Continue โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ igdb_search_game("Game") โ”‚ +โ”‚ โ”œโ”€ Found? โ†’ Get cover_id โ”‚ +โ”‚ โ””โ”€ Not found? โ†’ igdb_search_game("Game VR") โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ igdb_get_cover_url(cover_id) โ”‚ +โ”‚ โ”œโ”€ Returns: https://images.igdb.com/.../image_id.jpg โ”‚ +โ”‚ โ””โ”€ Not found? โ†’ Log "No cover available", skip โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ downloader_retry_with_backoff(url, "/path/to/Game/poster.png", 3)โ”‚ +โ”‚ โ”œโ”€ Try 1: curl download โ”‚ +โ”‚ โ”œโ”€ Fail? โ†’ Wait 1s โ†’ Try 2 โ”‚ +โ”‚ โ”œโ”€ Fail? โ†’ Wait 2s โ†’ Try 3 โ”‚ +โ”‚ โ”œโ”€ Fail? โ†’ Wait 4s โ†’ Try 4 โ”‚ +โ”‚ โ””โ”€ All fail? โ†’ Log error, skip โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## 4. API ENTEGRASYON DETAYLARI + +### 4.1 IGDB API Spesifikasyonu + +#### 4.1.1 OAuth Token Request +```bash +# Endpoint +POST https://id.twitch.tv/oauth2/token + +# Request Parameters (application/x-www-form-urlencoded) +client_id=buyzvv6qoyzj7rmauwkfom79h7fvpx +client_secret=tivj7d6b21vqybpb4fx1oe85nffibt +grant_type=client_credentials + +# Expected Response (200 OK) +{ + "access_token": "eyJhbGciOiIXVCJ9...", + "expires_in": 5045603, + "token_type": "bearer" +} + +# Error Response (400 Bad Request) +{ + "status": 400, + "message": "invalid client" +} +``` + +#### 4.1.2 Games Search +```bash +# Endpoint +POST https://api.igdb.com/v4/games + +# Headers +Authorization: Bearer ${ACCESS_TOKEN} +Client-ID: buyzvv6qoyzj7rmauwkfom79h7fvpx +Accept: application/json + +# Request Body (APICalc query format) +search "Cyberpunk 2077"; +fields name,cover; +limit 1; + +# Expected Response (200 OK) +[ + { + "id": 10904, + "name": "Cyberpunk 2077", + "cover": 12345 + } +] + +# Empty Response (no results) +[] +``` + +#### 4.1.3 Covers Query +```bash +# Endpoint +POST https://api.igdb.com/v4/covers + +# Headers (same as games) +Authorization: Bearer ${ACCESS_TOKEN} +Client-ID: buyzvv6qoyzj7rmauwkfom79h7fvpx + +# Request Body +fields url,image_id; +where id = 12345; + +# Expected Response +[ + { + "id": 12345, + "image_id": "co1abc", + "url": "//images.igdb.com/igdb/image/upload/t_cover_big/co1abc.jpg" + } +] + +# Final Image URL +https://images.igdb.com/igdb/image/upload/t_cover_big/co1abc.jpg +``` + +### 4.2 Image Size Options +| Size | URL Pattern | Dimensions | Use Case | +|------|-------------|------------|----------| +| cover_small | t_cover_small | 90x128 | Thumbnails | +| cover_big | **t_cover_big** | 264x374 | **Default (our choice)** | +| screenshot_med | t_screenshot_med | 569x320 | Not used | +| screenshot_big | t_screenshot_big | 889x500 | Not used | + +### 4.3 Error Handling Strategy +```bash +HTTP Code | Action +----------|-------------------------------------------------- +200 | Success, process response +400 | Invalid request, check token +401 | Unauthorized, refresh token +403 | Forbidden, check client credentials +404 | Not found (no games/covers), skip with warning +429 | Rate limit, wait and retry +500+ | Server error, retry with backoff +``` + +--- + +## 5. VERฤฐ YAPILARI + +### 5.1 ISO Entry Structure +```bash +# Bash array representation (space-separated) +ISO_ENTRY_FORMAT="{path}|{directory}|{name}|{has_poster}" + +# Example +"/path/to/Game/Game.iso|/path/to/Game|Game|0" + +# Access pattern +IFS='|' read -r path directory name has_poster <<< "$entry" +``` + +### 5.2 Config Structure +```bash +# Key-value pairs in bash source file +export IGDB_CLIENT_ID="buyzvv6qoyzj7rmauwkfom79h7fvpx" +export IGDB_CLIENT_SECRET="tivj7d6b21vqybpb4fx1oe85nffibt" +export IGDB_ACCESS_TOKEN="" +export IGDB_TOKEN_EXPIRES="" +export PROGRESS_MODE="both" +export LOG_LEVEL="info" +export RETRY_COUNT=3 +``` + +### 5.3 Summary Statistics +```bash +# Runtime statistics (stored in variables) +TOTAL_GAMES=0 +DOWNLOADED=0 +SKIPPED=0 +FAILED=0 +``` + +--- + +## 6. KULLANICI ARAYรœZรœ TASARIMI + +### 6.1 Terminal Dรผzeni +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— โ”‚ +โ”‚ โ•‘ GAME POSTER โ•‘ โ”‚ +โ”‚ โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• โ”‚ +โ”‚ โ”‚ +โ”‚ ๐Ÿ“‚ Scanning: /home/user/Games โ”‚ +โ”‚ ๐ŸŽฎ Found: 15 ISO files โ”‚ +โ”‚ โ”‚ +โ”‚ โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” โ”‚ +โ”‚ โ”‚ +โ”‚ [1/15] Cyberpunk 2077 [โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘โ–‘โ–‘] 40% โ”‚ +โ”‚ โœ“ Downloaded: poster.png โ”‚ +โ”‚ โ”‚ +โ”‚ [2/15] Elden Ring [โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘] 53% โ”‚ +โ”‚ โ„น Already has poster, skipping... โ”‚ +โ”‚ โ”‚ +โ”‚ [3/15] Unknown Game [โ–ˆโ–ˆโ–ˆโ–‘โ–‘โ–‘โ–‘โ–‘โ–‘] 20% โ”‚ +โ”‚ โš  Game not found, retrying with VR suffix... โ”‚ +โ”‚ โš  Not found in IGDB โ”‚ +โ”‚ โ”‚ +โ”‚ โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” โ”‚ +โ”‚ โ”‚ +โ”‚ ๐Ÿ“Š SUMMARY โ”‚ +โ”‚ โœ“ Downloaded: 12 โ”‚ +โ”‚ โ„น Skipped: 2 โ”‚ +โ”‚ โœ— Failed: 1 โ”‚ +โ”‚ โ”‚ +โ”‚ ๐Ÿ“ Log saved to: ~/.game-bash.log โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### 6.2 Renk Paleti +```bash +# ANSI Color Codes +COLOR_RESET='\033[0m' +COLOR_RED='\033[0;31m' # Errors +COLOR_GREEN='\033[0;32m' # Success +COLOR_YELLOW='\033[0;33m' # Warnings +COLOR_BLUE='\033[0;34m' # Info +COLOR_MAGENTA='\033[0;35m' # Headers +COLOR_CYAN='\033[0;36m' # Progress/Logo +COLOR_BOLD='\033[1m' +COLOR_DIM='\033[2m' + +# Usage +echo -e "${COLOR_GREEN}โœ“ Success message${COLOR_RESET}" +echo -e "${COLOR_RED}โœ— Error message${COLOR_RESET}" +``` + +### 6.3 Progress Bar Implementation +```bash +show_progress_bar() { + local current=$1 + local total=$2 + local width=40 + local percentage=$((current * 100 / total)) + local filled=$((width * current / total)) + local empty=$((width - filled)) + + printf "[" + printf "%${filled}s" | tr ' ' 'โ–ˆ' + printf "%${empty}s" | tr ' ' 'โ–‘' + printf "] %d%%\r" "$percentage" +} +``` + +--- + +## 7. HATA Yร–NETฤฐMฤฐ + +### 7.1 Exit Code Convention +```bash +EXIT_SUCCESS=0 # Normal exit +EXIT_ERROR_GENERAL=1 # General error +EXIT_ERROR_DEPS=2 # Missing dependencies +EXIT_ERROR_NETWORK=3 # Network error +EXIT_ERROR_AUTH=4 # Authentication failed +EXIT_ERROR_CONFIG=5 # Config error +EXIT_ERROR_API=6 # API error +``` + +### 7.2 Error Recovery Flow +``` +Error Occurs + โ”‚ + โ–ผ +Check Type + โ”‚ + โ”œโ”€ Network Error โ†’ Check internet โ†’ Exit if offline + โ”œโ”€ Auth Error โ†’ Refresh token โ†’ Retry โ†’ Exit if fail + โ”œโ”€ API Error โ†’ Log warning โ†’ Skip current game โ†’ Continue + โ”œโ”€ Download Error โ†’ Retry (3x) โ†’ Log โ†’ Skip โ†’ Continue + โ””โ”€ Config Error โ†’ Show message โ†’ Exit +``` + +### 7.3 Graceful Degradation +```bash +# Fallback behaviors +figlet not found โ†’ Use simple echo with colors +jq not found โ†’ Fatal error (required dependency) +curl not found โ†’ Fatal error (required dependency) +Token refresh fail โ†’ Exit with auth error +Internet down โ†’ Exit with network error +``` + +--- + +## 8. PERFORMANS VE SKALABฤฐLฤฐTE + +### 8.1 Performance Targets +| Metric | Target | Notes | +|--------|--------|-------| +| Startup time | < 2s | Logo + auth + scan | +| Per-game processing | 1-3s | API call + download | +| Memory usage | Minimal | Bash processes | +| Concurrent games | 1 | Sequential processing | + +### 8.2 Optimization Strategies +```bash +1. Token Caching + - Store in config, reuse until expired + - Reduces auth overhead + +2. Skip Existing Posters + - Check file existence before API call + - Saves bandwidth and time + +3. Minimal Dependencies + - Only curl, jq, figlet required + - Fast startup, low overhead + +4. Sequential Processing + - No parallel processing (bash limitation) + - Better error handling +``` + +--- + +## 9. GรœVENLฤฐK + +### 9.1 Security Considerations +```bash +1. Credential Protection + - Config file: ~/.game-bash.conf (chmod 600) + - .gitignore: Prevent git commit + - Never log tokens/secrets + +2. Input Sanitization + - Escape game names before API call + - Validate paths before file operations + +3. File Operations + - Check file exists before overwrite + - Use safe temp directories + +4. Network Security + - HTTPS only for API calls + - Validate SSL certificates +``` + +### 9.2 File Permissions +```bash +# Config file +chmod 600 ~/.game-bash.conf + +# Script +chmod 755 poster-bash + +# Library files +chmod 644 lib/*.sh +``` + +--- + +## 10. TEST STRATEJฤฐSฤฐ + +### 10.1 Unit Test Coverage +```bash +# Test categories +1. Config Tests + - Load valid config + - Handle missing config + - Save and reload + +2. Scanner Tests + - Find ISO files + - Extract game names + - Handle empty directories + +3. IGDB Tests + - Token refresh + - Game search + - Cover URL retrieval + +4. Downloader Tests + - Download success + - Retry logic + - File existence check + +5. Logger Tests + - All log levels + - File writing + - Terminal output +``` + +### 10.2 Integration Test Scenarios +```bash +1. End-to-End Flow + - Multiple games + - Mix of found/not found + - VR suffix retry + +2. Error Scenarios + - No internet + - Invalid credentials + - API rate limit + +3. Edge Cases + - Special characters in game names + - Very long directory names + - Permission denied +``` + +--- + +## 11. DEPLOYMENT + +### 11.1 Installation +```bash +# Clone or download +git clone https://github.com/user/poster-bash.git +cd poster-bash + +# Make executable +chmod +x poster-bash + +# Copy to PATH (optional) +sudo cp poster-bash /usr/local/bin/ + +# First run (creates config) +./poster-bash +``` + +### 11.2 Directory Structure After Install +``` +~/.game-bash.conf # User config +~/.game-bash.log # Log file +/usr/local/bin/poster-bash # Executable (if installed) +``` + +--- + +## 12. BAKIM VE EVRฤฐM + +### 12.1 Version Strategy +``` +Semantic Versioning: MAJOR.MINOR.PATCH + +- MAJOR: Breaking changes +- MINOR: New features (backward compatible) +- PATCH: Bug fixes + +Current: v1.0.0 +``` + +### 12.2 Future Enhancements +``` +Planned Features: +- [ ] Parallel processing (background jobs) +- [ ] Custom poster size selection +- [ ] Multiple image sources fallback +- [ ] Interactive mode (select games manually) +- [ ] Database cache for faster lookups +- [ ] GUI mode (optional) +``` + +--- + +## 13. REFERANSLAR + +### 13.1 External Documentation +- [IGDB API Documentation](https://api-docs.igdb.com/) +- [Twitch OAuth Documentation](https://dev.twitch.tv/docs/authentication/) +- [Bash Style Guide](https://google.github.io/styleguide/shellguide.html) + +### 13.2 Related Documents +- `REQUIREMENTS.md` - Functional requirements +- `README.md` - User documentation (to be created) +- `CHANGELOG.md` - Version history (to be created) + +--- + +## 14. ONAY KONTROL LฤฐSTESฤฐ + +- [x] Modรผler yapฤฑ tanฤฑmlandฤฑ +- [x] API entegrasyonu tasarlandฤฑ +- [x] Veri akฤฑลŸฤฑ diagramฤฑ oluลŸturuldu +- [x] Hata yรถnetimi stratejisi belirlendi +- [x] Gรผvenlik รถnlemleri tanฤฑmlandฤฑ +- [x] UI/UX tasarฤฑmฤฑ tamamlandฤฑ +- [ ] Implementasyon onayฤฑ bekliyor +- [ ] Kodlama aลŸamasฤฑna hazฤฑr + +--- + +_v1.0 - 2025-02-04 | Tasarฤฑm Onayฤฑ Bekliyor_ diff --git a/README.md b/README.md new file mode 100644 index 0000000..543722b --- /dev/null +++ b/README.md @@ -0,0 +1,225 @@ +# ๐ŸŽฎ POSTER-BASH + +Linux iรงin bash tabanlฤฑ terminal uygulamasฤฑ. Oyun .iso dosyalarฤฑnฤฑzฤฑ tarar, IGDB API'den poster resimlerini bulur ve indirir. + +## ๐Ÿ“‹ ร–zellikler + +- โœ… **Otomatik Kurulum**: Eksik paketleri otomatik indirir ve kurar +- โœ… **Otomatik Tarama**: Config'den belirtilen dizini tarar +- โœ… **IGDB Entegrasyonu**: Dรผnyanฤฑn en bรผyรผk oyun veritabanฤฑndan poster arar +- โœ… **VR DesteฤŸi**: Oyun bulunamazsa otomatik olarak "VR" ile tekrar dener +- โœ… **Akฤฑllฤฑ ร–nbellek**: Zaten indirilen posterleri atlar +- โœ… **Retries**: ฤฐndirme baลŸarฤฑsฤฑz olursa 3 kere dener +- โœ… **Retro Terminal**: Renkli รงฤฑktฤฑ, 3D ASCII logo, progress bar +- โœ… **Detaylฤฑ Loglama**: Tรผm iลŸlemleri log dosyasฤฑna kaydeder +- โœ… **Test Modu**: `--test-run` ile test dizininde eski posterleri temizleyip yenilerini indirir + +## ๐Ÿ“ธ Ekran Gรถrรผntรผsรผ + +``` + โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— + โ•‘ GAME POSTER โ•‘ + โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +INFO Taranan dizin: /media/Games +INFO Bulunan ISO dosyasฤฑ sayฤฑsฤฑ: 5 + +[1/5] Elden Ring [โ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘โ–‘โ–‘โ–‘] 20% +โœ“ ฤฐndirildi: poster.png + +[2/5] Cyberpunk 2077 [โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘โ–‘โ–‘] 40% +โ„น Poster zaten var, atlanฤฑyor... + +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +๐Ÿ“Š ร–ZET +โœ“ ฤฐndirildi: 2 +โ„น Atlandฤฑ: 2 +โœ— BaลŸarฤฑsฤฑz: 1 +``` + +## ๐Ÿš€ Kurulum + +### Tek Dosya + +```bash +# Script'i indirin +wget https://raw.githubusercontent.com/wisecolt/poster-bash/main/poster-bash + +# ร‡alฤฑลŸtฤฑrฤฑlabilir yapฤฑn +chmod +x poster-bash +``` + +### Ayarlama (.env) + +Script ile aynฤฑ dizine `.env` dosyasฤฑ oluลŸturun: + +```bash +# .env +IGDB_CLIENT_ID=your_client_id_here +IGDB_CLIENT_SECRET=your_client_secret_here + +SCAN_DIR="/media/Games" +TEST_DIR="/test-games" +``` + +**Not**: IGDB credentials almak iรงin: https://api-docs.igdb.com/#account-creation + +### Kullanฤฑma Hazฤฑr Kฤฑlma + +```bash +# Script'i oyunlarฤฑnฤฑzฤฑn olduฤŸu dizine koyun +cp poster-bash /media/Games/ + +# .env dosyasฤฑnฤฑ aynฤฑ dizine oluลŸturun +cd /media/Games +nano .env # yukarฤฑdaki iรงerikle doldurun + +# ร‡alฤฑลŸtฤฑrฤฑn +./poster-bash +``` + +**ฤฐlk รงalฤฑลŸtฤฑrmada script otomatik olarak ลŸunlarฤฑ yapar:** +- `curl` ve `jq` paketlerini kurar (sudo ile) +- Config dosyasฤฑnฤฑ oluลŸturur (`~/.game-bash.conf`) +- ฤฐlk poster indirmelerini yapar + +## ๐Ÿ“– Kullanฤฑm + +### Temel Kullanฤฑm + +```bash +# Normal mod - SCAN_DIR'yi tarar +./poster-bash + +# Test modu - TEST_DIR'yi tarar, eski posterleri temizler +./poster-bash --test-run +``` + +### Seรงenekler + +```bash +poster-bash [SEร‡ENEKLER] + +Seรงenekler: + -h, --help Yardฤฑm mesajฤฑnฤฑ gรถsterir + -v, --version Versiyon bilgisini gรถsterir + -c, --config Config dosyasฤฑnฤฑn konumunu gรถsterir + -l, --log Log dosyasฤฑnฤฑ gรถsterir + --clear-log Log dosyasฤฑnฤฑ temizler + --test-run Test modunda รงalฤฑลŸฤฑr (รถnce eski posterleri temizler) +``` + +### ร–rnekler + +```bash +# Yardฤฑm +./poster-bash --help + +# Versiyon +./poster-bash --version + +# Loglarฤฑ gรถrรผntรผle +./poster-bash --log + +# Config dosyasฤฑnฤฑ gรถrรผntรผle +./poster-bash --config +``` + +## โš™๏ธ Yapฤฑlandฤฑrma + +Script iki yerden ayar okur: + +### 1. .env Dosyasฤฑ (Proje Dizini) + +```bash +# .env +IGDB_CLIENT_ID=your_client_id +IGDB_CLIENT_SECRET=your_client_secret +SCAN_DIR="/path/to/games" +TEST_DIR="/test-games" +``` + +### 2. Config Dosyasฤฑ (Otomatik OluลŸturulur: `~/.game-bash.conf`) + +Config dosyasฤฑ IGDB token'ฤฑnฤฑ ve diฤŸer ayarlarฤฑ saklar. + +--- + +## ๐Ÿ“ Dizin Yapฤฑsฤฑ + +``` +/media/Games/ +โ”œโ”€โ”€ poster-bash # Script (tek dosya) +โ”œโ”€โ”€ .env # Ayarlarฤฑnฤฑz +โ”œโ”€โ”€ Elden Ring/ +โ”‚ โ”œโ”€โ”€ Elden Ring.iso +โ”‚ โ””โ”€โ”€ poster.png # Script indirir +โ”œโ”€โ”€ Cyberpunk 2077/ +โ”‚ โ”œโ”€โ”€ Cyberpunk 2077.iso +โ”‚ โ””โ”€โ”€ poster.png # Script indirir +โ””โ”€โ”€ ... (diฤŸer oyunlar) +``` + +## ๐Ÿ”„ Nasฤฑl ร‡alฤฑลŸฤฑr? + +1. **BaลŸlat**: Script รงalฤฑลŸtฤฑrฤฑlฤฑr +2. **Otomatik Kurulum**: Eksik paketler (`curl`, `jq`) otomatik kurulur +3. **.env Okuma**: Ayarlar aynฤฑ dizindeki `.env` dosyasฤฑndan okunur +4. **Tarama**: Config'deki dizin taranฤฑr +5. **IGDB Arama**: Oyunlar IGDB'de aranฤฑr +6. **VR Yeniden Deneme**: Bulunamazsa "VR" eklenip tekrar aranฤฑr +7. **Poster ฤฐndirme**: Bulunursa poster indirilir +8. **Kaydet**: `poster.png` olarak oyun dizinine kaydedilir + +## ๐Ÿ› Sorun Giderme + +### "sudo: ./poster-bash: command not found" + +```bash +# Tam path ile รงalฤฑลŸtฤฑrฤฑn +bash /path/to/poster-bash + +# Veya sistem bin dizinine kopyalayฤฑn +sudo cp /path/to/poster-bash /usr/local/bin/ +sudo poster-bash --test-run +``` + +### "Permission denied" hatasฤฑ + +```bash +# Script'e รงalฤฑลŸtฤฑrma izni verin +chmod +x poster-bash +``` + +### ".env dosyasฤฑ bulunamadฤฑ" + +```bash +# Script ile aynฤฑ dizinde .env oluลŸturun +cat > .env << EOF +IGDB_CLIENT_ID=your_id +IGDB_CLIENT_SECRET=your_secret +SCAN_DIR="/path/to/games" +TEST_DIR="/test-games" +EOF +``` + +## ๐Ÿ“œ Lisans + +Bu proje MIT lisansฤฑ altฤฑnda lisanslanmฤฑลŸtฤฑr. + +## ๐Ÿ™ TeลŸekkรผrler + +- [IGDB](https://www.igdb.com) - Oyun veritabanฤฑ iรงin +- [Twitch](https://dev.twitch.tv) - OAuth iรงin + +## ๐Ÿ“ž ฤฐletiลŸim + +- **GitHub**: [wisecolt/poster-bash](https://github.com/wisecolt/poster-bash) +- **Issues**: [GitHub Issues](https://github.com/wisecolt/poster-bash/issues) + +--- + +_v1.0.0 - 2025_ \ No newline at end of file diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md new file mode 100644 index 0000000..a2f9370 --- /dev/null +++ b/REQUIREMENTS.md @@ -0,0 +1,210 @@ +# ๐Ÿ“‹ GAME POSTER - GEREKSฤฐNฤฐMLER BELGESฤฐ v1.0 + +## Proje ร–zeti +Linux iรงin bash tabanlฤฑ terminal uygulamasฤฑ. Oyun .iso dosyalarฤฑnฤฑ tespit edip IGDB API'den poster resimlerini indirir. + +--- + +## 1. FONKSฤฐYONEL GEREKSฤฐNฤฐMLER + +### FR-1: Dizin Tarama +``` +FR-1.1: Script'in รงalฤฑลŸtฤฑฤŸฤฑ dizindeki alt klasรถrleri recursive olarak taramalฤฑdฤฑr +FR-1.2: Her klasรถrde .iso dosyasฤฑ aramalฤฑdฤฑr +FR-1.3: .iso bulunan klasรถrleri iลŸleme almalฤฑdฤฑr +FR-1.4: .iso bulunamayan klasรถrler iรงin ".iso dosyasฤฑ bulunamadฤฑ" mesajฤฑ vermeli ve atlamalฤฑdฤฑr +``` + +### FR-2: Oyun Adฤฑ ร‡ฤฑkarฤฑmฤฑ +``` +FR-2.1: .iso dosya adฤฑndan oyun adฤฑnฤฑ รงฤฑkarmalฤฑdฤฑr +FR-2.2: ".iso" uzantฤฑsฤฑnฤฑ kaldฤฑrmalฤฑdฤฑr +FR-2.3: ร–rnek: "Cyberpunk 2077.iso" โ†’ "Cyberpunk 2077" +``` + +### FR-3: IGDB API Entegrasyonu +``` +FR-3.1: IGDB API ile oyun aramalฤฑdฤฑr +FR-3.2: Client ID: 6174nc97wqqt2ny13fildjy5co52rg +FR-3.3: OAuth redirect URI: http://localhost +FR-3.4: Access token almak iรงin OAuth flow implement edilmelidir (client credentials flow) +FR-3.5: Oyun bulamazsa "[oyun_adฤฑ] VR" formatฤฑnda yeniden aramalฤฑdฤฑr +FR-3.6: Birden fazla sonuรง dรถnerse ilk sonucu kullanmalฤฑdฤฑr +FR-3.7: Access token'ฤฑ ~/.game-poster.conf'da cache'lemelidir +``` + +### FR-4: Poster ฤฐndirme +``` +FR-4.1: IGDB'den poster URL'sini almalฤฑdฤฑr +FR-4.2: Poster'i PNG formatฤฑnda indirmelidir +FR-4.3: Dosya adฤฑ: poster.png +FR-4.4: .iso ile aynฤฑ dizine kaydetmelidir +FR-4.5: Aynฤฑ dizinde poster.png zaten varsa atlamalฤฑdฤฑr +FR-4.6: ฤฐndirme baลŸarฤฑsฤฑz olursa 3 kere yeniden denemelidir +FR-4.7: 3 deneme sonunda da baลŸarฤฑsฤฑz olursa log'a yazmalฤฑdฤฑr (dosya koyma) +FR-4.8: Poster bulunamazsa hiรงbir dosya koyma, sadece log tut +``` + +### FR-5: Progress Gรถsterimi +``` +FR-5.1: Basamalฤฑ ilerleme gรถstermelidir: "[1/15] Cyberpunk 2077..." +FR-5.2: Progress bar gรถstermelidir: "[โ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘โ–‘โ–‘] 25% +FR-5.3: Default mode: "both" (hem steps hem bar) +``` + +--- + +## 2. NON-FONKSฤฐYONEL GEREKSฤฐNฤฐMLER + +### NFR-1: Teknoloji YฤฑฤŸฤฑnฤฑ +``` +NFR-1.1: Bash script (sh/POSIX uyumlu) +NFR-1.2: Minimum baฤŸฤฑmlฤฑlฤฑk (curl, jq, figlet/toilet) +NFR-1.3: Python/Yarn vs. gerektirmemelidir +``` + +### NFR-2: Terminal Arayรผzรผ +``` +NFR-2.1: Renkli รงฤฑktฤฑ (ANSI renk kodlarฤฑ) +NFR-2.2: BaลŸlangฤฑรงta "GAME POSTER" 3D ASCII logo (figlet/toilet) +NFR-2.3: Retro/pixel tarzฤฑ gรถrsel efektler +NFR-2.4: Tรผrkรงe dil desteฤŸi +``` + +### NFR-3: Yapฤฑlandฤฑrma +``` +NFR-3.1: Config dosyasฤฑ: ~/.game-bash.conf +NFR-3.2: Log dosyasฤฑ: ~/.game-bash.log +NFR-3.3: Access token'ฤฑ config'de cache'lemelidir +``` + +### NFR-4: Hata Yรถnetimi +``` +NFR-4.1: ฤฐnternet baฤŸlantฤฑsฤฑ yoksa hata verip รงฤฑkmalฤฑdฤฑr +NFR-4.2: IGDB API hatasฤฑnda kullanฤฑcฤฑyฤฑ bilgilendirmelidir +NFR-4.3: Her iลŸlem iรงin log tutmalฤฑdฤฑr +``` + +--- + +## 3. KULLANICI HฤฐKAYELERฤฐ + +| ID | Hikaye | Kabul Kriteri | +|----|--------|---------------| +| US-1 | Kullanฤฑcฤฑ, oyun posterlerini otomatik indirmek istiyor | Script รงalฤฑลŸtฤฑฤŸฤฑnda tรผm oyunlar iรงin poster indirilmeli | +| US-2 | Kullanฤฑcฤฑ, ilerleme durumunu gรถrmek istiyor | Terminalde progress bar ve basamalฤฑ ilerleme gรถsterilmeli | +| US-3 | Kullanฤฑcฤฑ, zaten indirilmiลŸ posterleri tekrar indirmek istemiyor | poster.png varsa atlanmalฤฑ | +| US-4 | Kullanฤฑcฤฑ, VR oyunlarฤฑ iรงin de poster indirmek istiyor | Oyun bulunamazsa "VR" eklenip aranmalฤฑ | + +--- + +## 4. TEKNฤฐK SPESฤฐFฤฐKASYON + +### 4.1 Proje Yapฤฑsฤฑ +``` +poster-bash/ +โ”œโ”€โ”€ poster-bash # Ana script (executable) +โ”œโ”€โ”€ lib/ +โ”‚ โ”œโ”€โ”€ igdb.sh # IGDB API fonksiyonlarฤฑ +โ”‚ โ”œโ”€โ”€ scanner.sh # .iso tarama fonksiyonlarฤฑ +โ”‚ โ”œโ”€โ”€ downloader.sh # Poster indirme fonksiyonlarฤฑ +โ”‚ โ””โ”€โ”€ ui.sh # Terminal UI fonksiyonlarฤฑ +โ”œโ”€โ”€ src/ +โ”‚ โ””โ”€โ”€ auth-setup.sh # IGDB OAuth setup helper +โ””โ”€โ”€ README.md # Kullanฤฑm dokรผmantasyonu +``` + +### 4.2 Config Dosyasฤฑ Formatฤฑ +```bash +# ~/.game-bash.conf +IGDB_CLIENT_ID="buyzvv6qoyzj7rmauwkfom79h7fvpx" +IGDB_CLIENT_SECRET="tivj7d6b21vqybpb4fx1oe85nffibt" +IGDB_REDIRECT_URI="http://localhost" +IGDB_ACCESS_TOKEN="" # Token cache (auto-filled) +IGDB_TOKEN_EXPIRES="" # Token expiration (auto-filled) +PROGRESS_MODE="both" # steps, bar, both +LOG_LEVEL="info" +RETRY_COUNT=3 +``` + +### 4.3 IGDB API Endpoints +``` +OAuth Token: https://id.twitch.tv/oauth2/token + - POST with: client_id, client_secret, grant_type=client_credentials + +Games Search: https://api.igdb.com/v4/games + - POST body: search "game name"; fields name,cover; + +Covers: https://api.igdb.com/v4/covers + - POST body: fields url,image_id; where id = X; + - Image URL: https://images.igdb.com/igdb/image/upload/t_cover_big/{image_id}.jpg +``` + +### 4.4 OAuth Flow (Client Credentials) +``` +1. Kullanฤฑcฤฑdan IGDB Client Secret al +2. POST https://id.twitch.tv/oauth2/token + - client_id: 6174nc97wqqt2ny13fildjy5co52rg + - client_secret: [kullanฤฑcฤฑdan alฤฑnan] + - grant_type: client_credentials +3. Response: access_token, expires_in, token_type +4. Token'ฤฑ config'e cache'le +5. Token expired ise yenile +``` + +--- + +## 5. KABUL TESTLERฤฐ + +``` +AT-1: Script games dizininde รงalฤฑลŸtฤฑrฤฑldฤฑฤŸฤฑnda tรผm klasรถrleri tarar +AT-2: .iso dosyasฤฑ "Cyberpunk 2077.iso" iรงin "Cyberpunk 2077" aranฤฑr +AT-3: IGDB'den poster bulunursa poster.png olarak kaydedilir +AT-4: poster.png zaten varsa yeniden indirilmez +AT-5: Oyun bulunamazsa "Oyun Adฤฑ VR" olarak yeniden aranฤฑr +AT-6: Terminalde renkli รงฤฑktฤฑ ve 3D logo gรถsterilir +AT-7: Log dosyasฤฑna tรผm iลŸlemler yazฤฑlฤฑr +AT-8: ฤฐnternet yoksa hata verilip รงฤฑkฤฑlฤฑr +AT-9: ฤฐndirme baลŸarฤฑsฤฑz olursa 3 kere yeniden denenir +``` + +--- + +## 6. LOG FORMATI + +``` +[2025-02-04 15:30:45] INFO Starting poster-bash v1.0 +[2025-02-04 15:30:46] INFO Scanning directory: /home/user/Games +[2025-02-04 15:30:47] INFO Found ISO: Cyberpunk 2077.iso +[2025-02-04 15:30:48] INFO Searching IGDB: "Cyberpunk 2077" +[2025-02-04 15:30:49] INFO Found game: Cyberpunk 2077 (ID: 10904) +[2025-02-04 15:30:50] INFO Downloading poster: https://images.igdb.com/... +[2025-02-04 15:30:52] SUCCESS Poster saved: /home/user/Games/Cyberpunk 2077/poster.png +[2025-02-04 15:30:53] WARN Game not found: "Unknown Game" +[2025-02-04 15:30:54] INFO Retrying with VR suffix: "Unknown Game VR" +[2025-02-04 15:30:55] ERROR Poster download failed: HTTP 404 +``` + +--- + +## 7. BฤฐTฤฐลž KRฤฐTERLERฤฐ + +- [x] Gereksinimler belgesi tamamlandฤฑ +- [x] OAuth flow planlandฤฑ +- [ ] Mimari tasarฤฑm tamamlanacak (/sc:design) +- [ ] Implementasyon tamamlanacak (/sc:implement) +- [ ] Testler yazฤฑlacak +- [ ] Dokรผmantasyon yazฤฑlacak + +--- + +## 8. SONRAKฤฐ ADIMLAR + +1. โœ… **Gereksinim Belgesi** (TAMAMLANDI) +2. โญ๏ธ **Mimari Tasarฤฑm** โ†’ `/sc:design` +3. โญ๏ธ **Implementasyon** โ†’ `/sc:implement` +4. โญ๏ธ **Test & Dokรผmantasyon** + +--- + +_v1.0 - 2025-02-04_ diff --git a/poster-bash b/poster-bash new file mode 100644 index 0000000..aae4cc1 --- /dev/null +++ b/poster-bash @@ -0,0 +1,1460 @@ +#!/bin/bash +# poster-bash - Oyun posteri indirici (Standalone) +# Version: 1.0.0 +# https://github.com/wisecolt/poster-bash +# +# Tรผm modรผlleri iรงeren tek dosya halinde script + +set -e + +############################################################################### +# CORE MODULE - lib/core.sh +############################################################################### + +# Version info +export SCRIPT_VERSION="1.0.0" +export SCRIPT_NAME="poster-bash" + +# Exit codes +export EXIT_SUCCESS=0 +export EXIT_ERROR_GENERAL=1 +export EXIT_ERROR_DEPS=2 +export EXIT_ERROR_NETWORK=3 +export EXIT_ERROR_AUTH=4 +export EXIT_ERROR_CONFIG=5 +export EXIT_ERROR_API=6 + +# Constants +export RETRY_MAX=3 +export TIMEOUT_SECONDS=30 +export CONFIG_FILE="${HOME}/.game-bash.conf" +export LOG_FILE="${HOME}/.game-bash.log" + +# IGDB API endpoints +export IGDB_OAUTH_URL="https://id.twitch.tv/oauth2/token" +export IGDB_GAMES_URL="https://api.igdb.com/v4/games" +export IGDB_COVERS_URL="https://api.igdb.com/v4/covers" +export IGDB_IMAGE_BASE="https://images.igdb.com/igdb/image/upload/t_cover_big" + +# Detect package manager +detect_package_manager() { + if command -v apt-get &> /dev/null; then + echo "apt" + elif command -v pacman &> /dev/null; then + echo "pacman" + elif command -v dnf &> /dev/null; then + echo "dnf" + elif command -v yum &> /dev/null; then + echo "yum" + else + echo "unknown" + fi +} + +# Install package automatically +install_package() { + local pkg="$1" + local pkg_manager + pkg_manager=$(detect_package_manager) + + echo "๐Ÿ“ฆ $pkg kuruluyor..." >&2 + + case "$pkg_manager" in + apt) + sudo apt-get update -qq > /dev/null 2>&1 + sudo apt-get install -y "$pkg" > /dev/null 2>&1 + ;; + pacman) + sudo pacman -S --noconfirm "$pkg" > /dev/null 2>&1 + ;; + dnf) + sudo dnf install -y "$pkg" > /dev/null 2>&1 + ;; + yum) + sudo yum install -y "$pkg" > /dev/null 2>&1 + ;; + *) + echo "Hata: Paket yรถneticisi bulunamadฤฑ. Lรผtfen manuel kurun:" >&2 + echo " sudo apt-get install $pkg" >&2 + return 1 + ;; + esac + + return $? +} + +# Check and auto-install required dependencies +check_dependencies() { + local missing=() + local deps=("curl" "jq") + local optional_deps=("figlet" "toilet") + + # Check required dependencies + for dep in "${deps[@]}"; do + if ! command -v "$dep" &> /dev/null; then + missing+=("$dep") + fi + done + + # Auto-install missing dependencies + if [ ${#missing[@]} -gt 0 ]; then + echo "" >&2 + echo "๐Ÿ”ง Eksik baฤŸฤฑmlฤฑlฤฑklar kuruluyor..." >&2 + for dep in "${missing[@]}"; do + if ! install_package "$dep"; then + echo "Hata: $dep kurulamadฤฑ." >&2 + exit $EXIT_ERROR_DEPS + fi + echo "โœ“ $dep kuruldu" >&2 + done + echo "" >&2 + fi + + # Check for optional ASCII art tools + export HAS_FIGLET=0 + export HAS_TOILET=0 + + if command -v figlet &> /dev/null; then + HAS_FIGLET=1 + fi + + if command -v toilet &> /dev/null; then + HAS_TOILET=1 + fi +} + +# Check internet connectivity +check_internet() { + if ! curl -s --head --connect-timeout 5 "https://api.igdb.com" > /dev/null 2>&1; then + echo "Hata: ฤฐnternet baฤŸlantฤฑsฤฑ bulunamadฤฑ." >&2 + echo "Lรผtfen internet baฤŸlantฤฑnฤฑzฤฑ kontrol edin." >&2 + exit $EXIT_ERROR_NETWORK + fi +} + +# Extract game name from ISO path +extract_game_name_from_path() { + local iso_path="$1" + local basename + + basename=$(basename "$iso_path") + # Remove .iso extension (case insensitive) + echo "${basename%.iso}" | sed 's/\.ISO$//' +} + +# Get timestamp in format YYYY-MM-DD HH:MM:SS +get_timestamp() { + date '+%Y-%m-%d %H:%M:%S' +} + +# Format bytes to human readable +format_bytes() { + local bytes=$1 + if [ $bytes -lt 1024 ]; then + echo "${bytes}B" + elif [ $bytes -lt 1048576 ]; then + echo "$((bytes / 1024))KB" + else + echo "$((bytes / 1048576))MB" + fi +} + +############################################################################### +# CONFIG MODULE - lib/config.sh +############################################################################### + +# Find .env file (search in script directory and parent directories) +config_find_env() { + local current_dir + current_dir="$(pwd)" + + # Check current directory + if [ -f "${current_dir}/.env" ]; then + echo "${current_dir}/.env" + return 0 + fi + + # Check parent directory (for poster_bash folder case) + local parent_dir + parent_dir="$(dirname "$current_dir")" + if [ -f "${parent_dir}/.env" ]; then + echo "${parent_dir}/.env" + return 0 + fi + + # Check up to 3 levels up + local check_dir="$current_dir" + for i in {1..3}; do + check_dir="$(dirname "$check_dir")" + if [ -f "${check_dir}/.env" ]; then + echo "${check_dir}/.env" + return 0 + fi + done + + # Not found + return 1 +} + +# Load .env file +config_load_env() { + local env_file + env_file="$(config_find_env)" + + if [ -z "$env_file" ]; then + # Silent return, will use defaults + return 1 + fi + + # Read .env file line by line and export variables + while IFS= read -r line || [ -n "$line" ]; do + # Skip comments and empty lines + [[ "$line" =~ ^#.*$ ]] && continue + [[ -z "$line" ]] && continue + + # Skip lines without = + [[ ! "$line" =~ = ]] && continue + + # Export KEY=VALUE format (with or without quotes) + # Use eval to properly handle quoted values + eval export "$line" + done < "$env_file" + + return 0 +} + +# Load configuration from file +config_load() { + if [ ! -f "$CONFIG_FILE" ]; then + return 1 + fi + + # Source the config file + # shellcheck source=/dev/null + source "$CONFIG_FILE" + return 0 +} + +# Save configuration to file +config_save() { + cat > "$CONFIG_FILE" << EOF +# ~/.game-bash.conf +# Game Poster Bash - Configuration File +# Auto-generated on $(get_timestamp) + +# IGDB API Credentials (from .env or defaults) +IGDB_CLIENT_ID="${IGDB_CLIENT_ID}" +IGDB_CLIENT_SECRET="${IGDB_CLIENT_SECRET}" +IGDB_REDIRECT_URI="${IGDB_REDIRECT_URI:-http://localhost}" + +# OAuth Token Cache (auto-filled by script) +IGDB_ACCESS_TOKEN="${IGDB_ACCESS_TOKEN:-}" +IGDB_TOKEN_EXPIRES="${IGDB_TOKEN_EXPIRES:-}" + +# Application Settings +PROGRESS_MODE="${PROGRESS_MODE:-both}" +LOG_LEVEL="${LOG_LEVEL:-info}" +RETRY_COUNT="${RETRY_COUNT:-3}" + +# Poster Settings +POSTER_FILENAME="${POSTER_FILENAME:-poster.png}" +POSTER_FORMAT="${POSTER_FORMAT:-png}" + +# UI Settings +SHOW_3D_LOGO="${SHOW_3D_LOGO:-true}" +RETRO_MODE="${RETRO_MODE:-true}" + +# Scan Directories (from .env) +SCAN_DIR="${SCAN_DIR:-}" +TEST_DIR="${TEST_DIR:-}" +EOF + + chmod 600 "$CONFIG_FILE" +} + +# Get config value +config_get() { + local key="$1" + local default="${2:-}" + + # Expand variable name and get value + if [ -n "${!key}" ]; then + echo "${!key}" + else + echo "$default" + fi +} + +# Set config value +config_set() { + local key="$1" + local value="$2" + + export "$key=$value" +} + +# Initialize config with defaults +config_init() { + # First, try to load .env file + config_load_env + + # Default values (will be overridden by .env if present) + export IGDB_CLIENT_ID="${IGDB_CLIENT_ID:-buyzvv6qoyzj7rmauwkfom79h7fvpx}" + export IGDB_CLIENT_SECRET="${IGDB_CLIENT_SECRET:-tivj7d6b21vqybpb4fx1oe85nffibt}" + export IGDB_REDIRECT_URI="${IGDB_REDIRECT_URI:-http://localhost}" + export IGDB_ACCESS_TOKEN="${IGDB_ACCESS_TOKEN:-}" + export IGDB_TOKEN_EXPIRES="${IGDB_TOKEN_EXPIRES:-}" + export PROGRESS_MODE="${PROGRESS_MODE:-both}" + export LOG_LEVEL="${LOG_LEVEL:-info}" + export RETRY_COUNT="${RETRY_COUNT:-3}" + export POSTER_FILENAME="${POSTER_FILENAME:-poster.png}" + export POSTER_FORMAT="${POSTER_FORMAT:-png}" + export SHOW_3D_LOGO="${SHOW_3D_LOGO:-true}" + export RETRO_MODE="${RETRO_MODE:-true}" + + # Directory settings (from .env or empty) + export SCAN_DIR="${SCAN_DIR:-}" + export TEST_DIR="${TEST_DIR:-}" +} + +# Validate required config +config_validate() { + local errors=0 + + if [ -z "$IGDB_CLIENT_ID" ]; then + echo "Hata: IGDB_CLIENT_ID yapฤฑlandฤฑrฤฑlmamฤฑลŸ." >&2 + errors=$((errors + 1)) + fi + + if [ -z "$IGDB_CLIENT_SECRET" ]; then + echo "Hata: IGDB_CLIENT_SECRET yapฤฑlandฤฑrฤฑlmamฤฑลŸ." >&2 + errors=$((errors + 1)) + fi + + # In test mode, validate TEST_DIR + if [ "$TEST_MODE" = "true" ]; then + if [ -z "$TEST_DIR" ]; then + echo "Hata: TEST modunda TEST_DIR yapฤฑlandฤฑrฤฑlmamฤฑลŸ." >&2 + errors=$((errors + 1)) + fi + fi + + return $errors +} + +# Enable test mode +config_enable_test_mode() { + export TEST_MODE="true" + log_debug "Test modu aktif" +} + +# Get scan directory based on mode +config_get_scan_dir() { + if [ "$TEST_MODE" = "true" ]; then + echo "$TEST_DIR" + else + echo "$SCAN_DIR" + fi +} + +############################################################################### +# LOGGER MODULE - lib/logger.sh +############################################################################### + +# ANSI color codes +export COLOR_RESET='\033[0m' +export COLOR_RED='\033[0;31m' +export COLOR_GREEN='\033[0;32m' +export COLOR_YELLOW='\033[0;33m' +export COLOR_BLUE='\033[0;34m' +export COLOR_MAGENTA='\033[0;35m' +export COLOR_CYAN='\033[0;36m' +export COLOR_WHITE='\033[0;37m' +export COLOR_BOLD='\033[1m' +export COLOR_DIM='\033[2m' + +# Log levels +export LOG_LEVEL_DEBUG=0 +export LOG_LEVEL_INFO=1 +export LOG_LEVEL_WARN=2 +export LOG_LEVEL_ERROR=3 +export LOG_LEVEL_SUCCESS=4 + +# Current log level (default: INFO) +export CURRENT_LOG_LEVEL=$LOG_LEVEL_INFO + +# Internal log function +_log_write() { + local level="$1" + local level_num="$2" + local message="$3" + local color="$4" + + # Check if we should log this level + if [ $level_num -lt $CURRENT_LOG_LEVEL ]; then + return 0 + fi + + local timestamp + timestamp=$(get_timestamp) + + # Format level to 8 characters + local level_formatted + level_formatted=$(printf "%-8s" "$level") + + # Write to log file + echo "[$timestamp] $level_formatted$message" >> "$LOG_FILE" + + # Write to terminal with color + if [ -n "$color" ]; then + echo -e "${color}${level_formatted}${COLOR_RESET} ${message}" + else + echo "${level_formatted} ${message}" + fi +} + +# Log info message +log_info() { + _log_write "INFO" $LOG_LEVEL_INFO "$1" "$COLOR_BLUE" +} + +# Log warning message +log_warn() { + _log_write "WARN" $LOG_LEVEL_WARN "$1" "$COLOR_YELLOW" +} + +# Log error message +log_error() { + _log_write "ERROR" $LOG_LEVEL_ERROR "$1" "$COLOR_RED" +} + +# Log success message +log_success() { + _log_write "SUCCESS" $LOG_LEVEL_SUCCESS "$1" "$COLOR_GREEN" +} + +# Log debug message +log_debug() { + _log_write "DEBUG" $LOG_LEVEL_DEBUG "$1" "$COLOR_DIM" +} + +# Initialize logger (create log file, set permissions) +logger_init() { + # Create log file if it doesn't exist + if [ ! -f "$LOG_FILE" ]; then + touch "$LOG_FILE" + fi + + # Set permissions + chmod 600 "$LOG_FILE" + + # Set log level from config + local log_level_upper + log_level_upper=$(echo "$LOG_LEVEL" | tr '[:lower:]' '[:upper:]') + + case "$log_level_upper" in + DEBUG) + CURRENT_LOG_LEVEL=$LOG_LEVEL_DEBUG + ;; + INFO) + CURRENT_LOG_LEVEL=$LOG_LEVEL_INFO + ;; + WARN) + CURRENT_LOG_LEVEL=$LOG_LEVEL_WARN + ;; + ERROR) + CURRENT_LOG_LEVEL=$LOG_LEVEL_ERROR + ;; + *) + CURRENT_LOG_LEVEL=$LOG_LEVEL_INFO + ;; + esac +} + +# Clear log file +logger_clear() { + > "$LOG_FILE" +} + +# Show recent log entries +logger_tail() { + local lines="${1:-20}" + tail -n "$lines" "$LOG_FILE" +} + +############################################################################### +# UI MODULE - lib/ui.sh +############################################################################### + +# Show 3D ASCII logo +ui_show_logo() { + if [ "$SHOW_3D_LOGO" != "true" ]; then + return 0 + fi + + echo "" + + # Try toilet first (has 3D fonts), then figlet, then simple echo + if [ $HAS_TOILET -eq 1 ]; then + # Try to use a 3D font if available + if toilet -f future "GAME POSTER" 2>/dev/null; then + echo "" + return 0 + elif toilet -f standard "GAME POSTER" 2>/dev/null; then + echo "" + return 0 + fi + fi + + if [ $HAS_FIGLET -eq 1 ]; then + echo -e "${COLOR_CYAN}$(figlet "GAME POSTER" 2>/dev/null)${COLOR_RESET}" + echo "" + return 0 + fi + + # Fallback: simple colored text + echo -e "${COLOR_BOLD}${COLOR_CYAN}" + echo " โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—" + echo " โ•‘ GAME POSTER โ•‘" + echo " โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" + echo -e "${COLOR_RESET}" + echo "" +} + +# Print section header +ui_print_header() { + local text="$1" + local width=50 + + echo "" + echo -e "${COLOR_BOLD}${COLOR_CYAN}$(printf '%*s' "$width" '' | tr ' ' 'โ”')${COLOR_RESET}" + echo -e "${COLOR_BOLD}${COLOR_CYAN} $text${COLOR_RESET}" + echo -e "${COLOR_BOLD}${COLOR_CYAN}$(printf '%*s' "$width" '' | tr ' ' 'โ”')${COLOR_RESET}" + echo "" +} + +# Print colored text +ui_color_print() { + local color="$1" + local text="$2" + + echo -e "${color}${text}${COLOR_RESET}" +} + +# Show progress with step counter and bar +ui_show_progress() { + local current="$1" + local total="$2" + local text="$3" + + # Print step counter + local step_text + step_text=$(printf "[%d/%d] %s" "$current" "$total" "$text") + echo -ne "${COLOR_CYAN}${step_text}${COLOR_RESET} " + + # Show progress bar + ui_progress_bar "$current" "$total" + + echo "" +} + +# Show progress bar only +ui_progress_bar() { + local current="$1" + local total="$2" + local width=${3:-30} + + if [ $total -eq 0 ]; then + total=1 + fi + + local percentage=$((current * 100 / total)) + local filled=$((width * current / total)) + local empty=$((width - filled)) + + echo -ne "${COLOR_MAGENTA}[${COLOR_RESET}" + + # Filled portion + printf "%${filled}s" | tr ' ' 'โ–ˆ' + + # Empty portion + echo -ne "${COLOR_DIM}" + printf "%${empty}s" | tr ' ' 'โ–‘' + echo -ne "${COLOR_RESET}" + + echo -ne "${COLOR_MAGENTA}]${COLOR_RESET} " + + # Percentage + echo -ne "${COLOR_BOLD}${percentage}%${COLOR_RESET}" +} + +# Print status icons +ui_status_success() { + echo -e "${COLOR_GREEN}โœ“${COLOR_RESET} $1" +} + +ui_status_info() { + echo -e "${COLOR_BLUE}โ„น${COLOR_RESET} $1" +} + +ui_status_warn() { + echo -e "${COLOR_YELLOW}โš ${COLOR_RESET} $1" +} + +ui_status_error() { + echo -e "${COLOR_RED}โœ—${COLOR_RESET} $1" +} + +# Print summary box +ui_print_summary() { + local downloaded="$1" + local skipped="$2" + local failed="$3" + + echo "" + ui_print_header "๐Ÿ“Š ร–ZET" + + echo " $(ui_status_success "ฤฐndirildi: $downloaded")" + echo " $(ui_status_info "Atlandฤฑ: $skipped")" + echo " $(ui_status_error "BaลŸarฤฑsฤฑz: $failed")" + echo "" + + local total=$((downloaded + skipped + failed)) + echo -e " ${COLOR_DIM}Toplam oyun: $total${COLOR_RESET}" + echo "" +} + +# Print divider line +ui_print_divider() { + local width=${1:-50} + local char=${2:--} + printf "${COLOR_DIM}%*s${COLOR_RESET}\n" "$width" '' | tr ' ' "$char" +} + +############################################################################### +# SCANNER MODULE - lib/scanner.sh +############################################################################### + +# Store found ISO entries (format: path|directory|name) +declare -a ISO_ENTRIES=() + +# Add an ISO entry to the array +scanner_add_iso_entry() { + local iso_path="$1" + local iso_dir + local game_name + + # Get directory + iso_dir=$(dirname "$iso_path") + + # Extract game name + game_name=$(extract_game_name_from_path "$iso_path") + + # Add to array (format: path|directory|name) + ISO_ENTRIES+=("$iso_path|$iso_dir|$game_name") +} + +# Get ISO entry count +scanner_get_count() { + echo "${#ISO_ENTRIES[@]}" +} + +# Get ISO entry at index +scanner_get_entry() { + local index="$1" + + if [ $index -ge ${#ISO_ENTRIES[@]} ]; then + return 1 + fi + + echo "${ISO_ENTRIES[$index]}" + return 0 +} + +# Check if directory has poster.png +scanner_has_poster() { + local directory="$1" + local poster_file="$directory/$POSTER_FILENAME" + + if [ -f "$poster_file" ]; then + return 0 # Has poster + else + return 1 # No poster + fi +} + +# Get poster file path for a directory +scanner_get_poster_path() { + local directory="$1" + echo "$directory/$POSTER_FILENAME" +} + +# Scan from config (uses SCAN_DIR or TEST_DIR based on mode) +scanner_scan_from_config() { + local scan_dir + scan_dir="$(config_get_scan_dir)" + + if [ -z "$scan_dir" ]; then + log_error "Taranacak dizin belirtilmemiลŸ (SCAN_DIR veya TEST_DIR)" + return 1 + fi + + if [ ! -d "$scan_dir" ]; then + log_error "Taranacak dizin bulunamadฤฑ: $scan_dir" + return 1 + fi + + # Log the mode + if [ "$TEST_MODE" = "true" ]; then + log_info "TEST MODU aktif" + fi + + log_info "Taranan dizin: $scan_dir" + + # Clear previous results + ISO_ENTRIES=() + + # Find subdirectories in scan directory + local subdirs=() + while IFS= read -r -d '' dir; do + # Skip poster_bash directory + local dir_name + dir_name=$(basename "$dir") + if [[ "$dir_name" != "poster_bash" ]] && [[ "$dir_name" != "poster-bash" ]]; then + subdirs+=("$dir") + fi + done < <(find "$scan_dir" -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null) + + # Check each subdirectory for ISO files + for subdir in "${subdirs[@]}"; do + while IFS= read -r -d '' iso_file; do + scanner_add_iso_entry "$iso_file" + done < <(find "$subdir" -type f \( -iname "*.iso" \) -print0 2>/dev/null) + done + + # Also check scan directory for ISO files + while IFS= read -r -d '' iso_file; do + scanner_add_iso_entry "$iso_file" + done < <(find "$scan_dir" -maxdepth 1 -type f \( -iname "*.iso" \) -print0 2>/dev/null) + + log_info "Bulunan ISO dosyasฤฑ sayฤฑsฤฑ: ${#ISO_ENTRIES[@]}" + return 0 +} + +############################################################################### +# AUTH MODULE - lib/auth.sh +############################################################################### + +# Calculate Unix timestamp for expiration +get_timestamp_unix() { + date +%s +} + +# Check if token is expired +auth_is_expired() { + local expires="$1" + + if [ -z "$expires" ]; then + return 0 # No expiration = expired + fi + + local now + now=$(get_timestamp_unix) + + # Add 60 second buffer + local expires_with_buffer=$((expires - 60)) + + [ $now -ge $expires_with_buffer ] +} + +# Authenticate and get access token +auth_authenticate() { + log_info "IGDB API'ye baฤŸlanฤฑlฤฑyor..." + + local response + local http_code + + # Make OAuth request + response=$(curl -s -w "\n%{http_code}" \ + --connect-timeout "$TIMEOUT_SECONDS" \ + -X POST "$IGDB_OAUTH_URL" \ + -d "client_id=$IGDB_CLIENT_ID" \ + -d "client_secret=$IGDB_CLIENT_SECRET" \ + -d "grant_type=client_credentials" \ + 2>&1) + + # Extract HTTP code (last line) + http_code=$(echo "$response" | tail -n1) + + # Extract body (everything except last line) + response=$(echo "$response" | head -n -1) + + if [ "$http_code" != "200" ]; then + log_error "OAuth baลŸarฤฑsฤฑz: HTTP $http_code" + log_error "Response: $response" + return 1 + fi + + # Parse JSON response + local access_token + local expires_in + + access_token=$(echo "$response" | jq -r '.access_token // empty') + expires_in=$(echo "$response" | jq -r '.expires_in // empty') + + if [ -z "$access_token" ]; then + log_error "Token alฤฑnamadฤฑ" + return 1 + fi + + # Calculate expiration timestamp + local now + local expires_timestamp + now=$(get_timestamp_unix) + expires_timestamp=$((now + expires_in)) + + # Save to environment + export IGDB_ACCESS_TOKEN="$access_token" + export IGDB_TOKEN_EXPIRES="$expires_timestamp" + + # Save to config + config_save + + log_success "OAuth baลŸarฤฑlฤฑ, token alฤฑndฤฑ" + return 0 +} + +# Get valid access token (refresh if needed) +auth_get_token() { + # Check if we have a token + if [ -z "$IGDB_ACCESS_TOKEN" ]; then + log_debug "Token bulunamadฤฑ, yeni token alฤฑnฤฑyor..." + auth_authenticate + return $? + fi + + # Check if token is expired + if auth_is_expired "$IGDB_TOKEN_EXPIRES"; then + log_debug "Token sรผresi doldu, yenileniyor..." + auth_authenticate + return $? + fi + + # Token is valid + log_debug "Mevcut token geรงerli" + return 0 +} + +# Clear token from memory and config +auth_clear_token() { + export IGDB_ACCESS_TOKEN="" + export IGDB_TOKEN_EXPIRES="" + config_save +} + +############################################################################### +# IGDB MODULE - lib/igdb.sh +############################################################################### + +# Search for a game by name +igdb_search_game() { + local game_name="$1" + + log_debug "IGDB'de aranฤฑyor: $game_name" + + # Ensure we have a valid token + if ! auth_get_token; then + return 1 + fi + + local response + local http_code + + # Build APICalc query + local query + query=$(printf 'search "%s"; fields name,cover; limit 1;' "$game_name") + + # Make API request + response=$(curl -s -w "\n%{http_code}" \ + --connect-timeout "$TIMEOUT_SECONDS" \ + -X POST "$IGDB_GAMES_URL" \ + -H "Authorization: Bearer $IGDB_ACCESS_TOKEN" \ + -H "Client-ID: $IGDB_CLIENT_ID" \ + -H "Accept: application/json" \ + -d "$query" \ + 2>&1) + + http_code=$(echo "$response" | tail -n1) + response=$(echo "$response" | head -n -1) + + case "$http_code" in + 200) + # Parse response + local first_result + first_result=$(echo "$response" | jq '.[0] // empty') + + if [ -z "$first_result" ] || [ "$first_result" = "null" ]; then + log_debug "Oyun bulunamadฤฑ: $game_name" + return 1 + fi + + # Extract game info + local game_id + local name + local cover_id + + game_id=$(echo "$response" | jq -r '.[0].id // empty') + name=$(echo "$response" | jq -r '.[0].name // empty') + cover_id=$(echo "$response" | jq -r '.[0].cover // empty') + + log_debug "Bulundu: $name (ID: $game_id, Cover: $cover_id)" + + # Export results + export IGDB_LAST_GAME_ID="$game_id" + export IGDB_LAST_GAME_NAME="$name" + export IGDB_LAST_COVER_ID="$cover_id" + + return 0 + ;; + 401) + log_error "Yetkilendirme hatasฤฑ, token yenileniyor..." + auth_clear_token + auth_get_token + # Retry once + igdb_search_game "$game_name" + return $? + ;; + 429) + log_warn "API rate limit aลŸฤฑmฤฑ, biraz bekleniyor..." + sleep 2 + igdb_search_game "$game_name" + return $? + ;; + *) + log_error "API hatasฤฑ: HTTP $http_code" + log_debug "Response: $response" + return 1 + ;; + esac +} + +# Get cover URL for a cover ID +igdb_get_cover_url() { + local cover_id="$1" + + if [ -z "$cover_id" ]; then + log_debug "Cover ID boลŸ" + return 1 + fi + + log_debug "Cover bilgisi alฤฑnฤฑyor: ID $cover_id" + + # Ensure we have a valid token + if ! auth_get_token; then + return 1 + fi + + local response + local http_code + + # Build query + local query + query=$(printf 'fields url,image_id; where id = %s;' "$cover_id") + + # Make API request + response=$(curl -s -w "\n%{http_code}" \ + --connect-timeout "$TIMEOUT_SECONDS" \ + -X POST "$IGDB_COVERS_URL" \ + -H "Authorization: Bearer $IGDB_ACCESS_TOKEN" \ + -H "Client-ID: $IGDB_CLIENT_ID" \ + -H "Accept: application/json" \ + -d "$query" \ + 2>&1) + + http_code=$(echo "$response" | tail -n1) + response=$(echo "$response" | head -n -1) + + case "$http_code" in + 200) + local image_id + image_id=$(echo "$response" | jq -r '.[0].image_id // empty') + + if [ -z "$image_id" ] || [ "$image_id" = "null" ]; then + log_debug "Image ID bulunamadฤฑ" + return 1 + fi + + # Build full URL + local cover_url="${IGDB_IMAGE_BASE}/${image_id}.jpg" + + log_debug "Cover URL: $cover_url" + + export IGDB_LAST_COVER_URL="$cover_url" + return 0 + ;; + *) + log_error "Cover API hatasฤฑ: HTTP $http_code" + return 1 + ;; + esac +} + +# Full game search with cover URL +igdb_search_and_get_cover() { + local game_name="$1" + + # Search for game + if ! igdb_search_game "$game_name"; then + return 1 + fi + + # Get cover URL + if [ -n "$IGDB_LAST_COVER_ID" ]; then + igdb_get_cover_url "$IGDB_LAST_COVER_ID" + return $? + else + log_debug "Bu oyunun cover'ฤฑ yok" + return 1 + fi +} + +# Full game search with VR fallback and cover URL +igdb_search_with_vr_fallback() { + local game_name="$1" + + # Try normal search first + if igdb_search_and_get_cover "$game_name"; then + return 0 + fi + + # Try with VR suffix + if igdb_search_and_get_cover "${game_name} VR"; then + return 0 + fi + + # Nothing found + return 1 +} + +############################################################################### +# DOWNLOADER MODULE - lib/downloader.sh +############################################################################### + +# Clean all posters from test directory before test run +downloader_clean_test_posters() { + local test_dir="$1" + + if [ ! -d "$test_dir" ]; then + log_warn "Test dizini bulunamadฤฑ: $test_dir" + return 1 + fi + + log_info "Test posterleri temizleniyor: $test_dir" + + local count=0 + local posters + posters=$(find "$test_dir" -type f -name "$POSTER_FILENAME" 2>/dev/null) + + if [ -z "$posters" ]; then + log_info "Temizlenecek poster bulunamadฤฑ" + return 0 + fi + + # Remove each poster + while IFS= read -r poster; do + if [ -f "$poster" ]; then + rm -f "$poster" + count=$((count + 1)) + log_debug "Silindi: $poster" + fi + done <<< "$posters" + + log_info "Temizlenen poster sayฤฑsฤฑ: $count" + return 0 +} + +# Check if poster already exists +downloader_check_if_exists() { + local poster_path="$1" + + if [ -f "$poster_path" ]; then + log_debug "Poster zaten var: $poster_path" + return 0 # Exists + else + return 1 # Does not exist + fi +} + +# Download poster from URL +downloader_download_poster() { + local url="$1" + local output_path="$2" + + log_debug "ฤฐndiriliyor: $url" + log_debug "Kayฤฑt: $output_path" + + # Download with curl + local response + local http_code + + response=$(curl -s -w "\n%{http_code}" \ + --connect-timeout "$TIMEOUT_SECONDS" \ + --max-time 60 \ + -L \ + -o "$output_path" \ + "$url" \ + 2>&1) + + http_code=$(echo "$response" | tail -n1) + + case "$http_code" in + 200) + # Verify file size + local file_size + file_size=$(stat -f%z "$output_path" 2>/dev/null || stat -c%s "$output_path" 2>/dev/null || echo "0") + + if [ "$file_size" -lt 1000 ]; then + log_warn "ฤฐndirilen dosya รงok kรผรงรผk ($file_size bytes), siliniyor" + rm -f "$output_path" + return 1 + fi + + log_debug "ฤฐndirme baลŸarฤฑlฤฑ ($(format_bytes "$file_size"))" + return 0 + ;; + 404) + log_debug "Poster bulunamadฤฑ (404)" + rm -f "$output_path" + return 1 + ;; + *) + log_warn "ฤฐndirme baลŸarฤฑsฤฑz: HTTP $http_code" + rm -f "$output_path" + return 1 + ;; + esac +} + +# Download with exponential backoff retry +downloader_retry_with_backoff() { + local url="$1" + local output_path="$2" + local max_retries="${3:-$RETRY_COUNT}" + + local attempt=0 + local wait_time=1 + + while [ $attempt -lt $max_retries ]; do + attempt=$((attempt + 1)) + + log_debug "Deneme $attempt/$max_retries" + + if downloader_download_poster "$url" "$output_path"; then + return 0 + fi + + # Wait before retry (exponential backoff: 1s, 2s, 4s...) + if [ $attempt -lt $max_retries ]; then + log_debug "$wait_time saniye bekleniyor..." + sleep "$wait_time" + wait_time=$((wait_time * 2)) + fi + done + + # All retries failed + log_error "ฤฐndirme baลŸarฤฑsฤฑz ($max_retries deneme)" + return 1 +} + +# Process a single game entry +downloader_process_game() { + local entry="$1" + + # Parse entry + local iso_path + local directory + local game_name + + IFS='|' read -r iso_path directory game_name <<< "$entry" + + log_info "ฤฐลŸleniyor: $game_name" + + # Check if poster already exists + local poster_path + poster_path="${directory}/${POSTER_FILENAME}" + + if downloader_check_if_exists "$poster_path"; then + log_info " โ†’ Poster zaten var, atlanฤฑyor" + return 2 # Skipped + fi + + # Search IGDB for cover URL + if ! igdb_search_with_vr_fallback "$game_name"; then + log_warn " โ†’ Oyun IGDB'de bulunamadฤฑ: $game_name" + return 1 # Not found + fi + + # Download poster + if downloader_retry_with_backoff "$IGDB_LAST_COVER_URL" "$poster_path"; then + log_success " โ†’ Poster indirildi: $poster_path" + return 0 # Success + else + log_error " โ†’ Poster indirilemedi" + return 1 # Failed + fi +} + +# Process all games with progress display +downloader_process_all() { + local total_games + total_games=$(scanner_get_count) + + if [ "$total_games" -eq 0 ]; then + log_warn "Hiรง oyun bulunamadฤฑ" + return 0 + fi + + local downloaded=0 + local skipped=0 + local failed=0 + + log_info "$total_games oyun iลŸlenecek" + + # Process each game + for ((i = 0; i < total_games; i++)); do + local entry + entry=$(scanner_get_entry "$i") + + if [ -z "$entry" ]; then + continue + fi + + # Parse for game name + local game_name + game_name=$(echo "$entry" | cut -d'|' -f3) + + # Show progress + ui_show_progress $((i + 1)) "$total_games" "$game_name" + + # Process the game (ignore return code to prevent exit on set -e) + downloader_process_game "$entry" || true + local result=$? + + case $result in + 0) downloaded=$((downloaded + 1)) ;; + 2) skipped=$((skipped + 1)) ;; + *) failed=$((failed + 1)) ;; + esac + done + + # Show summary + ui_print_summary "$downloaded" "$skipped" "$failed" + + return 0 +} + +############################################################################### +# MAIN APPLICATION +############################################################################### + +# Print usage +show_usage() { + cat << EOF +${COLOR_BOLD}Kullanฤฑm:${COLOR_RESET} + poster-bash [SEร‡ENEKLER] + +${COLOR_BOLD}Seรงenekler:${COLOR_RESET} + -h, --help Bu yardฤฑm mesajฤฑnฤฑ gรถsterir + -v, --version Versiyon bilgisini gรถsterir + -c, --config Yapฤฑlandฤฑrma dosyasฤฑnฤฑn konumunu gรถsterir + -l, --log Log dosyasฤฑnฤฑ gรถsterir + --clear-log Log dosyasฤฑnฤฑ temizler + --init Yapฤฑlandฤฑrma dosyasฤฑnฤฑ yeniden oluลŸturur + --test-run Test modunda รงalฤฑลŸฤฑr (TEST_DIR kullanฤฑr) + +${COLOR_BOLD}ร–rnekler:${COLOR_RESET} + poster-bash # Config'deki SCAN_DIR'yi tarar + poster-bash --test-run # Test modunda TEST_DIR'yi tarar + poster-bash --log # Son loglarฤฑ gรถster + +${COLOR_BOLD}.env Dosyasฤฑ:${COLOR_RESET} + Script ile aynฤฑ dizinde .env dosyasฤฑ oluลŸturun: + + IGDB_CLIENT_ID=your_client_id + IGDB_CLIENT_SECRET=your_client_secret + SCAN_DIR="/path/to/games" + TEST_DIR="/test-games" + +${COLOR_BOLD}GitHub:${COLOR_RESET} + https://github.com/wisecolt/poster-bash +EOF +} + +# Show version +show_version() { + echo "${COLOR_BOLD}${SCRIPT_NAME}${COLOR_RESET} v${SCRIPT_VERSION}" + echo "Bash oyun posteri indirici" +} + +# Initialize the application +app_init() { + # Show logo + ui_show_logo + + # Check dependencies + check_dependencies + + # Initialize config (loads .env if present) + config_init + + # Load config file if exists + if config_load; then + log_debug "Config dosyasฤฑ yรผklendi" + else + log_debug "Config dosyasฤฑ yok, varsayฤฑlanlar kullanฤฑlฤฑyor" + config_save + fi + + # Validate config + if ! config_validate; then + echo "" + echo "Hata: Gerekli yapฤฑlandฤฑrma eksik." >&2 + echo "Lรผtfen .env dosyasฤฑnฤฑ kontrol edin veya config dosyasฤฑnฤฑ dรผzenleyin: $CONFIG_FILE" >&2 + exit $EXIT_ERROR_CONFIG + fi + + # Initialize logger + logger_init + + # Check internet + check_internet + + log_info "BaลŸlatฤฑldฤฑ" +} + +# Main function +main() { + # Track if we're in test mode + local test_mode=false + + # Parse command line arguments + case "${1:-}" in + -h|--help|help) + show_usage + exit $EXIT_SUCCESS + ;; + -v|--version|version) + show_version + exit $EXIT_SUCCESS + ;; + -c|--config) + echo "Config: ${CONFIG_FILE}" + if [ -f "$CONFIG_FILE" ]; then + echo "" + cat "$CONFIG_FILE" + else + echo "Config dosyasฤฑ bulunamadฤฑ" + fi + exit $EXIT_SUCCESS + ;; + -l|--log) + echo "Log: ${LOG_FILE}" + if [ -f "$LOG_FILE" ]; then + echo "" + logger_tail 20 + else + echo "Log dosyasฤฑ bulunamadฤฑ" + fi + exit $EXIT_SUCCESS + ;; + --clear-log) + logger_clear + echo "Log dosyasฤฑ temizlendi" + exit $EXIT_SUCCESS + ;; + --init) + config_init + config_save + echo "Config dosyasฤฑ oluลŸturuldu: ${CONFIG_FILE}" + exit $EXIT_SUCCESS + ;; + --test-run) + test_mode=true + ;; + esac + + # Initialize application + app_init + + # Enable test mode if requested + if [ "$test_mode" = true ]; then + config_enable_test_mode + fi + + # Get scan directory from config + local scan_dir + scan_dir="$(config_get_scan_dir)" + + if [ -z "$scan_dir" ]; then + ui_status_error "Taranacak dizin belirtilmemiลŸ" + echo "" + echo "Lรผtfen .env dosyasฤฑnda ลŸunlarฤฑ tanฤฑmlayฤฑn:" + echo " SCAN_DIR=\"/path/to/games\" # Normal kullanฤฑm" + echo " TEST_DIR=\"/test-games\" # Test modu iรงin" + exit $EXIT_ERROR_CONFIG + fi + + # Show scanning info + ui_print_header "๐Ÿ“‚ Dizin Taramasฤฑ" + echo -e " ${COLOR_DIM}Mod:${COLOR_RESET} $([ "$TEST_MODE" = "true" ] && echo "TEST" || echo "Normal")" + echo -e " ${COLOR_DIM}Dizin:${COLOR_RESET} $scan_dir" + echo "" + + # Clean old posters in test mode before starting + if [ "$TEST_MODE" = "true" ]; then + log_info "Test modu: Eski posterler temizleniyor..." + downloader_clean_test_posters "$scan_dir" + echo "" + fi + + # Scan from config + if ! scanner_scan_from_config; then + ui_status_error "Dizin tarama baลŸarฤฑsฤฑz" + exit $EXIT_ERROR_GENERAL + fi + + local game_count + game_count=$(scanner_get_count) + + if [ "$game_count" -eq 0 ]; then + ui_status_warn "Bu dizinde .iso dosyasฤฑ bulunamadฤฑ" + log_info "ฤฐลŸlenecek oyun yok" + exit $EXIT_SUCCESS + fi + + echo -e " ${COLOR_GREEN}โœ“${COLOR_RESET} Bulunan oyun: ${COLOR_BOLD}$game_count${COLOR_RESET}" + echo "" + + # Authenticate with IGDB + ui_print_header "๐Ÿ” IGDB BaฤŸlantฤฑsฤฑ" + if ! auth_get_token; then + ui_status_error "IGDB baฤŸlantฤฑsฤฑ baลŸarฤฑsฤฑz" + exit $EXIT_ERROR_AUTH + fi + ui_status_success "IGDB baฤŸlantฤฑsฤฑ baลŸarฤฑlฤฑ" + echo "" + + # Process all games + ui_print_header "๐ŸŽฎ Poster ฤฐndirme" + downloader_process_all + + # Done + log_info "Tamamlandฤฑ" + + ui_print_divider 50 "โ”" + echo -e " ${COLOR_DIM}Log: ${LOG_FILE}${COLOR_RESET}" + echo "" +} + +# Run main function +main "$@" diff --git a/test-games/Crisis VRigade 2/Crisis VRigade 2.iso b/test-games/Crisis VRigade 2/Crisis VRigade 2.iso new file mode 100644 index 0000000..e69de29 diff --git a/test-games/Crisis VRigade 2/poster.png b/test-games/Crisis VRigade 2/poster.png new file mode 100644 index 0000000000000000000000000000000000000000..0bbdff1ae75b14e8b2be5e361cc11870fc2bb50a GIT binary patch literal 10807 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 Mut?ye_qO)`0K==L&j0`b literal 0 HcmV?d00001 diff --git a/test-games/Cyberpunk 2077/Cyberpunk 2077.iso b/test-games/Cyberpunk 2077/Cyberpunk 2077.iso new file mode 100644 index 0000000..e69de29 diff --git a/test-games/Cyberpunk 2077/poster.png b/test-games/Cyberpunk 2077/poster.png new file mode 100644 index 0000000000000000000000000000000000000000..e60b5abe4ed9c4b4b154f35c2964db2ae48062dd GIT binary patch literal 22329 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 T0_`>8KbHMK9_6)g-jn~?p<#UT literal 0 HcmV?d00001 diff --git a/test-games/Elden Ring/Elden Ring.iso b/test-games/Elden Ring/Elden Ring.iso new file mode 100644 index 0000000..e69de29 diff --git a/test-games/Elden Ring/poster.png b/test-games/Elden Ring/poster.png new file mode 100644 index 0000000000000000000000000000000000000000..287cb60ec442c24973925a50c7b9e0ec0ab859ae GIT binary patch literal 16188 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 l%p0lN;ktvPF5X`*)Xh literal 0 HcmV?d00001 diff --git a/test-games/Guns n Stories Bulletproof/Guns n Stories Bulletproof.iso b/test-games/Guns n Stories Bulletproof/Guns n Stories Bulletproof.iso new file mode 100644 index 0000000..e69de29 diff --git a/test-games/Guns n Stories Bulletproof/poster.png b/test-games/Guns n Stories Bulletproof/poster.png new file mode 100644 index 0000000000000000000000000000000000000000..f0de2cf813f1778026283fafb6e45de5e6f3e8ec GIT binary patch literal 26357 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 z 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 BkR|{C literal 0 HcmV?d00001 diff --git a/test-games/Richies Plank Experience/Richies Plank Experience.iso b/test-games/Richies Plank Experience/Richies Plank Experience.iso new file mode 100644 index 0000000..e69de29 diff --git a/test-games/Richies Plank Experience/poster.png b/test-games/Richies Plank Experience/poster.png new file mode 100644 index 0000000000000000000000000000000000000000..3d4b3ad2c9d86525275cbdd250f4a3c695385201 GIT binary patch literal 23363 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 literal 0 HcmV?d00001