commit f62e4cd769a0f8d7e0bd7466274f074592e9aef2 Author: wisecolt Date: Wed Feb 4 17:49:12 2026 +0300 first commit 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 0000000..0bbdff1 Binary files /dev/null and b/test-games/Crisis VRigade 2/poster.png differ 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 0000000..e60b5ab Binary files /dev/null and b/test-games/Cyberpunk 2077/poster.png differ 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 0000000..287cb60 Binary files /dev/null and b/test-games/Elden Ring/poster.png differ 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 0000000..f0de2cf Binary files /dev/null and b/test-games/Guns n Stories Bulletproof/poster.png differ 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 0000000..3d4b3ad Binary files /dev/null and b/test-games/Richies Plank Experience/poster.png differ