alexa-ai 2.1.2 → 2.3.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/CHANGELOG.md +53 -0
- package/README.md +68 -11
- package/examples/diagnose.js +134 -0
- package/examples/text2img-standalone.js +200 -0
- package/package.json +1 -1
- package/src/AlexaAI.js +83 -18
- package/src/core/Config.js +29 -2
- package/src/core/DeepAIClient.js +334 -30
- package/test/run-tests.js +171 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,59 @@ All notable changes to `alexa-ai` are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
|
|
5
5
|
[Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [2.3.0] — 2026-09-07
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **Multiple `/api/*` transports** (`transport` option / `DEEPAI_TRANSPORT`):
|
|
11
|
+
`'auto'` (default) tries `fetch`, then system `curl`, then a
|
|
12
|
+
`curl-impersonate` binary (Chrome TLS profile) when one is available;
|
|
13
|
+
`'fetch' | 'curl' | 'impersonate'` pin a single transport. A refusal of the
|
|
14
|
+
form "Please try this model on deepai.org" can be specific to the client
|
|
15
|
+
TLS stack, so `auto` retries that error through the next transport — quota
|
|
16
|
+
and auth errors are never re-driven. `curlImpersonatePath` /
|
|
17
|
+
`DEEPAI_CURL_IMPERSONATE` locate the binary.
|
|
18
|
+
- `examples/diagnose.js` — runs the browser-shaped request through every
|
|
19
|
+
transport with fresh keys and reports which one the network accepts.
|
|
20
|
+
- `examples/text2img-standalone.js` gained `--transport fetch|curl|impersonate`
|
|
21
|
+
and `--imp <path>`.
|
|
22
|
+
|
|
23
|
+
## [2.2.1] — 2026-09-07
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- Device identity: the client keeps a stable random `deepai_device_id`
|
|
27
|
+
(32 bytes, base64url) and sends it as a cookie; anonymous `/api/*`
|
|
28
|
+
generation is rate-limited per device. Override with `deviceId` /
|
|
29
|
+
`DEEPAI_DEVICE_ID`.
|
|
30
|
+
- `examples/text2img-standalone.js` — zero-dependency CLI for anonymous
|
|
31
|
+
image generation using the full browser request dialect.
|
|
32
|
+
|
|
33
|
+
## [2.2.0] — 2026-09-07
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
- Image generation with anonymous `tryit-…` keys. The key's hex part is a
|
|
37
|
+
deterministic hash over the User-Agent
|
|
38
|
+
(`H(UA + H(UA + H(UA + digits + SALT)))`, see `DeepAIClient._islandHash`);
|
|
39
|
+
`generateTryItKey(userAgent)` now derives it correctly, and `headers()`
|
|
40
|
+
mints a fresh key per request because anonymous keys are single-use.
|
|
41
|
+
- `generateImage()` now sends the browser dialect for anonymous requests
|
|
42
|
+
(`generation_source`, `width`/`height` mapped from `aspectRatio`,
|
|
43
|
+
`image_generator_version`, `quality`).
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
- Anonymous browser-shaped retry when a registered non-Pro key is refused
|
|
47
|
+
(result `via: 'anonymous'`, `runApiWithTryItKey(name, fields)`); disable
|
|
48
|
+
with `{ noAnonymousFallback: true }`.
|
|
49
|
+
- `DEEPAI_KEY` env alias alongside `DEEPAI_API_KEY`.
|
|
50
|
+
- `DeepAIClient.isTryItKey(key)`.
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
- Extended refusal classification ("Pro members", "model only available…",
|
|
54
|
+
"insufficient_credits", …) so quota errors rotate keys and report
|
|
55
|
+
`DEEPAI_QUOTA_EXCEEDED` correctly.
|
|
56
|
+
- `share_url` preferred over `output_url` in generated-image results.
|
|
57
|
+
- Default `userAgent` updated (the tryit key hash is computed over it; keep
|
|
58
|
+
`userAgent` stable if you persist keys).
|
|
59
|
+
|
|
7
60
|
## [2.1.2] — 2026-09-06
|
|
8
61
|
|
|
9
62
|
### Changed
|
package/README.md
CHANGED
|
@@ -427,14 +427,25 @@ await ai.deepai.runApi('waifu2x', { image: buffer }); // any /api/<name> e
|
|
|
427
427
|
|
|
428
428
|
All media arguments accept the same shapes as `chat({ image })`.
|
|
429
429
|
|
|
430
|
-
**`generateImage()` on a free key.**
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
430
|
+
**`generateImage()` on a free key.** Three routes are tried, in order:
|
|
431
|
+
|
|
432
|
+
1. `POST /api/text2img` with your key. Pro keys succeed here immediately.
|
|
433
|
+
With an anonymous `tryit-…` key the engine automatically sends the exact
|
|
434
|
+
browser dialect (`generation_source=chat`, `width`/`height` mapped from
|
|
435
|
+
`aspectRatio`, `image_generator_version=hd`, `quality=true`) and mints a
|
|
436
|
+
**fresh single-use key per request** — tryit keys are hash-validated
|
|
437
|
+
against the User-Agent and burned after one request.
|
|
438
|
+
2. If a registered (non-Pro) key is refused with `402 "Pro members in good
|
|
439
|
+
standing"`, one anonymous browser-shaped retry. (`noAnonymousFallback:
|
|
440
|
+
true` disables it.)
|
|
441
|
+
3. The legacy in-chat `generate_image` function call (kept for model
|
|
442
|
+
versions that still honour it).
|
|
443
|
+
|
|
444
|
+
The result reports the route that answered (`via: 'api' | 'anonymous' |
|
|
445
|
+
'chat'`). Options: `{ apiOnly }`, `{ chatToolOnly }`, `{ noAnonymousFallback }`,
|
|
446
|
+
`{ aspectRatio: '16:9' }`, and `width` / `height` / `image_generator_version`
|
|
447
|
+
for the API. The returned URL prefers `share_url` (public, stable) over
|
|
448
|
+
`output_url`, matching the browser client.
|
|
438
449
|
|
|
439
450
|
**`summarizeText()`** follows the same pattern — `/api/summarization` first, a
|
|
440
451
|
stateless chat request as the fallback.
|
|
@@ -841,10 +852,56 @@ error), and the reply came from DeepAI's own web access. Point
|
|
|
841
852
|
`webSearchProvider` at a search API you control if the public endpoints are
|
|
842
853
|
blocked where the bot runs.
|
|
843
854
|
|
|
855
|
+
**Works in the browser playground but fails in Postman / Node.js** — three
|
|
856
|
+
separate requirements:
|
|
857
|
+
|
|
858
|
+
1. **Anonymous keys are single-use and User-Agent-bound.** Each key is
|
|
859
|
+
`tryit-<digits>-<hash>` with
|
|
860
|
+
`hash = H(UA + H(UA + H(UA + digits + "hackers_become_a_little_stinkier_every_time_they_hack")))`,
|
|
861
|
+
validated against the request's `User-Agent` header. A reused key or
|
|
862
|
+
random hex → `401 "Please pass a valid Api-Key"`. A fresh, correctly
|
|
863
|
+
hashed key must be minted per request — `headers()` does this
|
|
864
|
+
automatically for `tryit-…` keys.
|
|
865
|
+
2. **The body must be `multipart/form-data`** (never JSON), with
|
|
866
|
+
`generation_source=img` (model page) or `generation_source=chat` +
|
|
867
|
+
`width`/`height`/`image_generator_version=hd`/`quality=true` (chat page).
|
|
868
|
+
Never set `Content-Type` manually — the multipart boundary is generated.
|
|
869
|
+
3. **`Origin: https://deepai.org` is required**, and anonymous generation is
|
|
870
|
+
refused from datacenter/VPN IPs (`401 "Please try this model on deepai.org"`) —
|
|
871
|
+
run from a residential IP. Passing `deviceId` (the `deepai_device_id`
|
|
872
|
+
cookie value) also helps the per-device quota.
|
|
873
|
+
|
|
874
|
+
`examples/text2img-standalone.js` is a zero-dependency CLI implementing the
|
|
875
|
+
full recipe, and `examples/diagnose.js` runs the same request through every
|
|
876
|
+
transport on your machine and prints which one DeepAI accepts.
|
|
877
|
+
|
|
878
|
+
**`transport` option (2.3.0).** Some networks serve non-browser TLS stacks a
|
|
879
|
+
refusal (`"Please try this model on deepai.org"`) even with a perfectly valid
|
|
880
|
+
key. `/api/*` calls therefore support multiple transports:
|
|
881
|
+
|
|
882
|
+
```js
|
|
883
|
+
new AlexaAI({ key, postgresUrl, transport: 'auto' }) // default: fetch → curl → curl-impersonate
|
|
884
|
+
new AlexaAI({ key, postgresUrl, transport: 'curl' }) // system curl only
|
|
885
|
+
new AlexaAI({ key, postgresUrl, transport: 'impersonate', curlImpersonatePath: 'C:/tools/curl-impersonate.exe' })
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
With `transport: 'auto'` (the default) the engine starts with `fetch` and, on
|
|
889
|
+
a transport-specific refusal, retries the request through system `curl`
|
|
890
|
+
(shipped with Windows 10+, macOS and Linux) and then through a
|
|
891
|
+
`curl-impersonate` binary if one is on PATH (a Chrome TLS profile; set
|
|
892
|
+
`curlImpersonatePath` or `DEEPAI_CURL_IMPERSONATE` to point at it — builds:
|
|
893
|
+
github.com/lexiforest/curl-impersonate/releases). Quota and auth errors are
|
|
894
|
+
never re-driven through other transports. Run `node examples/diagnose.js` to
|
|
895
|
+
see which transport your network accepts.
|
|
896
|
+
|
|
844
897
|
**`generateImage()` returns `{ ok: false, error: 'DEEPAI_QUOTA_EXCEEDED' }`**
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
898
|
+
`/api/text2img` is Pro-only for registered keys ("APIs are only available for
|
|
899
|
+
Pro members in good standing"), the anonymous browser-shaped retry was
|
|
900
|
+
refused too (DeepAI soft-blocks free image generation from datacenter IPs
|
|
901
|
+
with "Please try this model on deepai.org" — run the bot from a residential
|
|
902
|
+
IP), and the in-chat tool produced no image. Add a Pro key, more keys
|
|
903
|
+
(`keys: [...]`), or run from a non-datacenter IP. `message` carries DeepAI's
|
|
904
|
+
exact wording for each attempted route.
|
|
848
905
|
|
|
849
906
|
**Photos are answered with "I can't view images right now"**
|
|
850
907
|
Native vision needs a paid DeepAI key; on a free key only text inside the
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
/**
|
|
4
|
+
* diagnose.js — DeepAI text2img connectivity diagnostic.
|
|
5
|
+
*
|
|
6
|
+
* Run ON THE MACHINE where the browser playground works:
|
|
7
|
+
*
|
|
8
|
+
* node examples/diagnose.js
|
|
9
|
+
* node examples/diagnose.js --device-id <your deepai_device_id cookie>
|
|
10
|
+
* node examples/diagnose.js --imp /path/to/curl-impersonate
|
|
11
|
+
*
|
|
12
|
+
* Each test sends the browser-shaped request with a FRESH single-use key
|
|
13
|
+
* through a different transport and prints the raw server response, so one
|
|
14
|
+
* run shows exactly which transport DeepAI accepts on your network:
|
|
15
|
+
*
|
|
16
|
+
* 1. Node fetch (the library default)
|
|
17
|
+
* 2. system curl
|
|
18
|
+
* 3. curl-impersonate with a Chrome TLS profile (if the binary is found)
|
|
19
|
+
*
|
|
20
|
+
* Zero dependencies, Node 18+.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const API = 'https://api.deepai.org/api/text2img';
|
|
24
|
+
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36';
|
|
25
|
+
const PROFILE_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36';
|
|
26
|
+
const SALT = 'hackers_become_a_little_stinkier_every_time_they_hack';
|
|
27
|
+
|
|
28
|
+
// ---- key hash (same algorithm as DeepAIClient._islandHash) ----------------
|
|
29
|
+
function islandHash(input) {
|
|
30
|
+
const a = [];
|
|
31
|
+
for (let b = 0; 64 > b; ) a[b] = 0 | (4294967296 * Math.sin(++b % Math.PI));
|
|
32
|
+
let d, e, f, g = [(d = 1732584193), (e = 4023233417), ~d, ~e], h = [];
|
|
33
|
+
const l = unescape(encodeURI(input)) + '\u0080';
|
|
34
|
+
let k = l.length;
|
|
35
|
+
let c = (--k / 4 + 2) | 15;
|
|
36
|
+
for (h[--c] = 8 * k; ~k; ) h[k >> 2] |= l.charCodeAt(k) << (8 * k--);
|
|
37
|
+
for (let b = 0, m = 0; b < c; b += 16) {
|
|
38
|
+
for (k = g; 64 > m; k = [ (f = k[3]), d + (((f = k[0] + [d & e | ~d & f, f & d | ~f & e, d ^ e ^ f, e ^ (d | ~f)][(k = m >> 4)] + a[m] + ~~h[b | [m, 5 * m + 1, 3 * m + 5, 7 * m][k] & 15]) << (k = [7, 12, 17, 22, 5, 9, 14, 20, 4, 11, 16, 23, 6, 10, 15, 21][4 * k + (m++ % 4)])) | (f >>> -k)), d, e ]) {
|
|
39
|
+
d = k[1] | 0;
|
|
40
|
+
e = k[2];
|
|
41
|
+
}
|
|
42
|
+
for (m = 4; m; ) g[--m] += k[m];
|
|
43
|
+
}
|
|
44
|
+
let result = '';
|
|
45
|
+
for (let i = 0; 32 > i; ) result += ((g[i >> 3] >> 4 * (1 ^ i++)) & 15).toString(16);
|
|
46
|
+
return result.split('').reverse().join('');
|
|
47
|
+
}
|
|
48
|
+
const freshKey = (ua) => {
|
|
49
|
+
const digits = String(Math.round(Math.random() * 100000000000));
|
|
50
|
+
return `tryit-${digits}-${islandHash(ua + islandHash(ua + islandHash(ua + digits + SALT)))}`;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// ---- args -------------------------------------------------------------------
|
|
54
|
+
const args = process.argv.slice(2);
|
|
55
|
+
const opt = {};
|
|
56
|
+
for (let i = 0; i < args.length; i++) {
|
|
57
|
+
if (args[i] === '--device-id') opt.deviceId = args[++i];
|
|
58
|
+
else if (args[i] === '--imp') opt.imp = args[++i];
|
|
59
|
+
}
|
|
60
|
+
const deviceId = opt.deviceId || Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString('base64url');
|
|
61
|
+
const cookie = `deepai_device_id=${deviceId}`;
|
|
62
|
+
|
|
63
|
+
// ---- transports ---------------------------------------------------------------
|
|
64
|
+
async function viaFetch() {
|
|
65
|
+
const form = new FormData();
|
|
66
|
+
form.append('text', 'a small red boat on a calm lake');
|
|
67
|
+
form.append('generation_source', 'img');
|
|
68
|
+
const res = await fetch(API, {
|
|
69
|
+
method: 'POST', body: form,
|
|
70
|
+
headers: { 'api-key': freshKey(UA), 'User-Agent': UA, Origin: 'https://deepai.org', Referer: 'https://deepai.org/machine-learning-model/text2img', Accept: '*/*', Cookie: cookie },
|
|
71
|
+
});
|
|
72
|
+
return { status: res.status, body: await res.text() };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function runCurl(binary, impersonate) {
|
|
76
|
+
const { execFileSync } = require('child_process');
|
|
77
|
+
const ua = impersonate ? PROFILE_UA : UA;
|
|
78
|
+
const a = impersonate ? ['--impersonate', 'chrome136'] : [];
|
|
79
|
+
a.push(API, '-sS', '--compressed', '--max-time', '60', '-X', 'POST',
|
|
80
|
+
'-H', `api-key: ${freshKey(ua)}`);
|
|
81
|
+
if (!impersonate) a.push('-H', `User-Agent: ${ua}`);
|
|
82
|
+
a.push('-H', 'Origin: https://deepai.org',
|
|
83
|
+
'-H', 'Referer: https://deepai.org/machine-learning-model/text2img',
|
|
84
|
+
'-H', 'Accept: */*',
|
|
85
|
+
'-H', `Cookie: ${cookie}`,
|
|
86
|
+
'-F', 'text=a small red boat on a calm lake',
|
|
87
|
+
'-F', 'generation_source=img',
|
|
88
|
+
'-w', '\n%{http_code}');
|
|
89
|
+
const out = execFileSync(binary, a, { encoding: 'utf8', timeout: 60000, stdio: ['ignore', 'pipe', 'pipe'] }).replace(/\r/g, '');
|
|
90
|
+
const cut = out.lastIndexOf('\n');
|
|
91
|
+
return { status: Number(out.slice(cut + 1).trim()), body: out.slice(0, cut) };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function label(t) {
|
|
95
|
+
const b = String(t.body).slice(0, 140).replace(/\s+/g, ' ');
|
|
96
|
+
if (t.status === 200 && /share_url|output_url/.test(t.body)) return `✅ SUCCESS — image generated (HTTP 200)`;
|
|
97
|
+
if (/valid Api-Key/i.test(b)) return `❌ key rejected (unexpected — fresh key was used)`;
|
|
98
|
+
if (/try this model/i.test(b)) return `❌ transport refused ("Please try this model on deepai.org")`;
|
|
99
|
+
if (/Pro members/i.test(b)) return `❌ account-level refusal (needs a Pro key)`;
|
|
100
|
+
if (/try it exceeded/i.test(b)) return `⚠️ free quota exhausted for this device/IP — retry later or change --device-id`;
|
|
101
|
+
return `❌ HTTP ${t.status}: ${b}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
(async () => {
|
|
105
|
+
console.log(`DeepAI text2img diagnostic — device ${deviceId.slice(0, 8)}…\n`);
|
|
106
|
+
const rows = [];
|
|
107
|
+
try { rows.push(['1. Node fetch (library default)', label(await viaFetch())]); } catch (e) { rows.push(['1. Node fetch', `⚠️ ${e.message}`]); }
|
|
108
|
+
try { rows.push(['2. system curl', label(runCurl('curl', false))]); } catch (e) { rows.push(['2. system curl', `⚠️ ${e.message.split('\n')[0]}`]); }
|
|
109
|
+
|
|
110
|
+
let impBin = opt.imp;
|
|
111
|
+
if (!impBin) {
|
|
112
|
+
const { execFileSync } = require('child_process');
|
|
113
|
+
for (const name of ['curl-impersonate', 'curl-impersonate.exe']) {
|
|
114
|
+
try { execFileSync(name, ['--version'], { timeout: 5000, stdio: 'ignore' }); impBin = name; break; } catch { /* keep looking */ }
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (impBin) {
|
|
118
|
+
try { rows.push([`3. curl-impersonate (${impBin})`, label(runCurl(impBin, true))]); } catch (e) { rows.push(['3. curl-impersonate', `⚠️ ${e.message.split('\n')[0]}`]); }
|
|
119
|
+
} else {
|
|
120
|
+
rows.push(['3. curl-impersonate', '⏭ skipped — binary not found (see README for install)']);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
for (const [name, v] of rows) console.log(`${name.padEnd(34)} ${v}`);
|
|
124
|
+
console.log(`
|
|
125
|
+
Reading the results:
|
|
126
|
+
- A ✅ on ANY line → that transport works; use it (library: transport option,
|
|
127
|
+
standalone: --transport).
|
|
128
|
+
- ❌ on line 1 only → non-browser TLS stack refused; use 'curl' or
|
|
129
|
+
'impersonate'.
|
|
130
|
+
- ❌ on lines 1+2, ✅ on 3 → strict browser-TLS matching; use 'impersonate'.
|
|
131
|
+
- ❌ everywhere → the IP is refused for anonymous generation, or the free
|
|
132
|
+
quota is exhausted. Compare with the browser: DevTools
|
|
133
|
+
→ Network → generate → text2img request → Response.`);
|
|
134
|
+
})();
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
/**
|
|
4
|
+
* text2img-standalone.js — zero-dependency DeepAI text-to-image CLI.
|
|
5
|
+
*
|
|
6
|
+
* Speaks the anonymous browser dialect: a fresh single-use `tryit-…` key
|
|
7
|
+
* (hashed over the User-Agent) per run, browser-identical headers and a
|
|
8
|
+
* multipart/form-data body, plus a stable `deepai_device_id` cookie.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* node examples/text2img-standalone.js "a cute orange cat"
|
|
12
|
+
* node examples/text2img-standalone.js "a cat" --out cat.jpg
|
|
13
|
+
* node examples/text2img-standalone.js "a cat" --aspect 16:9
|
|
14
|
+
* node examples/text2img-standalone.js "a cat" --device-id <cookieValue>
|
|
15
|
+
* node examples/text2img-standalone.js "a cat" --key <proKey>
|
|
16
|
+
* node examples/text2img-standalone.js "a cat" --transport curl
|
|
17
|
+
* node examples/text2img-standalone.js "a cat" --transport impersonate --imp /path/to/curl-impersonate
|
|
18
|
+
*
|
|
19
|
+
* Transports: 'fetch' (default, Node fetch) | 'curl' (system curl) |
|
|
20
|
+
* 'impersonate' (curl-impersonate binary, Chrome TLS profile). Some
|
|
21
|
+
* networks serve non-browser TLS stacks a refusal — if 'fetch' fails with
|
|
22
|
+
* "Please try this model on deepai.org", try 'curl', then 'impersonate'.
|
|
23
|
+
*
|
|
24
|
+
* Requires Node.js 18+. Anonymous generation is refused from
|
|
25
|
+
* datacenter/VPN IPs; run from a residential network.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const API_URL = 'https://api.deepai.org/api/text2img';
|
|
29
|
+
const SALT = 'hackers_become_a_little_stinkier_every_time_they_hack';
|
|
30
|
+
// Keep this EXACT string in sync with the User-Agent header below — the key
|
|
31
|
+
// hash is computed over it and the server recomputes it from the request.
|
|
32
|
+
const USER_AGENT =
|
|
33
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36';
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Deterministic key hash (see DeepAIClient._islandHash in the engine).
|
|
37
|
+
// The integer/bit-level behaviour is intentional — do not simplify it.
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
function islandHash(input) {
|
|
40
|
+
const a = [];
|
|
41
|
+
for (let b = 0; 64 > b; ) a[b] = 0 | (4294967296 * Math.sin(++b % Math.PI));
|
|
42
|
+
let d, e, f, g = [(d = 1732584193), (e = 4023233417), ~d, ~e], h = [];
|
|
43
|
+
const l = unescape(encodeURI(input)) + '';
|
|
44
|
+
let k = l.length;
|
|
45
|
+
let c = (--k / 4 + 2) | 15;
|
|
46
|
+
for (h[--c] = 8 * k; ~k; ) h[k >> 2] |= l.charCodeAt(k) << (8 * k--);
|
|
47
|
+
for (let b = 0, m = 0; b < c; b += 16) {
|
|
48
|
+
for (k = g; 64 > m; k = [ (f = k[3]), d + (((f = k[0] + [d & e | ~d & f, f & d | ~f & e, d ^ e ^ f, e ^ (d | ~f)][(k = m >> 4)] + a[m] + ~~h[b | [m, 5 * m + 1, 3 * m + 5, 7 * m][k] & 15]) << (k = [7, 12, 17, 22, 5, 9, 14, 20, 4, 11, 16, 23, 6, 10, 15, 21][4 * k + (m++ % 4)])) | (f >>> -k)), d, e ]) {
|
|
49
|
+
d = k[1] | 0;
|
|
50
|
+
e = k[2];
|
|
51
|
+
}
|
|
52
|
+
for (m = 4; m; ) g[--m] += k[m];
|
|
53
|
+
}
|
|
54
|
+
let result = '';
|
|
55
|
+
for (let i = 0; 32 > i; ) result += ((g[i >> 3] >> 4 * (1 ^ i++)) & 15).toString(16);
|
|
56
|
+
return result.split('').reverse().join('');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Mint a fresh anonymous key, valid for exactly ONE request with USER_AGENT. */
|
|
60
|
+
function freshTryItKey(userAgent = USER_AGENT) {
|
|
61
|
+
const digits = String(Math.round(Math.random() * 100000000000));
|
|
62
|
+
const H = islandHash;
|
|
63
|
+
return `tryit-${digits}-${H(userAgent + H(userAgent + H(userAgent + digits + SALT)))}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Random device id, same shape the site sets as the deepai_device_id cookie. */
|
|
67
|
+
function randomDeviceId() {
|
|
68
|
+
return Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString('base64url');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// CLI
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
function parseArgs(argv) {
|
|
75
|
+
const opts = { prompt: '', out: null, key: null, deviceId: randomDeviceId(), aspect: null, genSource: 'img', transport: 'fetch', imp: null };
|
|
76
|
+
for (let i = 0; i < argv.length; i++) {
|
|
77
|
+
const a = argv[i];
|
|
78
|
+
if (a === '--out') opts.out = argv[++i];
|
|
79
|
+
else if (a === '--key') opts.key = argv[++i];
|
|
80
|
+
else if (a === '--device-id') opts.deviceId = argv[++i];
|
|
81
|
+
else if (a === '--aspect') opts.aspect = argv[++i];
|
|
82
|
+
else if (a === '--chat-source') opts.genSource = 'chat';
|
|
83
|
+
else if (a === '--transport') opts.transport = argv[++i];
|
|
84
|
+
else if (a === '--imp') opts.imp = argv[++i];
|
|
85
|
+
else if (a === '--help' || a === '-h') opts.help = true;
|
|
86
|
+
else if (!opts.prompt) opts.prompt = a;
|
|
87
|
+
}
|
|
88
|
+
return opts;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const ASPECTS = { '16:9': [832, 448], '4:3': [768, 576], '1:1': [640, 640], '3:4': [576, 768], '9:16': [448, 832] };
|
|
92
|
+
|
|
93
|
+
async function main() {
|
|
94
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
95
|
+
if (opts.help || !opts.prompt) {
|
|
96
|
+
console.log('Usage: node examples/text2img-standalone.js "your prompt" [--out file.jpg] [--key PRO_KEY] [--device-id VALUE] [--aspect 16:9|1:1|9:16|4:3|3:4] [--chat-source] [--transport fetch|curl|impersonate] [--imp PATH]');
|
|
97
|
+
process.exit(opts.help ? 0 : 1);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ---- body: multipart/form-data, NEVER JSON -----------------------------
|
|
101
|
+
const form = new FormData();
|
|
102
|
+
form.append('text', opts.prompt);
|
|
103
|
+
form.append('generation_source', opts.genSource); // 'img' = model page, 'chat' = chat page
|
|
104
|
+
if (opts.aspect && ASPECTS[opts.aspect]) {
|
|
105
|
+
// the chat-page dialect maps aspect ratios to pixel sizes
|
|
106
|
+
const [w, h] = ASPECTS[opts.aspect];
|
|
107
|
+
form.append('width', String(w));
|
|
108
|
+
form.append('height', String(h));
|
|
109
|
+
form.append('image_generator_version', 'hd');
|
|
110
|
+
form.append('quality', 'true');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ---- headers: fresh single-use anonymous key (or your Pro key) ---------
|
|
114
|
+
const apiKey = opts.key || freshTryItKey();
|
|
115
|
+
const headers = {
|
|
116
|
+
'api-key': apiKey,
|
|
117
|
+
'User-Agent': USER_AGENT, // MUST match the UA the key was hashed with
|
|
118
|
+
Origin: 'https://deepai.org',
|
|
119
|
+
Referer: 'https://deepai.org/machine-learning-model/text2img',
|
|
120
|
+
Accept: '*/*',
|
|
121
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
122
|
+
Cookie: `deepai_device_id=${opts.deviceId}`,
|
|
123
|
+
// NOTE: do NOT set Content-Type yourself — undici adds the multipart
|
|
124
|
+
// boundary. A manual Content-Type without the boundary is rejected.
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
console.log(`Prompt : ${opts.prompt}`);
|
|
128
|
+
console.log(`Key : ${opts.key ? '(registered key — needs Pro)' : apiKey + ' (fresh, single-use)'}`);
|
|
129
|
+
console.log('POST : ' + API_URL);
|
|
130
|
+
|
|
131
|
+
let res;
|
|
132
|
+
if (opts.transport === 'curl' || opts.transport === 'impersonate') {
|
|
133
|
+
const { execFile } = require('child_process');
|
|
134
|
+
const binary = opts.transport === 'impersonate' ? (opts.imp || 'curl-impersonate') : 'curl';
|
|
135
|
+
// the chrome136 profile sends its own Mac Chrome UA — the anonymous
|
|
136
|
+
// key hash must be derived from exactly that UA
|
|
137
|
+
const profileUa = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36';
|
|
138
|
+
const apiKey = opts.key || freshTryItKey(opts.transport === 'impersonate' ? profileUa : USER_AGENT);
|
|
139
|
+
const curlArgs = [];
|
|
140
|
+
if (opts.transport === 'impersonate') curlArgs.push('--impersonate', 'chrome136');
|
|
141
|
+
curlArgs.push(API_URL, '-sS', '--compressed', '--max-time', '120', '-X', 'POST',
|
|
142
|
+
'-H', `api-key: ${apiKey}`);
|
|
143
|
+
if (opts.transport !== 'impersonate') curlArgs.push('-H', `User-Agent: ${USER_AGENT}`);
|
|
144
|
+
curlArgs.push(
|
|
145
|
+
'-H', `Origin: ${headers.Origin}`,
|
|
146
|
+
'-H', `Referer: ${headers.Referer}`,
|
|
147
|
+
'-H', 'Accept: */*',
|
|
148
|
+
'-H', `Cookie: deepai_device_id=${opts.deviceId}`,
|
|
149
|
+
'-w', '\n%{http_code}'
|
|
150
|
+
);
|
|
151
|
+
for (const [k, v] of form.entries()) curlArgs.push('-F', `${k}=${v}`);
|
|
152
|
+
res = await new Promise((resolve, reject) => {
|
|
153
|
+
execFile(binary, curlArgs, { timeout: 120000, maxBuffer: 32 * 1024 * 1024 }, (err, stdout) => {
|
|
154
|
+
if (err && stdout == null) return reject(err);
|
|
155
|
+
const body = String(stdout).replace(/\r/g, '');
|
|
156
|
+
const cut = body.lastIndexOf('\n');
|
|
157
|
+
const status = Number(body.slice(cut + 1).trim());
|
|
158
|
+
resolve({ status, ok: status < 300, text: async () => body.slice(0, cut) });
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
} else {
|
|
162
|
+
res = await fetch(API_URL, { method: 'POST', headers, body: form });
|
|
163
|
+
}
|
|
164
|
+
const raw = await res.text();
|
|
165
|
+
let data = null;
|
|
166
|
+
try { data = JSON.parse(raw); } catch { /* non-JSON */ }
|
|
167
|
+
|
|
168
|
+
if (!res.ok || data?.err || (data?.status && !data.share_url && !data.output_url)) {
|
|
169
|
+
console.error(`\nFAILED HTTP ${res.status}`);
|
|
170
|
+
console.error(raw.slice(0, 500));
|
|
171
|
+
const s = String(data?.status || data?.err || '');
|
|
172
|
+
if (/valid Api-Key/i.test(s)) console.error('\n→ The key is invalid or already used. Keys are single-use and must be hashed for the exact User-Agent sent; this script mints a fresh one each run.');
|
|
173
|
+
else if (/try this model on deepai\.org/i.test(s)) console.error('\n→ DeepAI is refusing anonymous generation from your IP (datacenter/VPN) or the Origin header is missing. Run from a residential IP and keep the Origin/Referer headers.');
|
|
174
|
+
else if (/Pro members/i.test(s)) console.error('\n→ Your registered key is on the free plan; /api/* needs Pro. Use the anonymous mode (omit --key) or upgrade.');
|
|
175
|
+
else if (/try it exceeded/i.test(s)) console.error('\n→ Free quota for this device/IP is exhausted. Try a different --device-id or wait for the reset.');
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const url = data.share_url || data.output_url;
|
|
180
|
+
console.log(`\nOK HTTP ${res.status}`);
|
|
181
|
+
console.log(`Image : ${url}`);
|
|
182
|
+
if (data.id) console.log(`ID : ${data.id}`);
|
|
183
|
+
|
|
184
|
+
// ---- optional: download the image --------------------------------------
|
|
185
|
+
const out = opts.out || `deepai-${Date.now()}.jpg`;
|
|
186
|
+
try {
|
|
187
|
+
const img = await fetch(url);
|
|
188
|
+
if (img.ok) {
|
|
189
|
+
const buf = Buffer.from(await img.arrayBuffer());
|
|
190
|
+
require('fs').writeFileSync(out, buf);
|
|
191
|
+
console.log(`Saved : ${out} (${(buf.length / 1024).toFixed(1)} KB)`);
|
|
192
|
+
} else {
|
|
193
|
+
console.log(`(download skipped: HTTP ${img.status} — open the URL above in a browser)`);
|
|
194
|
+
}
|
|
195
|
+
} catch (e) {
|
|
196
|
+
console.log(`(download failed: ${e.message} — open the URL above in a browser)`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
main().catch((e) => { console.error('ERROR:', e.message); process.exit(1); });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "alexa-ai",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "AI engine for the Alexa WhatsApp bot: DeepAI-powered chat with PostgreSQL-backed long-term memory, cross-chat identity (@lid <-> phone), vision/OCR, image generation and web search.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|