notoken-core 1.6.0 → 2.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/config/chat-responses.json +767 -0
- package/config/concept-clusters.json +31 -0
- package/config/entities.json +93 -0
- package/config/image-prompts.json +20 -0
- package/config/intent-vectors.json +1 -0
- package/config/intents.json +4946 -83
- package/config/ollama-models.json +193 -0
- package/config/rules.json +32 -1
- package/dist/automation/discordPatchright.d.ts +35 -0
- package/dist/automation/discordPatchright.js +424 -0
- package/dist/automation/discordSetup.d.ts +31 -0
- package/dist/automation/discordSetup.js +338 -0
- package/dist/conversation/coreference.js +44 -4
- package/dist/conversation/pendingActions.d.ts +55 -0
- package/dist/conversation/pendingActions.js +127 -0
- package/dist/conversation/store.d.ts +72 -0
- package/dist/conversation/store.js +140 -1
- package/dist/conversation/topicTracker.d.ts +36 -0
- package/dist/conversation/topicTracker.js +141 -0
- package/dist/execution/ssh.d.ts +42 -1
- package/dist/execution/ssh.js +532 -3
- package/dist/handlers/executor.js +3981 -16
- package/dist/index.d.ts +25 -3
- package/dist/index.js +36 -2
- package/dist/nlp/batchParser.d.ts +30 -0
- package/dist/nlp/batchParser.js +77 -0
- package/dist/nlp/conceptExpansion.d.ts +54 -0
- package/dist/nlp/conceptExpansion.js +136 -0
- package/dist/nlp/conceptRouter.d.ts +49 -0
- package/dist/nlp/conceptRouter.js +302 -0
- package/dist/nlp/confidenceCalibrator.d.ts +62 -0
- package/dist/nlp/confidenceCalibrator.js +116 -0
- package/dist/nlp/correctionLearner.d.ts +45 -0
- package/dist/nlp/correctionLearner.js +207 -0
- package/dist/nlp/entitySpellCorrect.d.ts +35 -0
- package/dist/nlp/entitySpellCorrect.js +141 -0
- package/dist/nlp/knowledgeGraph.d.ts +70 -0
- package/dist/nlp/knowledgeGraph.js +380 -0
- package/dist/nlp/llmFallback.js +28 -1
- package/dist/nlp/multiClassifier.js +91 -6
- package/dist/nlp/multiIntent.d.ts +43 -0
- package/dist/nlp/multiIntent.js +154 -0
- package/dist/nlp/parseIntent.d.ts +6 -1
- package/dist/nlp/parseIntent.js +180 -5
- package/dist/nlp/ruleParser.js +315 -0
- package/dist/nlp/semanticSimilarity.d.ts +30 -0
- package/dist/nlp/semanticSimilarity.js +174 -0
- package/dist/nlp/vocabularyBuilder.d.ts +43 -0
- package/dist/nlp/vocabularyBuilder.js +224 -0
- package/dist/nlp/wikidata.d.ts +49 -0
- package/dist/nlp/wikidata.js +228 -0
- package/dist/policy/confirm.d.ts +10 -0
- package/dist/policy/confirm.js +39 -0
- package/dist/policy/safety.js +6 -4
- package/dist/utils/aliases.d.ts +5 -0
- package/dist/utils/aliases.js +39 -0
- package/dist/utils/analysis.js +71 -15
- package/dist/utils/browser.d.ts +64 -0
- package/dist/utils/browser.js +364 -0
- package/dist/utils/commandHistory.d.ts +20 -0
- package/dist/utils/commandHistory.js +108 -0
- package/dist/utils/completer.d.ts +17 -0
- package/dist/utils/completer.js +79 -0
- package/dist/utils/config.js +32 -2
- package/dist/utils/dbQuery.d.ts +25 -0
- package/dist/utils/dbQuery.js +248 -0
- package/dist/utils/discordDiag.d.ts +35 -0
- package/dist/utils/discordDiag.js +826 -0
- package/dist/utils/diskCleanup.d.ts +36 -0
- package/dist/utils/diskCleanup.js +775 -0
- package/dist/utils/entityResolver.d.ts +107 -0
- package/dist/utils/entityResolver.js +468 -0
- package/dist/utils/imageGen.d.ts +92 -0
- package/dist/utils/imageGen.js +2031 -0
- package/dist/utils/installTracker.d.ts +57 -0
- package/dist/utils/installTracker.js +160 -0
- package/dist/utils/multiExec.d.ts +21 -0
- package/dist/utils/multiExec.js +141 -0
- package/dist/utils/openclawDiag.d.ts +29 -0
- package/dist/utils/openclawDiag.js +1035 -0
- package/dist/utils/output.js +4 -0
- package/dist/utils/platform.js +2 -1
- package/dist/utils/progressReporter.d.ts +50 -0
- package/dist/utils/progressReporter.js +58 -0
- package/dist/utils/projectDetect.d.ts +44 -0
- package/dist/utils/projectDetect.js +319 -0
- package/dist/utils/projectScanner.d.ts +44 -0
- package/dist/utils/projectScanner.js +312 -0
- package/dist/utils/shellCompat.d.ts +78 -0
- package/dist/utils/shellCompat.js +186 -0
- package/dist/utils/smartArchive.d.ts +16 -0
- package/dist/utils/smartArchive.js +172 -0
- package/dist/utils/smartRetry.d.ts +26 -0
- package/dist/utils/smartRetry.js +114 -0
- package/dist/utils/updater.d.ts +1 -0
- package/dist/utils/updater.js +1 -1
- package/dist/utils/version.d.ts +20 -0
- package/dist/utils/version.js +212 -0
- package/package.json +6 -3
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord Developer Portal automation via patchright.
|
|
3
|
+
*
|
|
4
|
+
* Runs on Windows Node via cmd.exe (patchright can't launch visible
|
|
5
|
+
* Windows browsers from WSL due to pipe FD limitation).
|
|
6
|
+
*
|
|
7
|
+
* Scripts are written to C:\temp and executed via Windows Node.
|
|
8
|
+
* Browser uses persistent profile at C:\temp\notoken-browser-profile
|
|
9
|
+
* to preserve Discord login sessions across runs.
|
|
10
|
+
*
|
|
11
|
+
* User handles: captcha, MFA (password). Script waits patiently.
|
|
12
|
+
* Token captured via Windows clipboard (Get-Clipboard).
|
|
13
|
+
*/
|
|
14
|
+
import { execSync } from "node:child_process";
|
|
15
|
+
import { writeFileSync, readFileSync, existsSync } from "node:fs";
|
|
16
|
+
const c = {
|
|
17
|
+
reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
|
|
18
|
+
green: "\x1b[32m", yellow: "\x1b[33m", red: "\x1b[31m", cyan: "\x1b[36m",
|
|
19
|
+
};
|
|
20
|
+
function tryExec(cmd, timeout = 15_000) {
|
|
21
|
+
try {
|
|
22
|
+
return execSync(cmd, { timeout, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
return e.stdout?.trim?.() ?? "";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const isNativeWin = process.platform === "win32";
|
|
29
|
+
const tempDir = isNativeWin ? "C:\\temp" : "/mnt/c/temp";
|
|
30
|
+
const tempDirPosix = isNativeWin ? "/c/temp" : "/mnt/c/temp";
|
|
31
|
+
const cmdPrefix = isNativeWin ? `cmd.exe /c "cd ${tempDir} &&` : `/mnt/c/Windows/System32/cmd.exe /c "cd ${tempDir} &&`;
|
|
32
|
+
function winExec(script, timeout = 300_000) {
|
|
33
|
+
// Write script to temp dir, run via Node
|
|
34
|
+
const scriptPath = isNativeWin ? `${tempDir}\\notoken-discord-script.js` : `${tempDir}/notoken-discord-script.js`;
|
|
35
|
+
try {
|
|
36
|
+
execSync(`mkdir ${isNativeWin ? tempDir : "-p " + tempDir}`, { stdio: "pipe" });
|
|
37
|
+
}
|
|
38
|
+
catch { }
|
|
39
|
+
writeFileSync(scriptPath, script);
|
|
40
|
+
return tryExec(`${cmdPrefix} node notoken-discord-script.js"`, timeout);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Ensure patchright is installed on Windows.
|
|
44
|
+
*/
|
|
45
|
+
export function ensurePatchright() {
|
|
46
|
+
try {
|
|
47
|
+
execSync(`mkdir ${isNativeWin ? tempDir : "-p " + tempDir}`, { stdio: "pipe" });
|
|
48
|
+
}
|
|
49
|
+
catch { }
|
|
50
|
+
const check = tryExec(`${cmdPrefix} node -e \\"require('patchright');\\"" 2>&1`);
|
|
51
|
+
if (check.includes("Cannot find")) {
|
|
52
|
+
console.log(` ${c.dim}Installing patchright...${c.reset}`);
|
|
53
|
+
tryExec(`${cmdPrefix} npm install patchright" 2>&1`, 60_000);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
/** Detect which browser channel is available */
|
|
59
|
+
function detectBrowserChannel() {
|
|
60
|
+
if (isNativeWin) {
|
|
61
|
+
if (existsSync("C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe") ||
|
|
62
|
+
existsSync("C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe"))
|
|
63
|
+
return "msedge";
|
|
64
|
+
if (existsSync("C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe") ||
|
|
65
|
+
existsSync("C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe"))
|
|
66
|
+
return "chrome";
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// WSL — check Windows browsers via /mnt/c
|
|
70
|
+
if (existsSync("/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe") ||
|
|
71
|
+
existsSync("/mnt/c/Program Files/Microsoft/Edge/Application/msedge.exe"))
|
|
72
|
+
return "msedge";
|
|
73
|
+
if (existsSync("/mnt/c/Program Files/Google/Chrome/Application/chrome.exe"))
|
|
74
|
+
return "chrome";
|
|
75
|
+
}
|
|
76
|
+
return "chromium"; // fallback to bundled
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Create a Discord bot application, get token, enable intents.
|
|
80
|
+
* Returns { token, appId, success }.
|
|
81
|
+
*/
|
|
82
|
+
export async function createDiscordBot(appName = "NoToken-Bot") {
|
|
83
|
+
ensurePatchright();
|
|
84
|
+
const uniqueName = `${appName}-${Date.now().toString().slice(-4)}`;
|
|
85
|
+
const browserChannel = detectBrowserChannel();
|
|
86
|
+
console.log(`\n${c.bold}${c.cyan}── Creating Discord Bot: ${uniqueName} ──${c.reset}`);
|
|
87
|
+
console.log(`${c.dim} Browser: ${browserChannel}${c.reset}\n`);
|
|
88
|
+
const script = `
|
|
89
|
+
const { chromium } = require('patchright');
|
|
90
|
+
const fs = require('fs');
|
|
91
|
+
const APP_NAME = '${uniqueName}';
|
|
92
|
+
const USER_DATA_DIR = 'C:\\\\temp\\\\notoken-browser-profile';
|
|
93
|
+
|
|
94
|
+
(async () => {
|
|
95
|
+
const ctx = await chromium.launchPersistentContext(USER_DATA_DIR, {
|
|
96
|
+
headless: false, channel: '${browserChannel}',
|
|
97
|
+
args: ['--disable-blink-features=AutomationControlled'],
|
|
98
|
+
viewport: { width: 1280, height: 900 },
|
|
99
|
+
});
|
|
100
|
+
const page = ctx.pages()[0] || await ctx.newPage();
|
|
101
|
+
|
|
102
|
+
// Navigate
|
|
103
|
+
await page.goto('https://discord.com/developers/applications', { timeout: 60000 });
|
|
104
|
+
await page.waitForLoadState('networkidle').catch(() => {});
|
|
105
|
+
await page.waitForTimeout(3000);
|
|
106
|
+
|
|
107
|
+
// Login handling
|
|
108
|
+
if (await page.locator('button:has-text("Log In")').first().isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
109
|
+
await page.locator('button:has-text("Log In")').first().click();
|
|
110
|
+
await page.waitForTimeout(2000);
|
|
111
|
+
}
|
|
112
|
+
if (page.url().includes('/login') || await page.locator('input[name="password"]').isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
113
|
+
console.log('LOGIN_REQUIRED');
|
|
114
|
+
await page.waitForURL('**/developers/applications**', { timeout: 600000 });
|
|
115
|
+
await page.waitForTimeout(3000);
|
|
116
|
+
}
|
|
117
|
+
console.log('LOGGED_IN');
|
|
118
|
+
|
|
119
|
+
// Dismiss survey
|
|
120
|
+
await page.locator('button[aria-label="Dismiss"]').click({ timeout: 1000 }).catch(() => {});
|
|
121
|
+
|
|
122
|
+
// Create app
|
|
123
|
+
await page.waitForSelector('button:has-text("New Application")', { timeout: 10000 });
|
|
124
|
+
await page.click('button:has-text("New Application")');
|
|
125
|
+
await page.waitForSelector('input#appname', { timeout: 5000 });
|
|
126
|
+
await page.locator('input#appname').click();
|
|
127
|
+
await page.locator('input#appname').fill(APP_NAME);
|
|
128
|
+
await page.locator('div[class*="checkboxIndicator"]').first().click();
|
|
129
|
+
await page.waitForTimeout(300);
|
|
130
|
+
await page.locator('button[class*="primary"]:has-text("Create")').first().click();
|
|
131
|
+
console.log('CREATE_CLICKED');
|
|
132
|
+
|
|
133
|
+
// Wait for captcha/redirect
|
|
134
|
+
await page.waitForURL(/applications\\/\\d+/, { timeout: 300000 });
|
|
135
|
+
await page.waitForLoadState('networkidle').catch(() => {});
|
|
136
|
+
const appId = (page.url().match(/applications\\/(\\d+)/) || [])[1] || '';
|
|
137
|
+
console.log('APP_CREATED:' + appId);
|
|
138
|
+
|
|
139
|
+
// Bot tab
|
|
140
|
+
await page.locator('a:has-text("Bot")').first().click();
|
|
141
|
+
await page.waitForSelector('button:has-text("Reset Token")', { timeout: 10000 });
|
|
142
|
+
|
|
143
|
+
// Intercept API responses to capture token from network
|
|
144
|
+
let tokenFromApi = '';
|
|
145
|
+
page.on('response', async (resp) => {
|
|
146
|
+
try {
|
|
147
|
+
if (resp.url().includes('/bot/reset') || resp.url().includes('/bot/token') || resp.url().includes('/applications/')) {
|
|
148
|
+
const body = await resp.text().catch(() => '');
|
|
149
|
+
const match = body.match(/"token"\\s*:\\s*"([^"]+)"/);
|
|
150
|
+
if (match && match[1].includes('.') && match[1].length > 50) {
|
|
151
|
+
tokenFromApi = match[1];
|
|
152
|
+
fs.writeFileSync('C:\\\\temp\\\\discord-token-api.txt', tokenFromApi);
|
|
153
|
+
console.log('TOKEN_FROM_API');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
} catch {}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Reset Token
|
|
160
|
+
await page.click('button:has-text("Reset Token")');
|
|
161
|
+
await page.waitForTimeout(1500);
|
|
162
|
+
await page.click('button:has-text("Yes, do it!")', { timeout: 5000 }).catch(() => {});
|
|
163
|
+
console.log('TOKEN_RESET');
|
|
164
|
+
|
|
165
|
+
// Wait for MFA/token — poll API intercept, DOM, clipboard
|
|
166
|
+
let token = '';
|
|
167
|
+
const log = [];
|
|
168
|
+
for (let i = 0; i < 150; i++) {
|
|
169
|
+
await page.waitForTimeout(2000);
|
|
170
|
+
|
|
171
|
+
// Check API intercept first (most reliable)
|
|
172
|
+
if (tokenFromApi) { token = tokenFromApi; log.push('SOURCE:api'); break; }
|
|
173
|
+
|
|
174
|
+
// Check all page content for token pattern
|
|
175
|
+
token = await page.evaluate(() => {
|
|
176
|
+
const found = [];
|
|
177
|
+
// Walk entire DOM text
|
|
178
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
|
|
179
|
+
while (walker.nextNode()) {
|
|
180
|
+
const text = walker.currentNode.textContent || '';
|
|
181
|
+
if (text.includes('.') && text.length > 50 && text.length < 200 && !text.includes(' ') && !text.includes('\\n')) {
|
|
182
|
+
found.push(text.trim());
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Check input values
|
|
186
|
+
for (const inp of document.querySelectorAll('input')) {
|
|
187
|
+
const val = inp.value || inp.getAttribute('value') || '';
|
|
188
|
+
if (val.includes('.') && val.length > 50 && val.length < 200 && !val.includes(' ')) {
|
|
189
|
+
found.push(val.trim());
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return found[0] || '';
|
|
193
|
+
}).catch(() => '');
|
|
194
|
+
if (token) { log.push('SOURCE:dom'); break; }
|
|
195
|
+
|
|
196
|
+
// Click Copy and check clipboard
|
|
197
|
+
if (i % 2 === 1) {
|
|
198
|
+
await page.locator('button:has-text("Copy")').first().click({ timeout: 500 }).catch(() => {});
|
|
199
|
+
await page.locator('[class*="copy"] button, [class*="Copy"] button, button[class*="copy"]').first().click({ timeout: 500 }).catch(() => {});
|
|
200
|
+
await page.waitForTimeout(500);
|
|
201
|
+
try {
|
|
202
|
+
const { execSync: es } = require('child_process');
|
|
203
|
+
const clip = es('powershell -Command "Get-Clipboard"', { encoding: 'utf-8', timeout: 3000 }).trim();
|
|
204
|
+
if (clip && clip.includes('.') && clip.length > 50 && clip.length < 200 && !clip.includes(' ')) {
|
|
205
|
+
token = clip;
|
|
206
|
+
log.push('SOURCE:clipboard');
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
} catch {}
|
|
210
|
+
}
|
|
211
|
+
if (i % 5 === 0) log.push('POLL:' + (i * 2) + 's');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Write debug log
|
|
215
|
+
fs.writeFileSync('C:\\\\temp\\\\discord-debug.log', log.join('\\n') + '\\nTOKEN_LEN:' + token.length + '\\nAPI_LEN:' + tokenFromApi.length);
|
|
216
|
+
|
|
217
|
+
if (!token && tokenFromApi) token = tokenFromApi;
|
|
218
|
+
if (!token) {
|
|
219
|
+
console.log('TOKEN_NOT_FOUND');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Enable intents
|
|
223
|
+
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
|
224
|
+
await page.waitForTimeout(1500);
|
|
225
|
+
await page.locator('text=Privileged Gateway Intents').scrollIntoViewIfNeeded().catch(() => {});
|
|
226
|
+
await page.waitForTimeout(1000);
|
|
227
|
+
const switches = await page.locator('label[data-react-aria-pressable="true"] input[role="switch"]').all();
|
|
228
|
+
let toggled = 0;
|
|
229
|
+
for (const sw of switches) {
|
|
230
|
+
if (!await sw.isChecked().catch(() => true)) {
|
|
231
|
+
await sw.locator('..').locator('..').first().click({ force: true }).catch(() => {});
|
|
232
|
+
await page.waitForTimeout(500);
|
|
233
|
+
toggled++;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
await page.click('button:has-text("Save Changes")', { timeout: 3000 }).catch(() => {});
|
|
237
|
+
console.log('INTENTS_ENABLED:' + toggled);
|
|
238
|
+
|
|
239
|
+
// Save result
|
|
240
|
+
const result = { token, appId, success: !!token };
|
|
241
|
+
fs.writeFileSync('C:\\\\temp\\\\discord-bot-result.json', JSON.stringify(result));
|
|
242
|
+
if (token) console.log('TOKEN:' + token);
|
|
243
|
+
console.log('DONE');
|
|
244
|
+
await ctx.close();
|
|
245
|
+
})().catch(e => console.error('FATAL:' + e.message));
|
|
246
|
+
`;
|
|
247
|
+
const output = winExec(script, 600_000);
|
|
248
|
+
// Parse output
|
|
249
|
+
const lines = output.split("\n");
|
|
250
|
+
let appId = "";
|
|
251
|
+
let token = "";
|
|
252
|
+
for (const line of lines) {
|
|
253
|
+
if (line.startsWith("APP_CREATED:"))
|
|
254
|
+
appId = line.replace("APP_CREATED:", "");
|
|
255
|
+
if (line.startsWith("TOKEN:"))
|
|
256
|
+
token = line.replace("TOKEN:", "");
|
|
257
|
+
if (line === "LOGIN_REQUIRED")
|
|
258
|
+
console.log(` ${c.yellow}Log in to Discord in the browser window...${c.reset}`);
|
|
259
|
+
if (line === "LOGGED_IN")
|
|
260
|
+
console.log(` ${c.green}✓${c.reset} Logged in`);
|
|
261
|
+
if (line === "CREATE_CLICKED")
|
|
262
|
+
console.log(` ${c.dim}Creating app — solve captcha if shown...${c.reset}`);
|
|
263
|
+
if (line.startsWith("APP_CREATED"))
|
|
264
|
+
console.log(` ${c.green}✓${c.reset} App created: ${appId}`);
|
|
265
|
+
if (line === "TOKEN_RESET")
|
|
266
|
+
console.log(` ${c.dim}Token reset — complete MFA if shown...${c.reset}`);
|
|
267
|
+
if (line.startsWith("POLLING"))
|
|
268
|
+
console.log(` ${c.dim}Waiting for token... ${line.replace("POLLING:", "")}${c.reset}`);
|
|
269
|
+
if (line.startsWith("INTENTS_ENABLED"))
|
|
270
|
+
console.log(` ${c.green}✓${c.reset} Intents enabled: ${line.replace("INTENTS_ENABLED:", "")}`);
|
|
271
|
+
if (line === "TRYING_CLIPBOARD")
|
|
272
|
+
console.log(` ${c.dim}Checking clipboard...${c.reset}`);
|
|
273
|
+
}
|
|
274
|
+
// If token not from DOM, try clipboard
|
|
275
|
+
if (!token) {
|
|
276
|
+
const psCmd = isNativeWin
|
|
277
|
+
? 'powershell -Command "Get-Clipboard" 2>/dev/null'
|
|
278
|
+
: '/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -Command "Get-Clipboard" 2>/dev/null';
|
|
279
|
+
token = tryExec(psCmd);
|
|
280
|
+
if (token && token.includes(".") && token.length > 50) {
|
|
281
|
+
console.log(` ${c.green}✓${c.reset} Token captured from clipboard`);
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
token = "";
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// Also try saved result
|
|
288
|
+
if (!token) {
|
|
289
|
+
try {
|
|
290
|
+
const saved = JSON.parse(readFileSync(`${tempDirPosix}/discord-bot-result.json`, "utf-8"));
|
|
291
|
+
if (saved.token)
|
|
292
|
+
token = saved.token;
|
|
293
|
+
if (saved.appId && !appId)
|
|
294
|
+
appId = saved.appId;
|
|
295
|
+
}
|
|
296
|
+
catch { }
|
|
297
|
+
}
|
|
298
|
+
if (token) {
|
|
299
|
+
console.log(` ${c.green}✓${c.reset} Token: ${token.substring(0, 25)}...`);
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
console.log(` ${c.red}✗${c.reset} Could not capture token`);
|
|
303
|
+
}
|
|
304
|
+
return { token, appId, success: !!token };
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Authorize (invite) a Discord bot to a server.
|
|
308
|
+
* Opens invite page, selects server, clicks authorize, waits for captcha.
|
|
309
|
+
*/
|
|
310
|
+
export async function authorizeDiscordBot(appId) {
|
|
311
|
+
ensurePatchright();
|
|
312
|
+
const browserChannel = detectBrowserChannel();
|
|
313
|
+
console.log(`\n ${c.dim}Opening invite page...${c.reset}`);
|
|
314
|
+
const script = `
|
|
315
|
+
const { chromium } = require('patchright');
|
|
316
|
+
(async () => {
|
|
317
|
+
const ctx = await chromium.launchPersistentContext('C:\\\\temp\\\\notoken-browser-profile', {
|
|
318
|
+
headless: false, channel: '${browserChannel}',
|
|
319
|
+
args: ['--disable-blink-features=AutomationControlled'],
|
|
320
|
+
viewport: { width: 1280, height: 900 },
|
|
321
|
+
});
|
|
322
|
+
const page = ctx.pages()[0] || await ctx.newPage();
|
|
323
|
+
|
|
324
|
+
await page.goto('https://discord.com/oauth2/authorize?client_id=${appId}&permissions=68608&scope=bot', { timeout: 30000 });
|
|
325
|
+
await page.waitForLoadState('networkidle').catch(() => {});
|
|
326
|
+
await page.waitForTimeout(5000);
|
|
327
|
+
|
|
328
|
+
// Select server
|
|
329
|
+
await page.locator('[role="combobox"]').click({ timeout: 5000 });
|
|
330
|
+
await page.waitForTimeout(2000);
|
|
331
|
+
const opt = page.locator('[role="option"]').first();
|
|
332
|
+
const name = await opt.textContent().catch(() => '');
|
|
333
|
+
console.log('SERVER:' + name);
|
|
334
|
+
await opt.click();
|
|
335
|
+
await page.waitForTimeout(1000);
|
|
336
|
+
|
|
337
|
+
// Continue
|
|
338
|
+
await page.click('button:has-text("Continue")', { timeout: 3000 }).catch(() => {});
|
|
339
|
+
await page.waitForTimeout(3000);
|
|
340
|
+
|
|
341
|
+
// Authorize
|
|
342
|
+
await page.click('button:has-text("Authorize")', { timeout: 5000 }).catch(() => {});
|
|
343
|
+
console.log('AUTHORIZE_CLICKED');
|
|
344
|
+
|
|
345
|
+
// Wait for captcha resolution — poll for redirect
|
|
346
|
+
for (let i = 0; i < 90; i++) {
|
|
347
|
+
await page.waitForTimeout(2000);
|
|
348
|
+
if (page.url().includes('authorized')) { console.log('AUTHORIZED'); break; }
|
|
349
|
+
if (i % 15 === 0 && i > 0) console.log('WAITING_CAPTCHA:' + (i * 2) + 's');
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
await ctx.close();
|
|
353
|
+
})().catch(e => console.error('FATAL:' + e.message));
|
|
354
|
+
`;
|
|
355
|
+
const output = winExec(script, 300_000);
|
|
356
|
+
for (const line of output.split("\n")) {
|
|
357
|
+
if (line.startsWith("SERVER:"))
|
|
358
|
+
console.log(` ${c.green}✓${c.reset} Server: ${line.replace("SERVER:", "")}`);
|
|
359
|
+
if (line === "AUTHORIZE_CLICKED")
|
|
360
|
+
console.log(` ${c.dim}Authorize clicked — solve captcha if shown...${c.reset}`);
|
|
361
|
+
if (line.startsWith("WAITING_CAPTCHA"))
|
|
362
|
+
console.log(` ${c.dim}Waiting... ${line.replace("WAITING_CAPTCHA:", "")}${c.reset}`);
|
|
363
|
+
if (line === "AUTHORIZED")
|
|
364
|
+
console.log(` ${c.green}✓${c.reset} Bot authorized!`);
|
|
365
|
+
}
|
|
366
|
+
return output.includes("AUTHORIZED");
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Enable all Privileged Gateway Intents on the Discord Developer Portal.
|
|
370
|
+
*/
|
|
371
|
+
export async function enableDiscordIntents(appId) {
|
|
372
|
+
ensurePatchright();
|
|
373
|
+
const browserChannel = detectBrowserChannel();
|
|
374
|
+
console.log(` ${c.dim}Enabling intents on Developer Portal...${c.reset}`);
|
|
375
|
+
const script = `
|
|
376
|
+
const { chromium } = require('patchright');
|
|
377
|
+
(async () => {
|
|
378
|
+
const ctx = await chromium.launchPersistentContext('C:\\\\temp\\\\notoken-browser-profile', {
|
|
379
|
+
headless: false, channel: '${browserChannel}',
|
|
380
|
+
args: ['--disable-blink-features=AutomationControlled'],
|
|
381
|
+
viewport: { width: 1280, height: 900 },
|
|
382
|
+
});
|
|
383
|
+
const page = ctx.pages()[0] || await ctx.newPage();
|
|
384
|
+
|
|
385
|
+
await page.goto('https://discord.com/developers/applications/${appId}/bot', { timeout: 60000 });
|
|
386
|
+
await page.waitForLoadState('networkidle').catch(() => {});
|
|
387
|
+
await page.waitForTimeout(5000);
|
|
388
|
+
|
|
389
|
+
// Login if needed
|
|
390
|
+
if (await page.locator('button:has-text("Log In")').first().isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
391
|
+
await page.locator('button:has-text("Log In")').first().click();
|
|
392
|
+
console.log('LOGIN_REQUIRED');
|
|
393
|
+
await page.waitForURL('**/${appId}/bot**', { timeout: 600000 });
|
|
394
|
+
await page.waitForTimeout(3000);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Scroll to intents
|
|
398
|
+
await page.locator('text=Privileged Gateway Intents').scrollIntoViewIfNeeded().catch(() => {});
|
|
399
|
+
await page.waitForTimeout(2000);
|
|
400
|
+
|
|
401
|
+
// Enable unchecked switches
|
|
402
|
+
const switches = await page.locator('label[data-react-aria-pressable="true"] input[role="switch"]').all();
|
|
403
|
+
let toggled = 0;
|
|
404
|
+
for (const sw of switches) {
|
|
405
|
+
if (!await sw.isChecked().catch(() => true)) {
|
|
406
|
+
await sw.locator('..').locator('..').first().click({ force: true }).catch(() => {});
|
|
407
|
+
await page.waitForTimeout(500);
|
|
408
|
+
toggled++;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
await page.click('button:has-text("Save Changes")', { timeout: 3000 }).catch(() => {});
|
|
413
|
+
await page.waitForTimeout(2000);
|
|
414
|
+
console.log('ENABLED:' + toggled);
|
|
415
|
+
await ctx.close();
|
|
416
|
+
})().catch(e => console.error('FATAL:' + e.message));
|
|
417
|
+
`;
|
|
418
|
+
const output = winExec(script, 120_000);
|
|
419
|
+
const enabled = output.includes("ENABLED:");
|
|
420
|
+
const count = output.match(/ENABLED:(\d+)/)?.[1] ?? "0";
|
|
421
|
+
if (enabled)
|
|
422
|
+
console.log(` ${c.green}✓${c.reset} Enabled ${count} intent(s)`);
|
|
423
|
+
return enabled;
|
|
424
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Automated Discord bot setup via Playwright.
|
|
3
|
+
*
|
|
4
|
+
* Opens the Discord Developer Portal, walks the user through login,
|
|
5
|
+
* then automates: create application, create bot, copy token, enable
|
|
6
|
+
* Message Content Intent, generate OAuth2 invite URL, open it.
|
|
7
|
+
*
|
|
8
|
+
* The user only needs to:
|
|
9
|
+
* 1. Log in to Discord (notoken never touches credentials)
|
|
10
|
+
* 2. Pick which server to add the bot to
|
|
11
|
+
*
|
|
12
|
+
* Returns the bot token for OpenClaw channel registration.
|
|
13
|
+
*/
|
|
14
|
+
export interface DiscordSetupResult {
|
|
15
|
+
success: boolean;
|
|
16
|
+
botToken?: string;
|
|
17
|
+
applicationId?: string;
|
|
18
|
+
inviteUrl?: string;
|
|
19
|
+
error?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Run the automated Discord bot setup.
|
|
23
|
+
*
|
|
24
|
+
* @param appName - Name for the Discord application (default: "OpenClaw")
|
|
25
|
+
* @param headless - Run headless (default: false — user needs to see login)
|
|
26
|
+
*/
|
|
27
|
+
export declare function automateDiscordBotSetup(appName?: string, headless?: boolean): Promise<DiscordSetupResult>;
|
|
28
|
+
/**
|
|
29
|
+
* Full Discord setup flow — automate browser + register with OpenClaw.
|
|
30
|
+
*/
|
|
31
|
+
export declare function setupDiscordChannel(appName?: string): Promise<string>;
|