﻿# Changelog

## [5.5.0] - 2026-07-14

### Added
- **Anti-detection: TLS fingerprint impersonation** — ganti `requests` dengan `curl_cffi` untuk menghindari deteksi TLS fingerprint oleh Cloudflare/Akamai
  - 6 profil TLS rotation: `chrome`, `chrome110`, `chrome116`, `chrome120`, `safari15_5`, `edge99`
  - Setiap session mendapat profil TLS acak yang meniru browser nyata di level handshake
- **User-Agent rotation** — 12 profil browser realistis (Chrome, Edge, Firefox, Safari, Opera, Vivaldi di Windows/macOS/Linux)
  - Setiap request mendapat UA + `Sec-Ch-Ua` + `Sec-Ch-Ua-Platform` yang konsisten
  - Mendukung fixed UA mode (backward compatible) jika `user_agent` di-pass ke constructor
- **Exponential backoff untuk 429/403** — `_request_with_backoff()` dengan retry otomatis
  - Backoff formula: `2^attempt + random(1-5)` detik
  - Session di-reset (fresh cookies + TLS profile baru) setiap retry
  - Configurable `max_retries` (default: 3)
- **`reset_session()` method** — tutup session lama, buat baru dengan TLS profile berbeda
- **Fresh epoch per-request** — timestamp `time_epoch` di-generate ulang setiap request, bukan reuse nilai lama

### Changed
- `pinscrape/v2.py` — migrasi dari `requests.Session` ke `curl_cffi.requests.Session` dengan `impersonate`
- `pinscrape/v2.py` — sleep pattern berubah dari fixed delay ke **jitter** `random.uniform(min, max)`
  - Default: `(1.5, 4.0)` detik (configurable via `sleep_time` tuple atau float)
  - Mendukung backward compatibility: float → tambah jitter 0-1.5s, tuple → `uniform(min, max)`
- `scripts/scrape.py` — outer retry loop sekarang pakai jitter `random.uniform(3, 8)` + `reset_session()` antar attempt
- `scripts/scrape.py` — default `sleep_time` dari `0.5` ke `(1.5, 4.0)`
- `requirements.txt` — `requests~=2.32.4` diganti `curl_cffi>=0.15.0`

### Removed
- **`requests` library** — tidak lagi digunakan, digantikan `curl_cffi`
- **`urllib3.util.retry.Retry`** — retry logic dipindah ke `_request_with_backoff()` manual

### Security
- TLS fingerprint sekarang meniru browser Chrome/Firefox/Safari asli, bukan Python urllib3
- Setiap request terlihat seperti browser berbeda di level TLS + HTTP headers + timing

## [5.4.0] - 2026-07-07

### Added
- **Go HTML Exporter** (`go/`) — alternative exporter menggunakan Go yang menghasilkan output HTML identik dengan PHP exporter
  - Goroutines + channels untuk parallel export (lebih cepat, lebih efisien RAM)
  - `bufio.Writer` (64KB buffer) untuk file I/O
  - Pure Go SQLite (`modernc.org/sqlite`) — tanpa CGO/GCC dependency
  - Single static binary (~6.5MB), bisa dijalankan tanpa runtime dependency
  - `--interactive` mode dengan prompt split size, template list, workers
  - `--list-templates` flag untuk listing template dari CLI
  - `--split-size`, `--template`, `--workers`, `--create-csv` CLI flags
  - Auto-detect project root dari lokasi executable atau working directory
  - Path resolution yang konsisten dari project root
- **24 Go templates** (`templates/*.gohtml`) — konversi dari PHP templates, output HTML identik
  - Semua template PHP yang ada tidak diubah atau dihapus
  - `renderCompleteArticle` function di Go mereplikasi logika `render_complete_article` PHP
  - `htmlEscape`, `safe`, `truncateImages`, `truncateArticles` template functions
- **`go/template_test.go`** — unit tests untuk template parsing dan rendering
- **CLI menu additions** (`run.bat`):
  - Menu 7: Export (Go - Fast) dengan interactive mode
  - Menu 10: Full Deploy Pipeline (Go) — Generate CREATE.csv + Export + Deploy + Submit
  - `full-deploy-generate` command di `scripts/cli.py` untuk generate CREATE.csv dengan split_size arg
  - `--auto` flag support untuk `deploy` dan `submit` commands di `scripts/cli.py`

### Changed
- `run.bat` — restructure dari `if-else if` parenthesized blocks ke `goto :label` pattern untuk menghindari force close
- `run.bat` — gunakan `enabledelayedexpansion` dengan `!variable!` syntax konsisten
- `scripts/cli.py` — tambah `full-deploy-generate`, `deploy --auto`, `submit --auto` commands

### Fixed
- **Nil pointer pada `.MainImage`** — template crimson.gohtml, dark-stone.gohtml, dan template lain yang akses `.MainImage.ImageURL` crash saat images ada tapi `MainImage` tidak di-set
- **Goroutine deadlock** di parallel export — `sitemapChan` dan `errChan` sekarang di-drain concurrently via goroutine terpisah
- **Schema JSON double-encoded** — ganti `html/template` ke `text/template` karena `html/template` melakukan context-aware escaping di dalam `<script>` tag

