This commit is contained in:
DraxDev 2026-01-30 14:09:07 +03:00 committed by GitHub
commit 48008a1e73
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 76 additions and 1 deletions

View File

@ -40,6 +40,27 @@ openclaw channels login --channel whatsapp
openclaw channels logout --channel whatsapp openclaw channels logout --channel whatsapp
``` ```
### JSON mode (programmatic)
For programmatic use (e.g., web dashboards, hosting platforms), use `--json` to get QR data as JSON instead of terminal output:
```bash
moltbot channels login --channel whatsapp --json --timeout 60000
```
This outputs two JSON objects:
1. Initial response with QR data:
```json
{"status":"pending","qrDataUrl":"data:image/png;base64,...","message":"Scan this QR...","accountId":"default","channel":"whatsapp"}
```
2. Final response after scan (or timeout):
```json
{"status":"connected","connected":true,"message":"✅ Linked!","accountId":"default","channel":"whatsapp"}
```
The `qrDataUrl` is a base64-encoded PNG that can be displayed directly in an `<img>` tag.
## Troubleshooting ## Troubleshooting
- Run `openclaw status --deep` for a broad probe. - Run `openclaw status --deep` for a broad probe.

View File

@ -9,6 +9,8 @@ type ChannelAuthOptions = {
channel?: string; channel?: string;
account?: string; account?: string;
verbose?: boolean; verbose?: boolean;
json?: boolean;
timeoutMs?: number;
}; };
export async function runChannelLogin( export async function runChannelLogin(
@ -21,6 +23,54 @@ export async function runChannelLogin(
throw new Error(`Unsupported channel: ${channelInput}`); throw new Error(`Unsupported channel: ${channelInput}`);
} }
const plugin = getChannelPlugin(channelId); const plugin = getChannelPlugin(channelId);
// JSON mode: use gateway QR login methods for programmatic access
if (opts.json) {
if (!plugin?.gateway?.loginWithQrStart || !plugin?.gateway?.loginWithQrWait) {
throw new Error(`Channel ${channelId} does not support JSON login mode`);
}
setVerbose(Boolean(opts.verbose));
const cfg = loadConfig();
const accountId = opts.account?.trim() || resolveChannelDefaultAccountId({ plugin, cfg });
const timeoutMs = opts.timeoutMs ?? 60000;
// Start QR login and get QR data
const startResult = await plugin.gateway.loginWithQrStart({
accountId,
force: false,
timeoutMs,
verbose: Boolean(opts.verbose),
});
// Output QR data as JSON
const output = {
status: "pending",
qrDataUrl: startResult.qrDataUrl ?? null,
message: startResult.message,
accountId,
channel: channelId,
};
runtime.log(JSON.stringify(output));
// Wait for connection
const waitResult = await plugin.gateway.loginWithQrWait({
accountId,
timeoutMs,
});
// Output final result
const finalOutput = {
status: waitResult.connected ? "connected" : "failed",
connected: waitResult.connected,
message: waitResult.message,
accountId,
channel: channelId,
};
runtime.log(JSON.stringify(finalOutput));
return;
}
// Standard interactive mode
if (!plugin?.auth?.login) { if (!plugin?.auth?.login) {
throw new Error(`Channel ${channelId} does not support login`); throw new Error(`Channel ${channelId} does not support login`);
} }

View File

@ -215,6 +215,8 @@ export function registerChannelsCli(program: Command) {
.option("--channel <channel>", "Channel alias (default: whatsapp)") .option("--channel <channel>", "Channel alias (default: whatsapp)")
.option("--account <id>", "Account id (accountId)") .option("--account <id>", "Account id (accountId)")
.option("--verbose", "Verbose connection logs", false) .option("--verbose", "Verbose connection logs", false)
.option("--json", "Output QR data as JSON (for programmatic use)", false)
.option("--timeout <ms>", "Timeout for QR generation in ms", "60000")
.action(async (opts) => { .action(async (opts) => {
await runChannelsCommandWithDanger(async () => { await runChannelsCommandWithDanger(async () => {
await runChannelLogin( await runChannelLogin(
@ -222,6 +224,8 @@ export function registerChannelsCli(program: Command) {
channel: opts.channel as string | undefined, channel: opts.channel as string | undefined,
account: opts.account as string | undefined, account: opts.account as string | undefined,
verbose: Boolean(opts.verbose), verbose: Boolean(opts.verbose),
json: Boolean(opts.json),
timeoutMs: parseInt(String(opts.timeout), 10) || 60000,
}, },
defaultRuntime, defaultRuntime,
); );

View File

@ -208,7 +208,7 @@ describe("cli program (smoke)", () => {
from: "user", from: "user",
}); });
expect(runChannelLogin).toHaveBeenCalledWith( expect(runChannelLogin).toHaveBeenCalledWith(
{ channel: undefined, account: "work", verbose: false }, { channel: undefined, account: "work", verbose: false, json: false, timeoutMs: 60000 },
runtime, runtime,
); );
}); });