@irithell-js/akinator
v1.0.0
Published
Akinator game engine
Maintainers
Readme
@irithell-js/akinator
Puppeteer-based programmatic wrapper for the Akinator web game. It extracts game states via DOM evaluation, bypasses mandatory modals, and manages concurrent sessions.
Designed with explicit support for Termux/Android environments using custom bindings for Chromium and SQLite.
Termux & Android Compatibility
Standard Node.js packages relying on pre-compiled binaries (puppeteer, better-sqlite3) fail in Termux due to missing glibc and non-standard filesystem hierarchies.
This module natively resolves these environment constraints:
- Uses
@irithell-js/puppeteer-termuxto automatically locate the Android Chromium binary and disable incompatible sandbox flags. - Uses
@irithell-js/better-sqlite3-termuxto provide N-API bindings compiled specifically for Termux architectures.
On standard Linux/Windows hosts, it defaults to standard execution paths.
Installation
npm install @irithell-js/akinatorMulti-Language / Region Support
The engine natively supports 17 regions/languages by routing the Puppeteer instance to the respective localized Akinator subdomains. The initial loading state is automatically translated based on the selected region.
const engine = new AkinatorEngine(storage, {
region: "en", // Initializes the session in English
headless: "new",
});Usage Examples
The package supports both ECMAScript Modules (ESM) and CommonJS (CJS).
TypeScript / ESM
import { AkinatorEngine, SQLiteStorage } from "@irithell-js/akinator";
const storage = new SQLiteStorage("akinator_sessions.db");
const engine = new AkinatorEngine(storage, { region: "pt", headless: "new" });
async function play(sessionId: string) {
const startRes = await engine.start(sessionId);
if (!startRes.ok) throw new Error(startRes.reason);
console.log(`[Step ${startRes.step}] ${startRes.question}`);
// Answer: 0 (Yes), 1 (No), 2 (Don't Know), 3 (Probably), 4 (Probably Not)
const answerRes = await engine.answer(sessionId, 0);
if (!answerRes.ok) throw new Error(answerRes.reason);
if (answerRes.isWin) {
console.log("Result:", answerRes.result?.name);
await engine.end(sessionId);
}
}
play("session-uuid-1");CommonJS
const { AkinatorEngine, JSONStorage } = require("@irithell-js/akinator");
const storage = new JSONStorage("./sessions-dir");
const engine = new AkinatorEngine(storage, { maxSessions: 10 });
async function play(sessionId) {
const startRes = await engine.start(sessionId);
if (startRes.ok) {
console.log(startRes.question);
}
}
play("session-uuid-2");Architecture & API
AkinatorEngine
The primary facade class. It orchestrates the flow between the storage provider and the Puppeteer scraper. It implements a MutexMap internally, ensuring that multiple async calls for the same sessionId are queued and processed sequentially to prevent Puppeteer context crashes.
start(sessionId: string): Initializes a new page or resumes an existing session from storage. Injects localStorage overrides to bypass the theme selection screen.answer(sessionId: string, answer: AkiAnswer): Submits an answer and polls the DOM until the new question renders or the proposal block becomes visible.end(sessionId: string): Closes the specific Puppeteer page and updates the database status toENDED.destroyAll(): Closes the browser instance and clears the session map.
Storage Implementations
Storage classes implement the StorageProvider interface, allowing developers to write custom adapters (e.g., MongoDB, Redis).
SQLiteStorage
- Standard relational implementation using
better-sqlite3. - Creates
akinator_gamesandakinator_answerstables. - Best suited for multi-process or high-load environments.
JSONStorage
- File-based implementation using
fs/promises. - Stores one
.jsonfile persessionId. - I/O Optimization: Implements an in-memory cache with a 2000ms flush debounce. Concurrent writes within the 2-second window mutate the memory state and trigger a single disk write, preventing race conditions and I/O locking.
Types & Interfaces
EngineOptions
Passed to the AkinatorEngine constructor to configure the underlying browser and constraints.
type Region =
| "en"
| "ar"
| "cn"
| "de"
| "es"
| "fr"
| "il"
| "it"
| "jp"
| "kr"
| "nl"
| "pl"
| "pt"
| "ru"
| "tr"
| "id"
| "vi";
interface EngineOptions {
executablePath?: string; // Overrides Chromium path manually
profileDir?: string; // Persistent directory for browser cache/cookies
headless?: boolean | "new" | "shell"; // Puppeteer visibility state
timeoutMs?: number; // TTL for inactive sessions (defaults to 300000ms)
maxSessions?: number; // Hard limit for concurrent pages (defaults to 6)
region?: Region; // Game language/region subdomain (defaults to 'pt')
}AkiAnswer
Numeric mapping for the Akinator DOM inputs.
type AkiAnswer = 0 | 1 | 2 | 3 | 4;
// 0: Yes
// 1: No
// 2: Don't know
// 3: Probably yes
// 4: Probably noAkiResult
Extracted when the engine detects the #proposeGameBlock element.
interface AkiResult {
name?: string;
description?: string;
photoUrl?: string;
}AkiState
The standard return payload for a successful engine operation.
interface AkiState {
question: string;
isWin: boolean;
result?: AkiResult; // Populated only if isWin is true
}Debugging
The module includes an internal logger and a DOM state dumper for debugging navigation failures.
To enable execution logs and force the scraper to save .html and .png dumps when a timeout or DOM error occurs, set the environment variable:
AKINATOR_DEBUG=1 node app.jsDumps are written to [cwd]/data/akinator-debug/.