### Performance
- Go exporter dengan 4 workers: estimasi **2-3x lebih cepat** dari PHP untuk database besar
- Memory usage lebih rendah: Go GC vs PHP `2048M` memory limit
- Channel-based streaming: artikel di-process tanpa load semua ke memory sekaligus

## [5.3.1] - 2026-07-04

### Fixed
- **Worker OOM crash** — worker process tidak inherit `memory_limit` dari parent process. Tambah `ini_set('memory_limit', '2048M')` dan `set_time_limit(0)` di `php/worker.php`
- **Windows `proc_open()` compatibility** — gunakan array command format (PHP 7.4+) untuk menghindari masalah shell quoting, backslash escaping, dan path separator di Windows
- **`getopt()` unreliable untuk long options dengan hyphen** di Windows PHP 7.4 — ganti dengan manual `$argv` parsing di `php/worker.php`
- **Path separator** — normalisasi semua path ke `DIRECTORY_SEPARATOR` di `php/worker.php` dan `php/exporter.php`
- **Debug display** — perbaiki tampilan cmd di error output agar menampilkan command per-worker, bukan command terakhir dari loop

## [5.3.0] - 2026-07-03

### Added
- **Multi-process parallel export** via `proc_open()` — export kini dapat memanfaatkan multi-core CPU untuk rendering paralel
  - Konfigurasi `worker_count` di `config.json` (default: 4, recommended 4-6)
  - CLI override `--workers=N` pada `php exporter.php`
  - Fallback otomatis ke sequential jika `worker_count=1` atau artikel terlalu sedikit
- `php/worker.php` — standalone worker script yang menerima batch JSON, merender artikel, dan menulis `.html` + result JSON
- Pre-compute `related_articles` dan `first_articles` di main process sebelum distribusi ke workers

### Changed
- `php/exporter.php` — refactor `run()` menjadi dispatch ke `runSequential()` atau `runParallel()`
- `php/exporter.php` — tambah `runParallel()` dengan proc_open multi-process architecture
- `php/exporter.php` — tambah `preComputeRelated()` dan `preComputeFirstArticles()` untuk metadata pre-computation
- `php/exporter.php` — tambah `--workers` CLI option
- `config.json` — tambah field `worker_count`

### Performance
- Dengan 4 workers di 8-core: estimasi **3-4x lebih cepat** untuk database besar
- Zero overhead untuk `worker_count=1` (menggunakan sequential path)

## [5.2.1] - 2026-07-03

### Fixed
- **Out of Memory saat export database besar** — optimasi memory pada `php/exporter.php` dan `php/template.php` untuk mengatasi crash OOM pada database berukuran 2.5GB+

### Changed
- `php/exporter.php` — naikkan `memory_limit` dari `512M` menjadi `2048M`
- `php/exporter.php` — filter artikel kosong dipindahkan dari PHP ke SQL query di `streamArticles()` menghilangkan `json_decode` overhead di generator
- `php/exporter.php` — tambah `->finalize()` dan `->close()` eksplisit per batch di `streamArticles()` untuk me-release SQLite resources
- `php/exporter.php` — tambah `unset()` untuk intermediate data setelah `file_put_contents` di `processArticle()` agar data besar di-free lebih awal
- `php/exporter.php` — tambah `gc_collect_cycles()` setiap 100 artikel selama proses chunk
- `php/template.php` — hapus `array_merge()` dan recursive `render()` pada layout rendering. Layout kini di-include langsung karena semua variabel sudah tersedia di scope method setelah `extract()`
- `php/template.php` — tambah `unset()` setelah `extract()` untuk free array container

### Output Compatibility
- Semua perubahan **tidak mengubah output HTML** — file `.html`, `sitemap.xml`, `index.html`, dan `robots.txt` yang dihasilkan identik dengan versi sebelumnya

## [5.2.0] - 2026-07-02

### Added
- **Fallback models** untuk AI title dan article generator
  - Konfigurasi `fallback_models` di `config.json` — daftar model cadangan yang dicoba berurutan saat model utama gagal
  - Mendukung format string sederhana (`google/gemma-2-9b-it`) atau objek dengan `base_url`/`api_key` terpisah per model
  - Fungsi `build_model_chain()` di `scripts/ai_generator.py` untuk membangun rantai model dari config
- **Retry logic** per model sebelum fallback ke model berikutnya
  - Konfigurasi `max_retries` (default: 2) — jumlah percobaan ulang per model
  - Konfigurasi `retry_delay` (default: 5) — detik delay antar retry dengan pola eksponensial (`delay x attempt`)
- **Error classification** yang membedakan:
  - Rate limit (429) -> retry + fallback
  - Server error (500/502/503/504) -> retry + fallback
  - Timeout -> retry + fallback
  - Error lain (auth, invalid request) -> langsung skip ke model berikutnya tanpa retry
- **Startup info** — mencetak model chain dan pengaturan retry saat AI generation dimulai

### Changed
- `config.json` — section `ai` ditambah field `fallback_models`, `max_retries`, `retry_delay`
- `scripts/ai_generator.py` — refaktor `generate_content()` dan `process_db_ai()` untuk mendukung model chain dan retry logic
