silent-akinator-pro 3.0.0 → 4.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. package/LICENSE +9 -3
  2. package/README.md +38 -37
  3. package/index.d.ts +40 -15
  4. package/index.js +206 -191
  5. package/package.json +40 -29
package/LICENSE CHANGED
@@ -1,9 +1,15 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) Silent Tech
3
+ Copyright (c) 2026 Silent Tech
4
4
 
5
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
6
11
 
7
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
8
14
 
9
15
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
package/README.md CHANGED
@@ -1,42 +1,45 @@
1
1
  # silent-akinator-pro
2
2
 
3
- > 🧞 **The working Akinator API wrapper for Node.js** — fast, reliable, Cloudflare-safe. Modern Akinator client by **Silent Tech**.
4
-
5
3
  [![npm](https://img.shields.io/npm/v/silent-akinator-pro.svg)](https://www.npmjs.com/package/silent-akinator-pro)
6
4
  [![downloads](https://img.shields.io/npm/dm/silent-akinator-pro.svg)](https://www.npmjs.com/package/silent-akinator-pro)
7
5
  [![license](https://img.shields.io/npm/l/silent-akinator-pro.svg)](LICENSE)
8
6
 
9
- The most up-to-date **Akinator** Node.js wrapper that actually works in 2026. Bypasses the Cloudflare `403` by using the official HTML form endpoints with realistic browser headers and a full cookie jar — no third-party proxies, no API keys.
7
+ > **The working Akinator API for Node.js — the only akinator wrapper that still works in 2025 / 2026** after Cloudflare blocked the legacy `api-en4.akinator.com/ws/*` endpoints. Fast, zero-config, Promise-based, fully typed. By **Silent Tech**.
8
+
9
+ The genie / twenty-questions / character-guessing SDK for **Discord bots, Telegram bots, WhatsApp bots, web games, and CLIs**.
10
10
 
11
- ## Why silent-akinator-pro?
11
+ ## Why this package
12
12
 
13
- - ✅ **Working** — fixed the `403 Forbidden` / Cloudflare issue that broke every other akinator npm package
14
- - ⚡ **Fast** — zero heavy dependencies (only `tough-cookie`)
15
- - 🌍 **16 regions** — `en`, `fr`, `es`, `de`, `it`, `pt`, `ru`, `ar`, `jp`, `kr`, `cn`, `nl`, `pl`, `tr`, `il`, `id`
16
- - 🧒 **Child mode** supported
17
- - â†Šī¸ **Undo / back / continue / win** all supported
18
- - 🤖 **Discord bot / Telegram bot / WhatsApp bot ready**
19
- - 📘 **TypeScript types** included
20
- - 🆓 **MIT** licensed
13
+ Most akinator npm packages (`aki-api`, `akinator-api-node`, older `silent-akinator`, etc.) hit JSON endpoints that now return **HTTP 403 / Cloudflare Just-a-moment**. This package uses the **real HTML form flow** the actual website uses — `POST /game`, `/answer`, `/cancel_answer`, `/exclude`, `/list`, `/choice` — no TLS spoofing, no proxies, no `curl-impersonate`. Just clean `undici` + `cheerio`.
14
+
15
+ - Working in 2025 / 2026 (Cloudflare-safe)
16
+ - 22 regions including `en`, `en_objects`, `en_animals`, `fr`, `de`, `es`, `it`, `pt`, `ru`, `ar`, `jp`, `kr`, `cn`, `nl`, `pl`, `tr`, `il`, `id`
17
+ - Child mode supported
18
+ - Full game lifecycle: `start`, `answer`, `back`, `win`, `exclude`, `choice`
19
+ - TypeScript types included
20
+ - Tiny: only `undici` + `cheerio`
21
+ - MIT licensed
21
22
 
22
23
  ## Install
23
24
 
24
25
  ```bash
25
- npm install silent-akinator-pro
26
+ npm i silent-akinator-pro
26
27
  ```
27
28
 
28
- ## Quick Start
29
+ ## Usage
29
30
 
30
31
  ```js
31
- const { Akinator } = require('silent-akinator-pro');
32
+ const Akinator = require('silent-akinator-pro');
32
33
 
33
34
  (async () => {
34
35
  const aki = new Akinator({ region: 'en', childMode: false });
35
36
  await aki.start();
37
+ console.log(aki.question); // first question
36
38
 
37
- console.log(aki.question);
38
- await aki.answer('y'); // y / n / idk / p / pn (or 0-4)
39
- console.log(aki.question);
39
+ // 0=yes 1=no 2=idk 3=probably 4=probably-not
40
+ // (or 'y' / 'n' / 'idk' / 'p' / 'pn')
41
+ const r = await aki.answer('y');
42
+ console.log(r.question, r.progression);
40
43
 
41
44
  while (!aki.guessed && aki.step < 80) {
42
45
  await aki.answer('idk');
@@ -44,33 +47,29 @@ const { Akinator } = require('silent-akinator-pro');
44
47
 
45
48
  if (aki.guessed) {
46
49
  console.log('Akinator thinks of:', aki.guess.name);
47
- await aki.win();
50
+ await aki.choice(); // confirm
48
51
  }
49
52
  })();
50
53
  ```
51
54
 
52
55
  ## API
53
56
 
54
- ### `new Akinator({ region, childMode })`
55
- - `region` — `en | fr | es | de | it | pt | ru | ar | jp | kr | cn | nl | pl | tr | il | id`
56
- - `childMode` — `boolean`
57
-
58
- ### Methods
59
57
  | Method | Description |
60
58
  |---------------|----------------------------------------------|
61
- | `start()` | Begin a new game |
62
- | `answer(a)` | `y` / `n` / `idk` / `p` / `pn` (or `0-4`) |
63
- | `back()` | Undo the previous answer |
64
- | `continue()` | Reject the current guess and keep playing |
65
- | `win()` | Confirm the guess and end the game |
59
+ | `start()` | Begin a new game; returns first question |
60
+ | `answer(a)` | Submit answer (`0..4` or `y/n/idk/p/pn`) |
61
+ | `back()` | Undo last answer |
62
+ | `win()` | Force Akinator to guess now |
63
+ | `exclude()` | Reject the guess and continue |
64
+ | `choice()` | Confirm the guess (close session) |
66
65
 
67
66
  ### Properties
68
- `question`, `step`, `progression`, `guess`, `guessed`, `finished`.
67
+ `region`, `childMode`, `question`, `step`, `progression`, `guess`, `guessed`, `finished`.
69
68
 
70
69
  ## Discord bot example
71
70
 
72
71
  ```js
73
- const { Akinator } = require('silent-akinator-pro');
72
+ const Akinator = require('silent-akinator-pro');
74
73
  const games = new Map();
75
74
 
76
75
  client.on('messageCreate', async (msg) => {
@@ -78,20 +77,22 @@ client.on('messageCreate', async (msg) => {
78
77
  const aki = new Akinator({ region: 'en' });
79
78
  await aki.start();
80
79
  games.set(msg.author.id, aki);
81
- msg.reply(aki.question);
82
- } else if (['y','n','idk','p','pn'].includes(msg.content)) {
80
+ return msg.reply(aki.question);
81
+ }
82
+ if (['y','n','idk','p','pn'].includes(msg.content)) {
83
83
  const aki = games.get(msg.author.id);
84
84
  if (!aki) return;
85
85
  await aki.answer(msg.content);
86
- if (aki.guessed) msg.reply(`Is it **${aki.guess.name}**? (${aki.guess.description})`);
87
- else msg.reply(aki.question);
86
+ if (aki.guessed) return msg.reply(`Is it **${aki.guess.name}**? (${aki.guess.description})`);
87
+ msg.reply(aki.question);
88
88
  }
89
89
  });
90
90
  ```
91
91
 
92
92
  ## Keywords
93
- akinator, akinator api, akinator nodejs, akinator wrapper, akinator client, akinator bot, akinator discord, akinator 2026, working akinator, akinator cloudflare, silent tech, silent-akinator, silent-akinator-pro, twenty questions, character guessing game, genie api.
93
+
94
+ akinator, akinator api, working akinator, akinator 2025, akinator 2026, akinator cloudflare bypass, akinator 403 fix, akinator discord bot, akinator telegram bot, akinator whatsapp bot, twenty questions, character guessing game, genie sdk, node akinator client, aki-api alternative, silent tech, silent-akinator, silent-akinator-pro.
94
95
 
95
96
  ## License
96
97
 
97
- MIT Š **Silent Tech**
98
+ MIT (c) Silent Tech
package/index.d.ts CHANGED
@@ -1,26 +1,51 @@
1
+ export type Region =
2
+ | 'en' | 'en2' | 'en_objects' | 'en_animals' | 'ar' | 'cn' | 'de' | 'es'
3
+ | 'fr' | 'fr_objects' | 'fr_animals' | 'il' | 'it' | 'jp' | 'jp_animals'
4
+ | 'kr' | 'nl' | 'pl' | 'pt' | 'ru' | 'tr' | 'id';
1
5
 
2
- export interface AkinatorOptions {
3
- region?: 'en'|'ar'|'cn'|'de'|'es'|'fr'|'il'|'it'|'jp'|'kr'|'nl'|'pl'|'pt'|'ru'|'tr'|'id';
4
- childMode?: boolean;
6
+ export type Answer = 0 | 1 | 2 | 3 | 4 | 'y' | 'n' | 'idk' | 'p' | 'pn'
7
+ | 'yes' | 'no' | 'probably' | 'probably not';
8
+
9
+ export interface AkinatorOptions { region?: Region; childMode?: boolean; }
10
+
11
+ export interface Question {
12
+ question: string;
13
+ step: number;
14
+ progression: string;
15
+ question_id?: string;
16
+ completion?: string;
17
+ answers?: string[];
5
18
  }
6
- export interface AkinatorGuess {
7
- id?: string; name?: string; description?: string; photo?: string; pseudo?: string;
19
+
20
+ export interface Guess {
21
+ id?: string;
22
+ name?: string;
23
+ description?: string;
24
+ photo?: string;
25
+ pseudo?: string;
26
+ flag_photo?: string | number;
8
27
  }
9
- export class Akinator {
28
+
29
+ export declare class Akinator {
10
30
  constructor(opts?: AkinatorOptions);
11
- region: string;
31
+ region: Region;
12
32
  childMode: boolean;
13
- question: string | null;
33
+ session: string | null;
34
+ signature: string | null;
14
35
  step: number;
15
36
  progression: string;
16
- guess: AkinatorGuess | null;
37
+ question: string | null;
17
38
  guessed: boolean;
39
+ guess: Guess | null;
18
40
  finished: boolean;
19
- start(): Promise<this>;
20
- answer(a: 'y'|'n'|'idk'|'p'|'pn'|0|1|2|3|4|string|number): Promise<this>;
21
- back(): Promise<this>;
22
- continue(): Promise<this>;
23
- win(): Promise<AkinatorGuess>;
41
+
42
+ start(): Promise<Question>;
43
+ answer(a: Answer): Promise<Question | { guess: Guess; step: number; progression: string }>;
44
+ back(): Promise<Question>;
45
+ win(): Promise<any>;
46
+ exclude(): Promise<Question>;
47
+ choice(): Promise<Guess>;
24
48
  }
25
- export const REGIONS: Record<string,string>;
49
+
26
50
  export default Akinator;
51
+ export const REGIONS: Region[];
package/index.js CHANGED
@@ -1,245 +1,260 @@
1
-
2
1
  'use strict';
3
2
  /*!
4
3
  * silent-akinator-pro
5
- * Working Akinator API wrapper (HTML-form endpoints, Cloudflare-safe).
4
+ * Working Akinator API client (2025/2026) — bypasses Cloudflare 403 by using
5
+ * the real HTML form-POST flow instead of the deprecated /ws JSON endpoints.
6
+ *
7
+ * Answers: 0=yes 1=no 2=idk 3=probably 4=probably-not
6
8
  * (c) Silent Tech — MIT
7
9
  */
8
- const { CookieJar } = require('tough-cookie');
9
- const https = require('https');
10
- const { URL, URLSearchParams } = require('url');
11
-
12
- const REGIONS = {
13
- en: 'en', ar: 'ar', cn: 'cn', de: 'de', es: 'es', fr: 'fr',
14
- il: 'il', it: 'it', jp: 'jp', kr: 'kr', nl: 'nl', pl: 'pl',
15
- pt: 'pt', ru: 'ru', tr: 'tr', id: 'id'
16
- };
10
+ const { request } = require('undici');
11
+ const { load } = require('cheerio');
12
+
13
+ const REGIONS = [
14
+ 'en','en2','en_objects','en_animals','ar','cn','de','es','fr','fr_objects',
15
+ 'fr_animals','il','it','jp','jp_animals','kr','nl','pl','pt','ru','tr','id',
16
+ ];
17
17
 
18
18
  const ANSWER_MAP = {
19
19
  y: 0, yes: 0, '0': 0,
20
20
  n: 1, no: 1, '1': 1,
21
- idk: 2, "don't know": 2, dk: 2, '2': 2,
21
+ idk: 2, dk: 2, "don't know": 2, '2': 2,
22
22
  p: 3, probably: 3, '3': 3,
23
- pn: 4, 'probably not': 4, '4': 4
23
+ pn: 4, 'probably not': 4, '4': 4,
24
24
  };
25
25
 
26
- const UA =
27
- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' +
28
- '(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36';
29
-
30
- function request(method, urlStr, { headers = {}, body = null, jar } = {}) {
31
- return new Promise((resolve, reject) => {
32
- const u = new URL(urlStr);
33
- const cookieHeader = jar ? jar.getCookieStringSync(urlStr) : '';
34
- const opts = {
35
- method,
36
- hostname: u.hostname,
37
- path: u.pathname + u.search,
38
- headers: {
39
- 'User-Agent': UA,
40
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
41
- 'Accept-Language': 'en-US,en;q=0.9',
42
- 'Accept-Encoding': 'identity',
43
- 'Referer': `https://${u.hostname}/`,
44
- 'Origin': `https://${u.hostname}`,
45
- ...(cookieHeader ? { Cookie: cookieHeader } : {}),
46
- ...headers
47
- }
48
- };
49
- if (body) {
50
- opts.headers['Content-Type'] = 'application/x-www-form-urlencoded';
51
- opts.headers['Content-Length'] = Buffer.byteLength(body);
52
- opts.headers['X-Requested-With'] = 'XMLHttpRequest';
53
- }
54
- const req = https.request(opts, (res) => {
55
- const chunks = [];
56
- res.on('data', (c) => chunks.push(c));
57
- res.on('end', () => {
58
- const data = Buffer.concat(chunks).toString('utf8');
59
- if (jar && res.headers['set-cookie']) {
60
- for (const c of res.headers['set-cookie']) {
61
- try { jar.setCookieSync(c, urlStr); } catch (_) {}
62
- }
63
- }
64
- if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
65
- const next = new URL(res.headers.location, urlStr).toString();
66
- return resolve(request('GET', next, { jar, headers }));
67
- }
68
- resolve({ status: res.statusCode, headers: res.headers, body: data });
69
- });
70
- });
71
- req.on('error', reject);
72
- if (body) req.write(body);
73
- req.end();
74
- });
75
- }
26
+ const BASE_HEADERS = {
27
+ 'content-type' : 'application/x-www-form-urlencoded',
28
+ 'user-agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
29
+ 'x-requested-with': 'XMLHttpRequest',
30
+ };
76
31
 
77
- function extract(html, re) {
78
- const m = html.match(re);
79
- return m ? m[1] : null;
80
- }
32
+ const form = (obj) => new URLSearchParams(
33
+ Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, String(v)]))
34
+ ).toString();
81
35
 
82
36
  class Akinator {
37
+ /**
38
+ * @param {object} opts
39
+ * @param {string} [opts.region='en'] one of REGIONS
40
+ * @param {boolean} [opts.childMode=false] safe-mode filter
41
+ */
83
42
  constructor(opts = {}) {
84
- this.region = REGIONS[(opts.region || 'en').toLowerCase()] || 'en';
85
- this.childMode = !!opts.childMode;
86
- this.host = `https://${this.region}.akinator.com`;
87
- this.jar = new CookieJar();
88
-
89
- this.session = null;
90
- this.signature = null;
91
- this.question = null;
92
- this.step = 0;
93
- this.progression = '0.00000';
94
- this.guess = null;
95
- this.guessed = false;
96
- this.finished = false;
97
- }
98
-
99
- async start() {
100
- // 1. warm-up to set Cloudflare cf_clearance / __cf_bm cookies
101
- await request('GET', this.host + '/', { jar: this.jar });
102
-
103
- // 2. fetch the game page (the HTML form gives us session + signature)
104
- const url = `${this.host}/game?cm=${this.childMode ? 'true' : 'false'}`;
105
- const r = await request('GET', url, { jar: this.jar });
106
- if (r.status !== 200) {
107
- throw new Error(`Akinator start failed (${r.status}). Region "${this.region}" may be blocked — try another region.`);
43
+ const { region = 'en', childMode = false } = opts;
44
+ if (!REGIONS.includes(region)) {
45
+ throw new Error(`Invalid region "${region}". Valid: ${REGIONS.join(', ')}`);
108
46
  }
47
+ this.region = region;
48
+ this.childMode = !!childMode;
49
+ this.uri = `https://${region}.akinator.com`;
50
+ this.headers = { ...BASE_HEADERS };
109
51
 
110
- this.session = extract(r.body, /#session['"]?\s*value=['"]([^'"]+)/i)
111
- || extract(r.body, /name=['"]session['"][^>]*value=['"]([^'"]+)/i)
112
- || extract(r.body, /var\s+session\s*=\s*['"]([^'"]+)/i);
113
- this.signature = extract(r.body, /#signature['"]?\s*value=['"]([^'"]+)/i)
114
- || extract(r.body, /name=['"]signature['"][^>]*value=['"]([^'"]+)/i)
115
- || extract(r.body, /var\s+signature\s*=\s*['"]([^'"]+)/i);
116
- this.question = extract(r.body, /<p[^>]*class=['"][^'"]*question-text[^'"]*['"][^>]*>([^<]+)<\/p>/i)
117
- || extract(r.body, /id=['"]question-label['"][^>]*>([^<]+)</i);
118
- this.step = 0;
52
+ this.session = null;
53
+ this.signature = null;
54
+ this.step = 0;
119
55
  this.progression = '0.00000';
120
-
121
- if (!this.session || !this.signature) {
122
- throw new Error('Could not parse session/signature from Akinator page. The site layout may have changed.');
123
- }
124
- return this;
56
+ this.question = null;
57
+ this.guessed = false;
58
+ this.guess = null;
59
+ this.finished = false;
125
60
  }
126
61
 
127
62
  _answerCode(a) {
128
63
  if (typeof a === 'number') a = String(a);
129
64
  const code = ANSWER_MAP[String(a).toLowerCase().trim()];
130
- if (code === undefined) throw new Error(`Invalid answer "${a}". Use y / n / idk / p / pn.`);
65
+ if (code === undefined) {
66
+ throw new Error(`Invalid answer "${a}". Use y / n / idk / p / pn or 0..4.`);
67
+ }
131
68
  return code;
132
69
  }
133
70
 
71
+ /** Start a new game. Resolves with the first question. */
72
+ async start() {
73
+ const res = await request(`${this.uri}/game`, {
74
+ method: 'POST',
75
+ headers: this.headers,
76
+ body: form({ cm: this.childMode, sid: 1 }),
77
+ maxRedirections: 5,
78
+ });
79
+ if (res.statusCode !== 200) {
80
+ throw new Error(`Akinator /game returned ${res.statusCode}`);
81
+ }
82
+ const html = await res.body.text();
83
+ const $ = load(html);
84
+
85
+ this.session = $('#askSoundlike > #session').attr('value')
86
+ || $('#session').attr('value');
87
+ this.signature = $('#askSoundlike > #signature').attr('value')
88
+ || $('#signature').attr('value');
89
+ this.question = $('#question-label').text().trim()
90
+ || $('.bubble-body .question-text').text().trim();
91
+
92
+ if (!this.session || !this.signature) {
93
+ throw new Error('Failed to parse session/signature from Akinator response.');
94
+ }
95
+ this.step = 0;
96
+ this.progression = '0.00000';
97
+ return {
98
+ question: this.question,
99
+ step: this.step,
100
+ progression: this.progression,
101
+ answers: ['yes', 'no', 'idk', 'probably', 'probably not'],
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Submit an answer.
107
+ * @param {0|1|2|3|4|string} a
108
+ */
134
109
  async answer(a) {
135
110
  if (this.finished) throw new Error('Game already finished.');
111
+ if (!this.session) throw new Error('Call start() first.');
136
112
  const ans = this._answerCode(a);
137
- const body = new URLSearchParams({
138
- step: String(this.step),
139
- progression: String(this.progression),
140
- sid: '1',
141
- cm: this.childMode ? 'true' : 'false',
142
- answer: String(ans),
143
- step_last_proposition: '',
144
- session: this.session,
145
- signature: this.signature
146
- }).toString();
147
-
148
- const r = await request('POST', this.host + '/answer', { jar: this.jar, body });
113
+
114
+ const res = await request(`${this.uri}/answer`, {
115
+ method: 'POST',
116
+ headers: this.headers,
117
+ body: form({
118
+ step : this.step,
119
+ progression : this.progression,
120
+ sid : 1,
121
+ cm : this.childMode,
122
+ answer : ans,
123
+ step_last_proposition: '',
124
+ session : this.session,
125
+ signature : this.signature,
126
+ }),
127
+ });
128
+
149
129
  let data;
150
- try { data = JSON.parse(r.body); }
151
- catch { throw new Error(`Akinator answer failed (status ${r.status}). Response was not JSON.`); }
152
-
153
- if (data.completion && data.completion !== 'OK' && data.completion !== 'KO - TIMEOUT') {
154
- // proposal step (Akinator wants to guess)
155
- if (data.id_proposition || data.name_proposition) {
156
- this.guess = {
157
- id: data.id_proposition,
158
- name: data.name_proposition,
159
- description: data.description_proposition,
160
- photo: data.photo,
161
- pseudo: data.pseudo
162
- };
163
- this.guessed = true;
164
- return this;
165
- }
166
- }
130
+ try { data = JSON.parse(await res.body.text()); }
131
+ catch { throw new Error(`/answer returned non-JSON (status ${res.statusCode}).`); }
167
132
 
168
133
  if (data.id_proposition || data.name_proposition) {
169
134
  this.guess = {
170
- id: data.id_proposition,
171
- name: data.name_proposition,
172
- description: data.description_proposition,
173
- photo: data.photo,
174
- pseudo: data.pseudo
135
+ id : data.id_proposition,
136
+ name : data.name_proposition,
137
+ description : data.description_proposition,
138
+ photo : data.photo,
139
+ pseudo : data.pseudo,
140
+ flag_photo : data.flag_photo,
175
141
  };
176
142
  this.guessed = true;
177
- return this;
143
+ return { guess: this.guess, step: Number(this.step), progression: this.progression };
178
144
  }
179
145
 
180
- this.question = data.question || this.question;
181
- this.step = parseInt(data.step ?? this.step + 1, 10);
146
+ this.step = parseInt(data.step ?? this.step + 1, 10);
182
147
  this.progression = data.progression || this.progression;
183
- this.guessed = false;
184
- this.guess = null;
185
- return this;
148
+ this.question = data.question || this.question;
149
+ this.guessed = false;
150
+ this.guess = null;
151
+ return {
152
+ question : this.question,
153
+ step : this.step,
154
+ progression : this.progression,
155
+ question_id : data.question_id,
156
+ completion : data.completion,
157
+ };
186
158
  }
187
159
 
160
+ /** Undo the last answer. */
188
161
  async back() {
189
- if (this.step <= 0) throw new Error('Cannot go back from step 0.');
190
- const body = new URLSearchParams({
191
- step: String(this.step),
192
- progression: String(this.progression),
193
- sid: '1',
194
- cm: this.childMode ? 'true' : 'false',
195
- session: this.session,
196
- signature: this.signature
197
- }).toString();
198
- const r = await request('POST', this.host + '/cancel_answer', { jar: this.jar, body });
199
- const data = JSON.parse(r.body);
200
- this.question = data.question || this.question;
201
- this.step = parseInt(data.step ?? Math.max(0, this.step - 1), 10);
162
+ if (this.step <= 0) throw new Error('Nothing to undo.');
163
+ const res = await request(`${this.uri}/cancel_answer`, {
164
+ method: 'POST',
165
+ headers: this.headers,
166
+ body: form({
167
+ step : this.step,
168
+ progression: this.progression,
169
+ sid : 1,
170
+ cm : this.childMode,
171
+ session : this.session,
172
+ signature : this.signature,
173
+ }),
174
+ });
175
+ const data = JSON.parse(await res.body.text());
176
+ this.step = parseInt(data.step ?? Math.max(0, this.step - 1), 10);
202
177
  this.progression = data.progression || this.progression;
203
- this.guessed = false;
204
- this.guess = null;
205
- return this;
178
+ this.question = data.question || this.question;
179
+ this.guessed = false;
180
+ this.guess = null;
181
+ return { question: this.question, step: this.step, progression: this.progression };
182
+ }
183
+
184
+ /** Force Akinator to make its best guess now. */
185
+ async win() {
186
+ const res = await request(`${this.uri}/list`, {
187
+ method: 'POST',
188
+ headers: this.headers,
189
+ body: form({
190
+ step : this.step,
191
+ progression : this.progression,
192
+ sid : 1,
193
+ cm : this.childMode,
194
+ mode_question : 0,
195
+ session : this.session,
196
+ signature : this.signature,
197
+ }),
198
+ });
199
+ const text = await res.body.text();
200
+ let data;
201
+ try { data = JSON.parse(text); }
202
+ catch {
203
+ // /list sometimes returns HTML when progression is too low or session expired.
204
+ // Fall back to current guess or last known state.
205
+ if (this.guess) return this.guess;
206
+ throw new Error(`/list returned non-JSON (status ${res.statusCode}). Try answering more questions before calling win().`);
207
+ }
208
+ this.guessed = true;
209
+ this.guess = data;
210
+ return data;
206
211
  }
207
212
 
208
- async continue() {
213
+ /** Reject the current guess and continue playing. */
214
+ async exclude() {
209
215
  if (!this.guessed) throw new Error('No active guess to reject.');
210
- const body = new URLSearchParams({
211
- step: String(this.step),
212
- progression: '10',
213
- sid: '1',
214
- cm: this.childMode ? 'true' : 'false',
215
- session: this.session,
216
- signature: this.signature
217
- }).toString();
218
- const r = await request('POST', this.host + '/exclude', { jar: this.jar, body });
219
- const data = JSON.parse(r.body);
220
- this.question = data.question || this.question;
221
- this.step = parseInt(data.step ?? this.step + 1, 10);
216
+ const res = await request(`${this.uri}/exclude`, {
217
+ method: 'POST',
218
+ headers: this.headers,
219
+ body: form({
220
+ step : this.step,
221
+ progression: this.progression,
222
+ sid : 1,
223
+ cm : this.childMode,
224
+ session : this.session,
225
+ signature : this.signature,
226
+ }),
227
+ });
228
+ const data = JSON.parse(await res.body.text());
229
+ this.step = parseInt(data.step ?? this.step + 1, 10);
222
230
  this.progression = data.progression || this.progression;
223
- this.guessed = false;
224
- this.guess = null;
225
- return this;
231
+ this.question = data.question || this.question;
232
+ this.guessed = false;
233
+ this.guess = null;
234
+ return { question: this.question, step: this.step, progression: this.progression };
226
235
  }
227
236
 
228
- async win() {
229
- if (!this.guessed) throw new Error('No guess to confirm.');
230
- const body = new URLSearchParams({
231
- sid: '1',
232
- charac_name: this.guess.name || '',
233
- charac_desc: this.guess.description || '',
234
- session: this.session,
235
- signature: this.signature,
236
- pflag_photo: '0'
237
- }).toString();
238
- await request('POST', this.host + '/choice', { jar: this.jar, body });
237
+ /** Confirm Akinator's guess (closes the session). */
238
+ async choice() {
239
+ if (!this.guess) throw new Error('No guess to confirm.');
240
+ await request(`${this.uri}/choice`, {
241
+ method: 'POST',
242
+ headers: this.headers,
243
+ body: form({
244
+ sid : 1,
245
+ charac_name : this.guess.name || '',
246
+ charac_desc : this.guess.description || '',
247
+ session : this.session,
248
+ signature : this.signature,
249
+ pflag_photo : 0,
250
+ }),
251
+ });
239
252
  this.finished = true;
240
253
  return this.guess;
241
254
  }
242
255
  }
243
256
 
244
- module.exports = { Akinator, REGIONS };
245
- module.exports.default = Akinator;
257
+ module.exports = Akinator;
258
+ module.exports.default = Akinator;
259
+ module.exports.Akinator = Akinator;
260
+ module.exports.REGIONS = REGIONS;
package/package.json CHANGED
@@ -1,46 +1,62 @@
1
1
  {
2
2
  "name": "silent-akinator-pro",
3
- "version": "3.0.0",
4
- "description": "Akinator API wrapper for Node.js \u2014 fast, working, Cloudflare-safe. The genie that guesses any character. Modern Akinator client with full region, child-mode, undo, and guess-confirm support. (silent-tech)",
3
+ "version": "4.0.3",
4
+ "description": "Working Akinator API for Node.js (2025/2026). Fast, lightweight, Cloudflare-safe akinator client / wrapper / SDK / bot library \u2014 supports 22 regions, child mode, undo, win, exclude. The genie / 20-questions / character-guessing API that actually works after the api-en4 shutdown. Drop-in replacement for aki-api and akinator-api.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
7
7
  "type": "commonjs",
8
+ "files": [
9
+ "index.js",
10
+ "index.d.ts",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
8
14
  "scripts": {
9
15
  "test": "node test.js"
10
16
  },
11
17
  "keywords": [
12
18
  "akinator",
13
19
  "akinator-api",
14
- "akinator-wrapper",
20
+ "akinator-bot",
15
21
  "akinator-client",
22
+ "akinator-sdk",
23
+ "akinator-wrapper",
16
24
  "akinator-node",
17
25
  "akinator-js",
18
- "akinator-bot",
19
- "akinator-game",
20
- "silent",
21
- "silent-tech",
22
- "silent-akinator",
23
- "silent-akinator-pro",
26
+ "akinator-2025",
27
+ "akinator-2026",
28
+ "working-akinator",
29
+ "akinator-fix",
30
+ "akinator-cloudflare",
31
+ "cloudflare-bypass",
32
+ "akinator-403",
33
+ "aki-api",
34
+ "aki",
24
35
  "genie",
25
- "guess",
26
- "guessing-game",
27
36
  "20-questions",
28
37
  "twenty-questions",
38
+ "guessing-game",
29
39
  "character-guess",
30
40
  "ai-guess",
31
41
  "game",
32
- "api",
33
- "wrapper",
34
- "client",
35
- "cloudflare-bypass",
36
- "working-akinator",
37
- "akinator-2025",
38
- "akinator-2026",
42
+ "ai-game",
43
+ "quiz",
44
+ "trivia",
45
+ "fun-api",
39
46
  "discord-bot",
47
+ "discord-akinator",
40
48
  "telegram-bot",
41
49
  "whatsapp-bot",
50
+ "baileys",
42
51
  "chatbot",
43
- "fun-api"
52
+ "scraper",
53
+ "typescript",
54
+ "esm",
55
+ "cjs",
56
+ "silent",
57
+ "silent-tech",
58
+ "silent-akinator",
59
+ "silent-akinator-pro"
44
60
  ],
45
61
  "author": "Silent Tech <silent.tech.offc@gmail.com>",
46
62
  "license": "MIT",
@@ -48,20 +64,15 @@
48
64
  "type": "git",
49
65
  "url": "git+https://github.com/silent-tech/silent-akinator-pro.git"
50
66
  },
51
- "homepage": "https://www.npmjs.com/package/silent-akinator-pro",
67
+ "homepage": "https://github.com/silent-tech/silent-akinator-pro#readme",
52
68
  "bugs": {
53
69
  "url": "https://github.com/silent-tech/silent-akinator-pro/issues"
54
70
  },
55
71
  "engines": {
56
- "node": ">=14"
72
+ "node": ">=18"
57
73
  },
58
74
  "dependencies": {
59
- "tough-cookie": "^4.1.4"
60
- },
61
- "files": [
62
- "index.js",
63
- "index.d.ts",
64
- "README.md",
65
- "LICENSE"
66
- ]
75
+ "undici": "^6.19.8",
76
+ "cheerio": "^1.0.0"
77
+ }
67
78
  }