silent-akinator-pro 2.0.1 â 3.0.0
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 -0
- package/README.md +59 -14
- package/index.d.ts +26 -0
- package/index.js +245 -0
- package/package.json +48 -15
- package/src/index.d.ts +0 -33
- package/src/index.js +0 -244
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Silent Tech
|
|
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:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
|
package/README.md
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
# silent-akinator-pro
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> đ§ **The working Akinator API wrapper for Node.js** â fast, reliable, Cloudflare-safe. Modern Akinator client by **Silent Tech**.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/silent-akinator-pro)
|
|
6
|
+
[](https://www.npmjs.com/package/silent-akinator-pro)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
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.
|
|
10
|
+
|
|
11
|
+
## Why silent-akinator-pro?
|
|
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
|
|
6
21
|
|
|
7
22
|
## Install
|
|
8
23
|
|
|
@@ -20,12 +35,16 @@ const { Akinator } = require('silent-akinator-pro');
|
|
|
20
35
|
await aki.start();
|
|
21
36
|
|
|
22
37
|
console.log(aki.question);
|
|
23
|
-
await aki.answer('y');
|
|
38
|
+
await aki.answer('y'); // y / n / idk / p / pn (or 0-4)
|
|
24
39
|
console.log(aki.question);
|
|
25
40
|
|
|
26
|
-
|
|
27
|
-
|
|
41
|
+
while (!aki.guessed && aki.step < 80) {
|
|
42
|
+
await aki.answer('idk');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (aki.guessed) {
|
|
28
46
|
console.log('Akinator thinks of:', aki.guess.name);
|
|
47
|
+
await aki.win();
|
|
29
48
|
}
|
|
30
49
|
})();
|
|
31
50
|
```
|
|
@@ -33,20 +52,46 @@ const { Akinator } = require('silent-akinator-pro');
|
|
|
33
52
|
## API
|
|
34
53
|
|
|
35
54
|
### `new Akinator({ region, childMode })`
|
|
36
|
-
- `region` â `en
|
|
37
|
-
- `childMode` â boolean
|
|
55
|
+
- `region` â `en | fr | es | de | it | pt | ru | ar | jp | kr | cn | nl | pl | tr | il | id`
|
|
56
|
+
- `childMode` â `boolean`
|
|
38
57
|
|
|
39
58
|
### Methods
|
|
40
|
-
| Method
|
|
41
|
-
|
|
42
|
-
| `start()` | Begin a new game
|
|
43
|
-
| `answer(a)` | `y` / `n` / `idk` / `p` / `pn`
|
|
44
|
-
| `back()` | Undo the previous answer
|
|
45
|
-
| `continue()` | Reject the current guess and keep playing
|
|
46
|
-
| `win()` | Confirm the guess and end the game
|
|
59
|
+
| Method | Description |
|
|
60
|
+
|---------------|----------------------------------------------|
|
|
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 |
|
|
47
66
|
|
|
48
67
|
### Properties
|
|
49
68
|
`question`, `step`, `progression`, `guess`, `guessed`, `finished`.
|
|
50
69
|
|
|
70
|
+
## Discord bot example
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
const { Akinator } = require('silent-akinator-pro');
|
|
74
|
+
const games = new Map();
|
|
75
|
+
|
|
76
|
+
client.on('messageCreate', async (msg) => {
|
|
77
|
+
if (msg.content === '!aki') {
|
|
78
|
+
const aki = new Akinator({ region: 'en' });
|
|
79
|
+
await aki.start();
|
|
80
|
+
games.set(msg.author.id, aki);
|
|
81
|
+
msg.reply(aki.question);
|
|
82
|
+
} else if (['y','n','idk','p','pn'].includes(msg.content)) {
|
|
83
|
+
const aki = games.get(msg.author.id);
|
|
84
|
+
if (!aki) return;
|
|
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);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
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.
|
|
94
|
+
|
|
51
95
|
## License
|
|
52
|
-
|
|
96
|
+
|
|
97
|
+
MIT Š **Silent Tech**
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
|
|
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;
|
|
5
|
+
}
|
|
6
|
+
export interface AkinatorGuess {
|
|
7
|
+
id?: string; name?: string; description?: string; photo?: string; pseudo?: string;
|
|
8
|
+
}
|
|
9
|
+
export class Akinator {
|
|
10
|
+
constructor(opts?: AkinatorOptions);
|
|
11
|
+
region: string;
|
|
12
|
+
childMode: boolean;
|
|
13
|
+
question: string | null;
|
|
14
|
+
step: number;
|
|
15
|
+
progression: string;
|
|
16
|
+
guess: AkinatorGuess | null;
|
|
17
|
+
guessed: boolean;
|
|
18
|
+
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>;
|
|
24
|
+
}
|
|
25
|
+
export const REGIONS: Record<string,string>;
|
|
26
|
+
export default Akinator;
|
package/index.js
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
|
|
2
|
+
'use strict';
|
|
3
|
+
/*!
|
|
4
|
+
* silent-akinator-pro
|
|
5
|
+
* Working Akinator API wrapper (HTML-form endpoints, Cloudflare-safe).
|
|
6
|
+
* (c) Silent Tech â MIT
|
|
7
|
+
*/
|
|
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
|
+
};
|
|
17
|
+
|
|
18
|
+
const ANSWER_MAP = {
|
|
19
|
+
y: 0, yes: 0, '0': 0,
|
|
20
|
+
n: 1, no: 1, '1': 1,
|
|
21
|
+
idk: 2, "don't know": 2, dk: 2, '2': 2,
|
|
22
|
+
p: 3, probably: 3, '3': 3,
|
|
23
|
+
pn: 4, 'probably not': 4, '4': 4
|
|
24
|
+
};
|
|
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
|
+
}
|
|
76
|
+
|
|
77
|
+
function extract(html, re) {
|
|
78
|
+
const m = html.match(re);
|
|
79
|
+
return m ? m[1] : null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
class Akinator {
|
|
83
|
+
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.`);
|
|
108
|
+
}
|
|
109
|
+
|
|
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;
|
|
119
|
+
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;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
_answerCode(a) {
|
|
128
|
+
if (typeof a === 'number') a = String(a);
|
|
129
|
+
const code = ANSWER_MAP[String(a).toLowerCase().trim()];
|
|
130
|
+
if (code === undefined) throw new Error(`Invalid answer "${a}". Use y / n / idk / p / pn.`);
|
|
131
|
+
return code;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async answer(a) {
|
|
135
|
+
if (this.finished) throw new Error('Game already finished.');
|
|
136
|
+
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 });
|
|
149
|
+
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
|
+
}
|
|
167
|
+
|
|
168
|
+
if (data.id_proposition || data.name_proposition) {
|
|
169
|
+
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
|
|
175
|
+
};
|
|
176
|
+
this.guessed = true;
|
|
177
|
+
return this;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
this.question = data.question || this.question;
|
|
181
|
+
this.step = parseInt(data.step ?? this.step + 1, 10);
|
|
182
|
+
this.progression = data.progression || this.progression;
|
|
183
|
+
this.guessed = false;
|
|
184
|
+
this.guess = null;
|
|
185
|
+
return this;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
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);
|
|
202
|
+
this.progression = data.progression || this.progression;
|
|
203
|
+
this.guessed = false;
|
|
204
|
+
this.guess = null;
|
|
205
|
+
return this;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async continue() {
|
|
209
|
+
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);
|
|
222
|
+
this.progression = data.progression || this.progression;
|
|
223
|
+
this.guessed = false;
|
|
224
|
+
this.guess = null;
|
|
225
|
+
return this;
|
|
226
|
+
}
|
|
227
|
+
|
|
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 });
|
|
239
|
+
this.finished = true;
|
|
240
|
+
return this.guess;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
module.exports = { Akinator, REGIONS };
|
|
245
|
+
module.exports.default = Akinator;
|
package/package.json
CHANGED
|
@@ -1,34 +1,67 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "silent-akinator-pro",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "
|
|
6
|
-
"types": "
|
|
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)",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"type": "commonjs",
|
|
7
8
|
"scripts": {
|
|
8
|
-
"test": "node
|
|
9
|
+
"test": "node test.js"
|
|
9
10
|
},
|
|
10
11
|
"keywords": [
|
|
11
12
|
"akinator",
|
|
13
|
+
"akinator-api",
|
|
14
|
+
"akinator-wrapper",
|
|
15
|
+
"akinator-client",
|
|
16
|
+
"akinator-node",
|
|
17
|
+
"akinator-js",
|
|
18
|
+
"akinator-bot",
|
|
19
|
+
"akinator-game",
|
|
12
20
|
"silent",
|
|
13
21
|
"silent-tech",
|
|
14
22
|
"silent-akinator",
|
|
15
|
-
"
|
|
23
|
+
"silent-akinator-pro",
|
|
16
24
|
"genie",
|
|
25
|
+
"guess",
|
|
26
|
+
"guessing-game",
|
|
27
|
+
"20-questions",
|
|
28
|
+
"twenty-questions",
|
|
29
|
+
"character-guess",
|
|
30
|
+
"ai-guess",
|
|
31
|
+
"game",
|
|
17
32
|
"api",
|
|
18
|
-
"wrapper"
|
|
33
|
+
"wrapper",
|
|
34
|
+
"client",
|
|
35
|
+
"cloudflare-bypass",
|
|
36
|
+
"working-akinator",
|
|
37
|
+
"akinator-2025",
|
|
38
|
+
"akinator-2026",
|
|
39
|
+
"discord-bot",
|
|
40
|
+
"telegram-bot",
|
|
41
|
+
"whatsapp-bot",
|
|
42
|
+
"chatbot",
|
|
43
|
+
"fun-api"
|
|
19
44
|
],
|
|
20
|
-
"author": "Silent Tech",
|
|
45
|
+
"author": "Silent Tech <silent.tech.offc@gmail.com>",
|
|
21
46
|
"license": "MIT",
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "git+https://github.com/silent-tech/silent-akinator-pro.git"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://www.npmjs.com/package/silent-akinator-pro",
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/silent-tech/silent-akinator-pro/issues"
|
|
25
54
|
},
|
|
26
55
|
"engines": {
|
|
27
56
|
"node": ">=14"
|
|
28
57
|
},
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"url": "https://github.com/silent-tech/silent-akinator-pro"
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"tough-cookie": "^4.1.4"
|
|
32
60
|
},
|
|
33
|
-
"
|
|
61
|
+
"files": [
|
|
62
|
+
"index.js",
|
|
63
|
+
"index.d.ts",
|
|
64
|
+
"README.md",
|
|
65
|
+
"LICENSE"
|
|
66
|
+
]
|
|
34
67
|
}
|
package/src/index.d.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
|
|
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;
|
|
5
|
-
}
|
|
6
|
-
export interface Guess {
|
|
7
|
-
id: string;
|
|
8
|
-
name: string;
|
|
9
|
-
description: string;
|
|
10
|
-
image: string;
|
|
11
|
-
}
|
|
12
|
-
export type AnswerInput = 'y'|'n'|'idk'|'p'|'pn'|0|1|2|3|4|string|number;
|
|
13
|
-
|
|
14
|
-
export class Akinator {
|
|
15
|
-
constructor(options?: AkinatorOptions);
|
|
16
|
-
region: string;
|
|
17
|
-
childMode: boolean;
|
|
18
|
-
session: string | null;
|
|
19
|
-
signature: string | null;
|
|
20
|
-
step: number;
|
|
21
|
-
progression: string;
|
|
22
|
-
question: string | null;
|
|
23
|
-
guess: Guess | null;
|
|
24
|
-
guessed: boolean;
|
|
25
|
-
finished: boolean;
|
|
26
|
-
start(): Promise<{ question: string; step: number; progression: string }>;
|
|
27
|
-
answer(ans: AnswerInput): Promise<{ question?: string; step?: number; progression?: string; guess?: Guess }>;
|
|
28
|
-
back(): Promise<{ question: string; step: number; progression: string }>;
|
|
29
|
-
continue(): Promise<{ question: string; step: number; progression: string }>;
|
|
30
|
-
win(): Promise<{ winner: Guess | null }>;
|
|
31
|
-
}
|
|
32
|
-
export const REGIONS: string[];
|
|
33
|
-
export default Akinator;
|
package/src/index.js
DELETED
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* silent-akinator-pro
|
|
4
|
-
* Silent Tech â Working Akinator API wrapper.
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* const { Akinator } = require('silent-akinator-pro');
|
|
8
|
-
* const aki = new Akinator({ region: 'en', childMode: false });
|
|
9
|
-
* await aki.start();
|
|
10
|
-
* console.log(aki.question);
|
|
11
|
-
* await aki.answer('y'); // y / n / idk / p / pn
|
|
12
|
-
* if (aki.guess) console.log(aki.guess);
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const axios = require('axios');
|
|
16
|
-
const cheerio = require('cheerio');
|
|
17
|
-
|
|
18
|
-
const REGIONS = ['en','ar','cn','de','es','fr','il','it','jp','kr','nl','pl','pt','ru','tr','id'];
|
|
19
|
-
|
|
20
|
-
const ANSWER_MAP = {
|
|
21
|
-
// yes
|
|
22
|
-
'y': 0, 'yes': 0, 'oui': 0, '0': 0,
|
|
23
|
-
// no
|
|
24
|
-
'n': 1, 'no': 1, 'non': 1, '1': 1,
|
|
25
|
-
// don't know
|
|
26
|
-
'idk': 2, 'dk': 2, "don't know": 2, 'dont know': 2, '2': 2,
|
|
27
|
-
// probably
|
|
28
|
-
'p': 3, 'probably': 3, '3': 3,
|
|
29
|
-
// probably not
|
|
30
|
-
'pn': 4, 'probably not': 4, '4': 4
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
class Akinator {
|
|
34
|
-
constructor({ region = 'en', childMode = false } = {}) {
|
|
35
|
-
if (!REGIONS.includes(region)) {
|
|
36
|
-
throw new Error(`Invalid region "${region}". Use one of: ${REGIONS.join(', ')}`);
|
|
37
|
-
}
|
|
38
|
-
this.region = region;
|
|
39
|
-
this.childMode = !!childMode;
|
|
40
|
-
this.baseURL = `https://${region}.akinator.com`;
|
|
41
|
-
this.session = null;
|
|
42
|
-
this.signature = null;
|
|
43
|
-
this.step = 0;
|
|
44
|
-
this.progression = '0.00000';
|
|
45
|
-
this.question = null;
|
|
46
|
-
this.guess = null; // { name, description, image, id }
|
|
47
|
-
this.guessed = false;
|
|
48
|
-
this.finished = false;
|
|
49
|
-
this._client = axios.create({
|
|
50
|
-
baseURL: this.baseURL,
|
|
51
|
-
headers: {
|
|
52
|
-
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 SilentTech/1.0',
|
|
53
|
-
'Accept-Language': 'en-US,en;q=0.9',
|
|
54
|
-
'Origin': this.baseURL,
|
|
55
|
-
'Referer': `${this.baseURL}/game`,
|
|
56
|
-
'X-Requested-With': 'XMLHttpRequest'
|
|
57
|
-
},
|
|
58
|
-
timeout: 20000,
|
|
59
|
-
validateStatus: () => true
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** Begin a new game session. */
|
|
64
|
-
async start() {
|
|
65
|
-
const body = new URLSearchParams({
|
|
66
|
-
sid: '1',
|
|
67
|
-
cm: this.childMode ? 'true' : 'false'
|
|
68
|
-
}).toString();
|
|
69
|
-
|
|
70
|
-
const res = await this._client.post('/game', body, {
|
|
71
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
if (res.status >= 400) {
|
|
75
|
-
throw new Error(`Failed to start game (HTTP ${res.status})`);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const html = res.data;
|
|
79
|
-
const $ = cheerio.load(html);
|
|
80
|
-
|
|
81
|
-
// session + signature live in inline JS / hidden inputs
|
|
82
|
-
const sessionMatch = html.match(/#session['"]?\s*\)\s*\.val\(\s*['"]?(\d+)['"]?\s*\)/)
|
|
83
|
-
|| html.match(/name=['"]session['"][^>]*value=['"](\d+)['"]/);
|
|
84
|
-
const signatureMatch = html.match(/#signature['"]?\s*\)\s*\.val\(\s*['"]?(\d+)['"]?\s*\)/)
|
|
85
|
-
|| html.match(/name=['"]signature['"][^>]*value=['"](\d+)['"]/);
|
|
86
|
-
|
|
87
|
-
if (!sessionMatch || !signatureMatch) {
|
|
88
|
-
throw new Error('Akinator did not return a valid session. The site layout may have changed.');
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
this.session = sessionMatch[1];
|
|
92
|
-
this.signature = signatureMatch[1];
|
|
93
|
-
this.step = 0;
|
|
94
|
-
this.progression = '0.00000';
|
|
95
|
-
this.guess = null;
|
|
96
|
-
this.guessed = false;
|
|
97
|
-
this.finished = false;
|
|
98
|
-
|
|
99
|
-
const q = $('#question-label').text().trim() || $('.question-text').text().trim();
|
|
100
|
-
this.question = q || null;
|
|
101
|
-
return { question: this.question, step: this.step, progression: this.progression };
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
_normalizeAnswer(a) {
|
|
105
|
-
if (typeof a === 'number') {
|
|
106
|
-
if (a < 0 || a > 4) throw new Error('Answer must be 0-4');
|
|
107
|
-
return a;
|
|
108
|
-
}
|
|
109
|
-
const k = String(a).toLowerCase().trim();
|
|
110
|
-
if (!(k in ANSWER_MAP)) {
|
|
111
|
-
throw new Error(`Unknown answer "${a}". Use y / n / idk / p / pn or 0-4.`);
|
|
112
|
-
}
|
|
113
|
-
return ANSWER_MAP[k];
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/** Answer the current question. */
|
|
117
|
-
async answer(ans) {
|
|
118
|
-
if (!this.session) throw new Error('Call start() first.');
|
|
119
|
-
if (this.finished) throw new Error('Game already finished.');
|
|
120
|
-
|
|
121
|
-
const answer = this._normalizeAnswer(ans);
|
|
122
|
-
|
|
123
|
-
const body = new URLSearchParams({
|
|
124
|
-
step: String(this.step),
|
|
125
|
-
progression: String(this.progression),
|
|
126
|
-
sid: '1',
|
|
127
|
-
cm: this.childMode ? 'true' : 'false',
|
|
128
|
-
answer: String(answer),
|
|
129
|
-
step_last_proposition: '',
|
|
130
|
-
session: this.session,
|
|
131
|
-
signature: this.signature
|
|
132
|
-
}).toString();
|
|
133
|
-
|
|
134
|
-
const res = await this._client.post('/answer', body, {
|
|
135
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
if (res.status >= 400) {
|
|
139
|
-
throw new Error(`Answer failed (HTTP ${res.status})`);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const data = typeof res.data === 'string' ? safeJSON(res.data) : res.data;
|
|
143
|
-
if (!data) throw new Error('Invalid response from Akinator.');
|
|
144
|
-
|
|
145
|
-
// Akinator decided to guess
|
|
146
|
-
if (data.id_proposition || data.id_base_proposition) {
|
|
147
|
-
this.guess = {
|
|
148
|
-
id: data.id_proposition || data.id_base_proposition,
|
|
149
|
-
name: data.name_proposition,
|
|
150
|
-
description: data.description_proposition,
|
|
151
|
-
image: data.photo
|
|
152
|
-
};
|
|
153
|
-
this.guessed = true;
|
|
154
|
-
return { guess: this.guess };
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
this.step = parseInt(data.step ?? this.step + 1, 10);
|
|
158
|
-
this.progression = String(data.progression ?? this.progression);
|
|
159
|
-
this.question = data.question || null;
|
|
160
|
-
|
|
161
|
-
return {
|
|
162
|
-
question: this.question,
|
|
163
|
-
step: this.step,
|
|
164
|
-
progression: this.progression
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/** Undo the last answer. */
|
|
169
|
-
async back() {
|
|
170
|
-
if (!this.session) throw new Error('Call start() first.');
|
|
171
|
-
if (this.step <= 0) throw new Error('No previous question.');
|
|
172
|
-
|
|
173
|
-
const body = new URLSearchParams({
|
|
174
|
-
step: String(this.step),
|
|
175
|
-
progression: String(this.progression),
|
|
176
|
-
sid: '1',
|
|
177
|
-
cm: this.childMode ? 'true' : 'false',
|
|
178
|
-
session: this.session,
|
|
179
|
-
signature: this.signature
|
|
180
|
-
}).toString();
|
|
181
|
-
|
|
182
|
-
const res = await this._client.post('/cancel_answer', body, {
|
|
183
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
const data = typeof res.data === 'string' ? safeJSON(res.data) : res.data;
|
|
187
|
-
if (!data) throw new Error('Invalid response from Akinator.');
|
|
188
|
-
|
|
189
|
-
this.step = parseInt(data.step ?? Math.max(0, this.step - 1), 10);
|
|
190
|
-
this.progression = String(data.progression ?? this.progression);
|
|
191
|
-
this.question = data.question || this.question;
|
|
192
|
-
|
|
193
|
-
return {
|
|
194
|
-
question: this.question,
|
|
195
|
-
step: this.step,
|
|
196
|
-
progression: this.progression
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/** Tell Akinator the guess was wrong and continue playing. */
|
|
201
|
-
async continue() {
|
|
202
|
-
if (!this.guessed) throw new Error('No active guess to reject.');
|
|
203
|
-
const body = new URLSearchParams({
|
|
204
|
-
step: String(this.step),
|
|
205
|
-
progression: String(this.progression),
|
|
206
|
-
sid: '1',
|
|
207
|
-
cm: this.childMode ? 'true' : 'false',
|
|
208
|
-
session: this.session,
|
|
209
|
-
signature: this.signature
|
|
210
|
-
}).toString();
|
|
211
|
-
|
|
212
|
-
const res = await this._client.post('/exclude', body, {
|
|
213
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
const data = typeof res.data === 'string' ? safeJSON(res.data) : res.data;
|
|
217
|
-
if (!data) throw new Error('Invalid response from Akinator.');
|
|
218
|
-
|
|
219
|
-
this.guess = null;
|
|
220
|
-
this.guessed = false;
|
|
221
|
-
this.step = parseInt(data.step ?? this.step + 1, 10);
|
|
222
|
-
this.progression = String(data.progression ?? this.progression);
|
|
223
|
-
this.question = data.question || null;
|
|
224
|
-
|
|
225
|
-
return {
|
|
226
|
-
question: this.question,
|
|
227
|
-
step: this.step,
|
|
228
|
-
progression: this.progression
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/** Confirm the guess was correct (ends the game). */
|
|
233
|
-
async win() {
|
|
234
|
-
this.finished = true;
|
|
235
|
-
return { winner: this.guess };
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function safeJSON(s) {
|
|
240
|
-
try { return JSON.parse(s); } catch { return null; }
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
module.exports = { Akinator, REGIONS, ANSWER_MAP };
|
|
244
|
-
module.exports.default = Akinator;
|