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.
- package/LICENSE +9 -3
- package/README.md +38 -37
- package/index.d.ts +40 -15
- package/index.js +206 -191
- 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
|
|
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
|
|
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
|
[](https://www.npmjs.com/package/silent-akinator-pro)
|
|
6
4
|
[](https://www.npmjs.com/package/silent-akinator-pro)
|
|
7
5
|
[](LICENSE)
|
|
8
6
|
|
|
9
|
-
The
|
|
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
|
|
11
|
+
## Why this package
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
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
|
|
26
|
+
npm i silent-akinator-pro
|
|
26
27
|
```
|
|
27
28
|
|
|
28
|
-
##
|
|
29
|
+
## Usage
|
|
29
30
|
|
|
30
31
|
```js
|
|
31
|
-
const
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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.
|
|
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)` | `
|
|
63
|
-
| `back()` | Undo
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
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
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
7
|
-
|
|
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
|
-
|
|
28
|
+
|
|
29
|
+
export declare class Akinator {
|
|
10
30
|
constructor(opts?: AkinatorOptions);
|
|
11
|
-
region:
|
|
31
|
+
region: Region;
|
|
12
32
|
childMode: boolean;
|
|
13
|
-
|
|
33
|
+
session: string | null;
|
|
34
|
+
signature: string | null;
|
|
14
35
|
step: number;
|
|
15
36
|
progression: string;
|
|
16
|
-
|
|
37
|
+
question: string | null;
|
|
17
38
|
guessed: boolean;
|
|
39
|
+
guess: Guess | null;
|
|
18
40
|
finished: boolean;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
win(): Promise<
|
|
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
|
-
|
|
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
|
|
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 {
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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,
|
|
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
|
|
27
|
-
'
|
|
28
|
-
'
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
122
|
-
|
|
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)
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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(
|
|
151
|
-
catch { throw new Error(
|
|
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.
|
|
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.
|
|
184
|
-
this.
|
|
185
|
-
|
|
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('
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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.
|
|
204
|
-
this.
|
|
205
|
-
|
|
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
|
-
|
|
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
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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.
|
|
224
|
-
this.
|
|
225
|
-
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
|
245
|
-
module.exports.default
|
|
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": "
|
|
4
|
-
"description": "Akinator API
|
|
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-
|
|
20
|
+
"akinator-bot",
|
|
15
21
|
"akinator-client",
|
|
22
|
+
"akinator-sdk",
|
|
23
|
+
"akinator-wrapper",
|
|
16
24
|
"akinator-node",
|
|
17
25
|
"akinator-js",
|
|
18
|
-
"akinator-
|
|
19
|
-
"akinator-
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
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
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
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
|
-
"
|
|
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://
|
|
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": ">=
|
|
72
|
+
"node": ">=18"
|
|
57
73
|
},
|
|
58
74
|
"dependencies": {
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
|
|
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
|
}
|