### Quick start: create bot, login, handle messages, and run Source: https://github.com/epiral/weixin-bot/blob/main/python/README.md Shows the minimal setup to create a WeixinBot, log in, register a message handler, and start the long-poll loop. ```python from weixin_bot import WeixinBot bot = WeixinBot() bot.login() @bot.on_message async def handle(msg): print(f"{msg.user_id}: {msg.text}") await bot.send_typing(msg.user_id) await bot.reply(msg, f"你说了: {msg.text}") bot.run() ``` -------------------------------- ### Install weixin-bot-sdk with pip Source: https://github.com/epiral/weixin-bot/blob/main/python/README.md Install the SDK from the local python directory using pip. ```bash cd python pip install . ``` -------------------------------- ### Install @pinixai/weixin-bot Source: https://github.com/epiral/weixin-bot/blob/main/nodejs/README.md Install the SDK as a dependency in your Node.js project. ```bash npm install @pinixai/weixin-bot ``` -------------------------------- ### WeixinBot instantiation example Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/nodejs-weixinbot.md Example of creating a WeixinBot instance with custom baseUrl, tokenPath, and onError callback. ```typescript import { WeixinBot } from '@pinixai/weixin-bot' const bot = new WeixinBot({ baseUrl: 'https://ilinkai.weixin.qq.com', tokenPath: '/etc/weixin-bot/credentials.json', onError: (err) => console.error('[bot error]', err), }) ``` -------------------------------- ### Run Node.js Echo Bot with npm install and tsx Source: https://github.com/epiral/weixin-bot/blob/main/examples/nodejs/README.md Setup and initial run of the echo bot. After running, a QR code is displayed for WeChat login. Credentials are saved to `~/.weixin-bot/credentials.json` so subsequent runs skip login. ```bash cd examples/nodejs npm install npx tsx echo-bot.ts ``` -------------------------------- ### bot.run() Source: https://github.com/epiral/weixin-bot/blob/main/python/README.md Starts the bot's main event loop. ```APIDOC ## bot.run() ### Description Starts the long-poll loop, dispatches incoming messages to registered handlers, reconnects on transient failures, and forces a fresh QR login if the session expires. ``` -------------------------------- ### Login example Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/nodejs-weixinbot.md Logs in using cached credentials if available and prints the logged-in user and bot account IDs. ```typescript const credentials = await bot.login() console.log(`Logged in as ${credentials.userId} (bot account ${credentials.accountId})`) ``` -------------------------------- ### Quick start with WeixinBot Source: https://github.com/epiral/weixin-bot/blob/main/nodejs/README.md Bootstraps the bot with login, a message handler that echoes the user's text, and the long-poll loop. ```typescript import { WeixinBot } from '@pinixai/weixin-bot' const bot = new WeixinBot() await bot.login() bot.onMessage(async (msg) => { console.log(`[${msg.timestamp.toLocaleTimeString()}] ${msg.userId}: ${msg.text}`) await bot.reply(msg, `你说了: ${msg.text}`) }) await bot.run() ``` -------------------------------- ### Usage example: basic login Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/python-auth.md Imports login and Credentials, calls login() to reuse credentials.json if valid, and prints token and base_url. ```python import asyncio from weixin_bot.auth import login, Credentials creds: Credentials = await login() # reuses credentials.json if valid print(creds.token, creds.base_url) ``` -------------------------------- ### await bot.login(options?) Source: https://github.com/epiral/weixin-bot/blob/main/nodejs/README.md Starts QR login if needed, stores credentials locally, and returns the active session. Can force a fresh QR login by ignoring cached credentials. ```APIDOC ## await bot.login(options?) ### Description Starts QR login if needed, stores credentials locally, and returns the active session. ### Parameters - `force?: boolean` - Optional - Ignore cached credentials and require a fresh QR login. ### Returns Active session. ``` -------------------------------- ### run loop and graceful stop Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/nodejs-weixinbot.md Shows the basic run loop setup and a graceful stop pattern using SIGINT. ```typescript await bot.login() bot.onMessage(async (msg) => await bot.reply(msg, `Echo: ${msg.text}`)) await bot.run() ``` ```typescript const runPromise = bot.run() // start the loop process.on('SIGINT', () => bot.stop()) // graceful stop await runPromise // resolves after stop() aborts the poll ``` -------------------------------- ### Instantiate WeixinBot with custom token path and error handler Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/python-weixinbot.md Example of creating a WeixinBot instance with a custom token path and an error callback that prints the error. ```python from weixin_bot import WeixinBot bot = WeixinBot( token_path="/etc/weixin-bot/credentials.json", on_error=lambda err: print("bot error:", err), ) ``` -------------------------------- ### sendTyping Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/nodejs-api.md Sends a typing indicator (start or stop) to the user. ```APIDOC ## sendTyping ### Description Sends a typing indicator (start or stop) to the user. ### Method POST ### Endpoint `/ilink/bot/sendtyping` ### Parameters #### Request Body - **ilink_user_id** (string) - Required - The user ID. - **typing_ticket** (string) - Required - The typing ticket. - **status** (number) - Required - Typing status: 1 for start, 2 for stop. - **base_info** (object) - Required - Base information. ### Request Example ```json { "ilink_user_id": "user123", "typing_ticket": "ticket123", "status": 1, "base_info": {} } ``` ### Response #### Success Response (200) - **ret** (number) - Return code. #### Response Example ```json { "ret": 0 } ``` ``` -------------------------------- ### onMessage echo example Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/nodejs-weixinbot.md Registers an async message handler that logs the message and echoes the text back. ```typescript bot.onMessage(async (msg) => { console.log(`[${msg.timestamp.toISOString()}] ${msg.userId}: ${msg.text}`) await bot.reply(msg, `Echo: ${msg.text}`) }) ``` -------------------------------- ### on message event example Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/nodejs-weixinbot.md Registers a message handler using the on method and replies with the message type. ```typescript bot.on('message', async (msg) => { await bot.reply(msg, `Got ${msg.type} message`) }) ``` -------------------------------- ### Usage example: forced login with error handling Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/python-auth.md Forces a new login with a custom base URL, catches RuntimeError for missing token and other exceptions, and clears credentials in finally. ```python from weixin_bot.auth import login, clear_credentials, ApiError # ApiError from .api try: creds = await login(base_url="https://ilinkai.weixin.qq.com", force=True) except RuntimeError as exc: print("QR confirmed but token missing:", exc) except Exception as exc: print("login failed:", exc) finally: await clear_credentials() ``` -------------------------------- ### Login with default credentials Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/nodejs-auth.md Imports and calls the login function, which reuses the default credential file at ~/.weixin-bot/credentials.json if it exists. The example shows the minimal usage of the login function. ```typescript import { login } from '../src/auth.js' // internal module const creds = await login() // reuses ~/.weixin-bot/credentials.json if present ``` -------------------------------- ### POST /ilink/bot/sendtyping Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/endpoints.md Starts or cancels the "对方正在输入中" typing indicator. This is the only way to trigger the indicator (`message_state: GENERATING` does not). ```APIDOC ## POST /ilink/bot/sendtyping ### Description Starts or cancels the "对方正在输入中" typing indicator. This is the only way to trigger the indicator (`message_state: GENERATING` does not). ### Method POST ### Endpoint /ilink/bot/sendtyping ### Parameters #### Request Body - **ilink_user_id** (string) - Required - WeChat user id. - **typing_ticket** (string) - Required - Ticket from `getconfig` (`typing_ticket`). - **status** (1 | 2) - Required - `1` = start/keep typing, `2` = cancel. - **base_info** (BaseInfo) - Required - `{ channel_version: "1.0.0" }` ### Response #### Success Response (200) - Response body: `Record` / `dict[str, Any]` (e.g. `{ "ret": 0 }`); `ret ≠ 0` → `ApiError`. ### Response Example { "ret": 0 } ``` -------------------------------- ### Force login example Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/nodejs-weixinbot.md Forces a fresh QR scan by passing force: true. Catches and logs errors, including ApiError when the QR-status API errors or Error when the API confirms the scan but omits required fields. ```typescript // Force a fresh QR scan even though ~/.weixin-bot/credentials.json exists try { await bot.login({ force: true }) } catch (err) { console.error('Login failed', err) // e.g. ApiError when the QR-status API errors, or Error when the API // confirms the scan but omits bot_token/ilink_bot_id/ilink_user_id } ``` -------------------------------- ### reply with typing indicator example Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/nodejs-weixinbot.md Sends a typing indicator, waits 1.5 seconds, then replies with 'Done thinking.' and catches any errors. ```typescript bot.onMessage(async (msg) => { try { await bot.sendTyping(msg.userId) await new Promise((r) => setTimeout(r, 1500)) await bot.reply(msg, 'Done thinking.') } catch (err) { console.error('reply failed', err) } }) ``` -------------------------------- ### run Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/python-weixinbot.md Synchronous blocking entry point for the WeixinBot client. It starts the long-poll loop, handles message dispatch, and manages retries and session expiration. ```APIDOC ## `run` ### Description Synchronous blocking entry point that starts the long-poll loop. It resolves credentials, polls for updates, dispatches incoming messages to handlers, and handles retries and session expiration. ### Method `run` ### Signature ```python def run(self) -> None: ``` ### Behavior - Calls `asyncio.run(self._run_loop())`. - Resolves credentials via `load_credentials` and `login`. - Each iteration creates an `asyncio.Task` for `get_updates` (40 s timeout inside `api._api_fetch`), advances `self._cursor` from `get_updates_buf`. - Converts USER messages to `IncomingMessage` and dispatches to handlers via `asyncio.gather(return_exceptions=True)`. - Catches `asyncio.CancelledError`/`TimeoutError` as abort conditions when stopped. - On `ApiError` with `code == -14` (session expired), clears credentials/cursor/context tokens, deletes credential file, and calls `self._login(force=True)`. - Other exceptions are reported via `_report_error`. - Retry backoff: 1 s doubling to 10 s cap. ### Notes - `stop()` must be called from another thread (it uses `loop.call_soon_threadsafe`). - `KeyboardInterrupt` in the same thread surfaces from `asyncio.run`; the example wraps it with `bot.stop()`. ### Example ```python bot = WeixinBot() bot.login() @bot.on_message async def handle(msg): await bot.reply(msg, "Echo: " + msg.text) bot.run() ``` ```python import threading bot.run() # main thread blocks threading.Thread(target=lambda: (time.sleep(5), bot.stop())).start() # run() returns after stop() ``` ``` -------------------------------- ### fetch_qr_code Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/python-api.md Fetches a QR code for bot login by sending a GET request to the /ilink/bot/get_bot_qrcode endpoint. Returns a QrCodeResponse containing the QR code string and image content. ```APIDOC ## GET /ilink/bot/get_bot_qrcode ### Description Fetches a QR code for bot login by sending a GET request to the /ilink/bot/get_bot_qrcode endpoint. Returns a QrCodeResponse containing the QR code string and image content. ### Method GET ### Endpoint /ilink/bot/get_bot_qrcode?bot_type=3 ### Parameters #### Query Parameters - **bot_type** (integer) - Required - Set to 3. ### Request Example No request body required. ### Response #### Success Response (200) - **qrcode** (string) - The QR code string. - **qrcode_img_content** (string) - The QR code image content. #### Response Example { "qrcode": "string", "qrcode_img_content": "string" } ``` -------------------------------- ### Force login and clear credentials Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/nodejs-auth.md Imports login and clearCredentials, forces a new login with a custom token path, logs the token, and clears the credentials in a finally block. The example shows error handling for login rejection. ```typescript import { login, clearCredentials } from '../src/auth.js' try { const creds = await login({ tokenPath: '/tmp/weixin.json', force: true }) console.log(creds.token) } catch (err) { console.error('login rejected', err) } finally { await clearCredentials('/tmp/weixin.json') } ``` -------------------------------- ### await bot.run() Source: https://github.com/epiral/weixin-bot/blob/main/nodejs/README.md Starts the long-poll loop, dispatches incoming messages to registered handlers, reconnects on transient failures, and triggers re-login if the session expires. ```APIDOC ## await bot.run() ### Description Starts the long-poll loop, dispatches incoming messages to registered handlers, reconnects on transient failures, and triggers re-login if the session expires. ``` -------------------------------- ### apiGet Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/nodejs-api.md Generic GET helper used by other API functions. Sends a GET request to the specified path and returns the parsed response. Rejects with ApiError on non-2xx status or if the response's 'ret' field is not 0. ```APIDOC ## `apiGet` ### Description Generic GET helper used by `fetchQrCode` and `pollQrStatus`. Sends a GET request to the specified path and returns the parsed response. Rejects with `ApiError` on non-2xx status or if the response's `ret` field is not 0. ### Signature ```typescript export async function apiGet( baseUrl: string, path: string, headers: Record = {}, ): Promise ``` ### Parameters - **baseUrl** (string) - Required - API base URL. - **path** (string) - Required - Path to append to the base URL. - **headers** (Record) - Optional - Additional headers to include in the request. ### Returns - **Promise** - Parsed response body. ### Errors - **ApiError** - Thrown on non-2xx HTTP status or when the response body's `ret` field is not 0. ``` -------------------------------- ### Full configuration in use (Python) Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/configuration.md Configures a WeixinBot instance with base_url, token_path, and on_error callback, then calls login with force=False. ```python from weixin_bot import WeixinBot bot = WeixinBot( base_url="https://ilinkai.weixin.qq.com", token_path="./secrets/weixin-credentials.json", on_error=lambda e: print("[bot]", e), ) bot.login(force=False) ``` -------------------------------- ### reply echo example Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/nodejs-weixinbot.md Echoes the received message text using the reply method. ```typescript bot.onMessage(async (msg) => { await bot.reply(msg, `Echo: ${msg.text}`) }) ``` -------------------------------- ### WeixinBot.__init__ Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/python-weixinbot.md Constructor for the WeixinBot client. Initializes the client with optional base URL, token path, and error callback. No I/O is performed during construction. ```APIDOC ## Constructor ### Description Initializes a new WeixinBot client instance. No I/O happens in the constructor. ### Method Constructor ### Endpoint `WeixinBot.__init__` ### Parameters - **base_url** (`str | None`) - Optional - iLink API base URL; defaults to `DEFAULT_BASE_URL` (`https://ilinkai.weixin.qq.com`). Overwritten by `credentials.base_url` after login. - **token_path** (`str | None`) - Optional - Credential file path; defaults to `~/.weixin-bot/credentials.json`. - **on_error** (`Callable[[object], None] | None`) - Optional - Callback for polling/handler errors (always logged to stderr first). ### Request Example ```python from weixin_bot import WeixinBot bot = WeixinBot( token_path="/etc/weixin-bot/credentials.json", on_error=lambda err: print("bot error:", err), ) ``` ### Response No return value (constructor). ``` -------------------------------- ### sendTyping API function Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/nodejs-api.md Sends a typing status update (start or stop) to the /ilink/bot/sendtyping endpoint. ```typescript export async function sendTyping( baseUrl: string, token: string, userId: string, ticket: string, status: SendTypingReq['status'], ): Promise> ``` -------------------------------- ### new WeixinBot(options?) Source: https://github.com/epiral/weixin-bot/blob/main/nodejs/README.md Creates a bot client instance. Accepts optional configuration options to override the API base URL, credential file path, and error handler. ```APIDOC ## new WeixinBot(options?) ### Description Creates a bot client instance. ### Parameters - `baseUrl?: string` - Optional - Override the iLink API base URL. - `tokenPath?: string` - Optional - Override the credential file path. Default: `~/.weixin-bot/credentials.json` - `onError?: (error: unknown) => void` - Optional - Receive polling or handler errors. ### Returns A new WeixinBot instance. ``` -------------------------------- ### run Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/nodejs-weixinbot.md Starts the long-poll loop and resolves when the loop stops. Handles retries and error reporting internally. ```APIDOC ## `run` ### Description Starts the long-poll loop and resolves only when the loop stops (via `stop()` or an unrecoverable abort). Calling `run()` again while running returns the existing `runPromise` (loop is not duplicated). ### Method `async run(): Promise` ### Examples ```typescript await bot.login() bot.onMessage(async (msg) => await bot.reply(msg, `Echo: ${msg.text}`)) await bot.run() ``` ```typescript const runPromise = bot.run() // start the loop process.on('SIGINT', () => bot.stop()) // graceful stop await runPromise // resolves after stop() aborts the poll ``` ``` -------------------------------- ### Get Config for Typing Ticket Source: https://github.com/epiral/weixin-bot/blob/main/PROTOCOL.md Retrieves a typing ticket for the sendtyping endpoint. Requires ilink_user_id and context_token. ```http POST /ilink/bot/getconfig { ilink_user_id, context_token, base_info } → { typing_ticket: "" } ``` -------------------------------- ### bot.login(force=False) Source: https://github.com/epiral/weixin-bot/blob/main/python/README.md Initiates the login process for the bot. ```APIDOC ## bot.login(force=False) ### Description Starts QR login if needed, stores credentials locally, and returns the active session. ### Parameters - **force** (boolean) - Optional - If true, forces a fresh QR login. ### Returns The active session. ``` -------------------------------- ### Full configuration in use (TypeScript) Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/configuration.md Configures a WeixinBot instance with optional baseUrl, tokenPath, and onError observer, then calls login with a force option that reads an environment variable in user code. ```typescript import { WeixinBot } from '@pinixai/weixin-bot' const bot = new WeixinBot({ baseUrl: 'https://ilinkai.weixin.qq.com', // optional override tokenPath: './secrets/weixin-credentials.json', // optional override onError: (err) => console.error('[bot]', err), // optional observer }) await bot.login({ force: process.env.CI === 'true' }) // force is an option here, but env is read by your code ``` -------------------------------- ### GET /download Source: https://github.com/epiral/weixin-bot/blob/main/PROTOCOL.md Downloads media from the CDN. The response body is AES-128-ECB encrypted and must be decrypted with the aes_key. ```APIDOC ## GET /download ### Description Downloads media from the CDN. The response body is AES-128-ECB encrypted and must be decrypted with the aes_key. ### Method GET ### Endpoint /download ### Parameters #### Query Parameters - **encrypted_query_param** (string) - Required - The download parameter obtained from the upload response header. ### Response #### Success Response (200) - The body is the AES-128-ECB encrypted bytes. Decrypt with the aes_key. ``` -------------------------------- ### LoginOptions interface Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/nodejs-auth.md Defines optional configuration for login, including baseUrl, tokenPath, and force. Defaults: baseUrl uses DEFAULT_BASE_URL, tokenPath uses ~/.weixin-bot/credentials.json, force is false. ```typescript export interface LoginOptions { baseUrl?: string tokenPath?: string force?: boolean } ``` -------------------------------- ### login method signature Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/python-weixinbot.md Synchronous wrapper around asyncio.run(self._login(force=force)); must not be called from inside a running event loop. Returns a Credentials dataclass (token, base_url, account_id, user_id); base_url is updated from the response and the cursor/context-token cache is reset when the token changes. Throws RuntimeError("QR login confirmed, but the API did not return bot credentials") on missing fields and ApiError on network/API failures. ```python def login(self, force: bool = False) -> Credentials: ``` -------------------------------- ### fetchQrCode API function Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/nodejs-api.md Fetches the bot QR code via a GET request to /ilink/bot/get_bot_qrcode without authentication headers. ```typescript export async function fetchQrCode(baseUrl: string): Promise ``` -------------------------------- ### GET /ilink/bot/get_bot_qrcode Source: https://github.com/epiral/weixin-bot/blob/main/PROTOCOL.md Retrieves a QR code for bot login. The response contains a token and a URL to the QR code image. ```APIDOC ## GET /ilink/bot/get_bot_qrcode ### Description Retrieves a QR code for bot login. The response contains a token and a URL to the QR code image. ### Method GET ### Endpoint /ilink/bot/get_bot_qrcode ### Parameters #### Query Parameters - **bot_type** (integer) - Required - Must be set to 3. ### Response #### Success Response (200) - **qrcode** (string) - The token used for polling status. - **qrcode_img_content** (string) - URL to the QR code image. #### Response Example { "qrcode": "", "qrcode_img_content": "" } ``` -------------------------------- ### Force login with error handling Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/python-weixinbot.md Forces a fresh QR scan by ignoring cached credentials. Catches and prints any exception raised during login. ```python try: bot.login(force=True) except Exception as exc: print(f"login failed: {exc}") ``` -------------------------------- ### Module-level constants Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/python-auth.md Default token directory and path, and QR polling interval in milliseconds. ```python DEFAULT_TOKEN_DIR = Path.home() / ".weixin-bot" DEFAULT_TOKEN_PATH = DEFAULT_TOKEN_DIR / "credentials.json" QR_POLL_INTERVAL_MS = 2_000 ``` -------------------------------- ### pollQrStatus API function Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/nodejs-api.md Polls the QR code status by sending a GET request to /ilink/bot/get_qrcode_status with the required client version header. ```typescript export async function pollQrStatus(baseUrl: string, qrcode: string): Promise ``` -------------------------------- ### login Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/python-auth.md Performs QR code login. Reuses cached credentials unless force is True. Otherwise runs the QR loop: fetches QR code, polls status every 2 seconds, and on confirmation persists credentials and returns them. ```APIDOC ## login ### Description Performs QR code login. Reuses cached credentials unless force is True. Otherwise runs the QR loop: fetches QR code, polls status every 2 seconds, and on confirmation persists credentials and returns them. ### Signature ```python async def login( base_url: str = DEFAULT_BASE_URL, token_path: str | Path | None = None, force: bool = False, ) -> Credentials: ``` ### Parameters - **base_url** (str) - Optional - iLink API base URL. Defaults to `DEFAULT_BASE_URL`. - **token_path** (str | Path | None) - Optional - Credential file path. Defaults to `~/.weixin-bot/credentials.json`. - **force** (bool) - Optional - Skip cached credentials. Defaults to `False`. ### Returns - **Credentials** - The credentials obtained from the QR login. ### Raises - **RuntimeError** - If QR login is confirmed but the API did not return bot credentials (missing/None `bot_token`/`ilink_bot_id`/`ilink_user_id`). ### Flow 1. Unless `force`, `load_credentials(base_url, token_path)` is tried first; valid cached credentials are returned as-is. 2. Otherwise the QR loop runs forever: - `fetch_qr_code(base_url)` → print the URL via `_print_qr_instructions` ("在微信中打开以下链接完成登录:" + URL to stderr). - Poll `poll_qr_status` every 2 s (`asyncio.sleep(QR_POLL_INTERVAL_MS / 1000)`), logging `scaned`/`confirmed`/`expired` transitions; `expired` breaks the inner loop and requests a new QR code. - On `confirmed`: missing/`None` `bot_token`/`ilink_bot_id`/`ilink_user_id` raises `RuntimeError("QR login confirmed, but the API did not return bot credentials")`. Otherwise persists via `_save_credentials_sync` (in a thread; mkdir `0o700`, chmod `0o600`, JSON with camelCase keys) and returns the `Credentials`. ``` -------------------------------- ### Get Bot QR Code Source: https://github.com/epiral/weixin-bot/blob/main/PROTOCOL.md Requests a QR code for bot login. The response contains a token and a URL for the QR image. ```http GET /ilink/bot/get_bot_qrcode?bot_type=3 → { qrcode: "", qrcode_img_content: "" } ``` -------------------------------- ### Basic WeixinBot usage with on_message handler Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/python-weixinbot.md Basic usage: create a bot, log in, register an async message handler, and run the blocking loop. The handler echoes the message text. ```python bot = WeixinBot() bot.login() @bot.on_message async def handle(msg): await bot.reply(msg, "Echo: " + msg.text) bot.run() ``` -------------------------------- ### Get Upload URL Source: https://github.com/epiral/weixin-bot/blob/main/PROTOCOL.md Obtains an encrypted upload parameter for uploading media to the CDN. media_type values: 1=IMG, 2=VID, 3=FILE, 4=VOICE. ```http POST /ilink/bot/getuploadurl Body: { filekey, media_type(1=IMG,2=VID,3=FILE,4=VOICE), to_user_id, rawsize, rawfilemd5, filesize, aeskey, no_need_thumb: true, base_info } → { upload_param: "" } ``` -------------------------------- ### Import WeixinBot and types Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/nodejs-weixinbot.md Import the WeixinBot class and its message types from the @pinixai/weixin-bot package. This is the entry point for using the client. ```typescript import { WeixinBot, type WeixinMessage, type IncomingMessage } from '@pinixai/weixin-bot' ``` -------------------------------- ### onMessage error handling example Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/nodejs-weixinbot.md Shows that handler errors are caught and do not break the poll loop. Throws an error for image messages and replies to others. ```typescript // Handler errors never break the poll loop bot.onMessage(async (msg) => { if (msg.type === 'image') { throw new Error('image handling not implemented') } await bot.reply(msg, `You said: ${msg.text}`) }) ``` -------------------------------- ### send_typing Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/python-api.md Sends a typing indicator to a user. Sends a POST request to /ilink/bot/sendtyping with a 15-second timeout. Status 1 starts typing, 2 cancels. ```APIDOC ## POST /ilink/bot/sendtyping ### Description Sends a typing indicator to a user. Sends a POST request with a 15-second timeout. Status 1 starts typing, 2 cancels. ### Method POST ### Endpoint /ilink/bot/sendtyping ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - `ilink_user_id` (string) - Required - The user ID. - `typing_ticket` (string) - Required - Typing ticket. - `status` (integer) - Required - Typing status: `1` starts typing, `2` cancels. - `base_info` (object) - Required - Base information including `channel_version`. ### Request Example { "ilink_user_id": "user123", "typing_ticket": "ticket", "status": 1, "base_info": { "channel_version": "1.0.0" } } ### Response #### Success Response (200) - Response body (object) - Typically `{}`. #### Response Example {} ### Error Handling No specific error handling documented. ``` -------------------------------- ### fetchQrCode Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/nodejs-api.md Fetches a QR code for bot login. ```APIDOC ## fetchQrCode ### Description Fetches a QR code for bot login. ### Method GET ### Endpoint `/ilink/bot/get_bot_qrcode?bot_type=3` ### Parameters #### Query Parameters - **bot_type** (number) - Required - Bot type, fixed to 3. ### Response #### Success Response (200) - **qrcode** (string) - The QR code identifier. - **qrcode_img_content** (string) - The QR code image content. #### Response Example ```json { "qrcode": "...", "qrcode_img_content": "..." } ``` ``` -------------------------------- ### Force re-login with --force-login flag Source: https://github.com/epiral/weixin-bot/blob/main/examples/nodejs/README.md Runs the echo bot with `--force-login` to force a new QR code login, overriding saved credentials. ```bash npx tsx echo-bot.ts --force-login ``` -------------------------------- ### login signature Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/python-auth.md Async function to perform QR login. Parameters include base_url, token_path, and force. Returns Credentials. ```python async def login( base_url: str = DEFAULT_BASE_URL, token_path: str | Path | None = None, force: bool = False, ) -> Credentials: ``` -------------------------------- ### login Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/nodejs-auth.md Performs QR code login flow. Returns credentials or throws on failure. ```APIDOC ## `login` ### Description Performs QR code login flow. If `options.force` is falsy, attempts to load cached credentials first; otherwise runs the QR loop until login is confirmed. ### Signature ```typescript export async function login(options: LoginOptions = {}): Promise ``` ### Parameters - `options` (LoginOptions) - Optional - See `LoginOptions` above. Default: `{}`. ### Flow 1. If `options.force` is falsy, `loadCredentials()` is attempted first; valid cached credentials are returned immediately. 2. Otherwise the QR loop runs forever: - `fetchQrCode(baseUrl)` → print `qrcode_img_content` to stderr (the QR URL; companion example apps render it with `qrcode-terminal`). - Poll `pollQrStatus` every `QR_POLL_INTERVAL_MS` (2 s), logging transitions: `scaned` → "QR code scanned. Confirm the login inside WeChat.", `confirmed` → "Login confirmed.", `expired` → "QR code expired. Requesting a new one..." (then breaks the inner loop and requests a fresh QR code). - On `confirmed`: if any of `bot_token`, `ilink_bot_id`, `ilink_user_id` is missing, throws `Error('QR login confirmed, but the API did not return bot credentials')`. Otherwise builds `Credentials` (with `baseUrl` = `status.baseurl ?? baseUrl`), persists them via `saveCredentials` (mkdir `0o700`, file mode `0o600`, JSON + trailing newline), and returns. The loop is infinite by design: `expired` codes only cause a new QR request, network failures from `fetchQrCode`/`pollQrStatus` reject out of the loop (surfacing as `ApiError`). ### Returns - `Promise` - The obtained credentials. ### Errors - `Error('QR login confirmed, but the API did not return bot credentials')` - Thrown when the API response is missing required fields. - `ApiError` - Propagated from network failures. ### Examples ```typescript import { login } from '../src/auth.js' // internal module const creds = await login() // reuses ~/.weixin-bot/credentials.json if present ``` ```typescript import { login, clearCredentials } from '../src/auth.js' try { const creds = await login({ tokenPath: '/tmp/weixin.json', force: true }) console.log(creds.token) } catch (err) { console.error('login rejected', err) } finally { await clearCredentials('/tmp/weixin.json') } ``` ``` -------------------------------- ### apiGet helper function Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/nodejs-api.md A GET helper used by fetchQrCode and pollQrStatus. It does not support timeout signals and rejects with ApiError on non-2xx responses or when ret is not 0. ```typescript export async function apiGet( baseUrl: string, path: string, headers: Record = {}, ): Promise ``` -------------------------------- ### WeixinBot constructor Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/nodejs-weixinbot.md Constructor for the WeixinBot client. Accepts optional configuration and performs no I/O until needed. ```APIDOC ## Constructor ```typescript constructor(options: WeixinBotOptions = {}) ``` ### Description Creates a new WeixinBot client instance. The constructor performs no I/O; credentials are resolved lazily by the first operation that needs them. ### Parameters - **options** (WeixinBotOptions) - Optional - Optional configuration (see table above). ### Example ```typescript import { WeixinBot } from '@pinixai/weixin-bot' const bot = new WeixinBot({ baseUrl: 'https://ilinkai.weixin.qq.com', tokenPath: '/etc/weixin-bot/credentials.json', onError: (err) => console.error('[bot error]', err), }) ``` ``` -------------------------------- ### GET /ilink/bot/get_qrcode_status Source: https://github.com/epiral/weixin-bot/blob/main/PROTOCOL.md Polls the status of a QR code login. Returns the current status and, upon confirmation, provides the bot token and user IDs. ```APIDOC ## GET /ilink/bot/get_qrcode_status ### Description Polls the status of a QR code login. Returns the current status and, upon confirmation, provides the bot token and user IDs. ### Method GET ### Endpoint /ilink/bot/get_qrcode_status ### Parameters #### Query Parameters - **qrcode** (string) - Required - The token obtained from the QR code endpoint. #### Headers - **iLink-App-ClientVersion** (string) - Required - Must be set to "1". ### Response #### Success Response (200) - **status** (string) - One of: "wait", "scaned", "confirmed", "expired". - **bot_token** (string) - Optional - Present when status is "confirmed". - **ilink_bot_id** (string) - Optional - Present when status is "confirmed". - **ilink_user_id** (string) - Optional - Present when status is "confirmed". - **baseurl** (string) - Optional - Present when status is "confirmed". #### Response Example { "status": "confirmed", "bot_token": "", "ilink_bot_id": "", "ilink_user_id": "", "baseurl": "" } ``` -------------------------------- ### login method signature Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/nodejs-weixinbot.md Logs in and returns credentials. If force is true, ignores cached credentials and requires a fresh QR scan. Cached credentials are loaded first unless force is set; the response baseUrl permanently replaces the client's configured base URL. On failure, the underlying login() loops forever requesting new QR codes until login is confirmed; ApiError conditions propagate as rejects. ```typescript async login(options: { force?: boolean } = {}): Promise ``` -------------------------------- ### fetch_qr_code function signature Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/python-api.md Defines the asynchronous function signature for fetching a QR code. It GETs `/ilink/bot/get_bot_qrcode?bot_type=3` with no headers and returns a `QrCodeResponse` containing `qrcode` and `qrcode_img_content`. ```python async def fetch_qr_code(base_url: str) -> QrCodeResponse: ``` -------------------------------- ### Import WeixinBot and IncomingMessage Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/python-weixinbot.md Import the WeixinBot client and IncomingMessage type from the weixin-bot-sdk package. ```python from weixin_bot import WeixinBot, IncomingMessage ``` -------------------------------- ### build_text_message output structure Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/python-api.md Shows the message dictionary built by `build_text_message`. It includes `from_user_id` as empty string, `to_user_id` from the `user_id` parameter, a generated `client_id` via `uuid4()`, `message_type` as `MessageType.BOT` (value 2), `message_state` as `MessageState.FINISH` (value 2), the echoed `context_token`, and a text item list. ```python { "from_user_id": "", "to_user_id": user_id, "client_id": str(uuid4()), "message_type": MessageType.BOT, # 2 "message_state": MessageState.FINISH, # 2 "context_token": context_token, "item_list": [{"type": MessageItemType.TEXT, "text_item": {"text": text}}], } ``` -------------------------------- ### POST /ilink/bot/getconfig Source: https://github.com/epiral/weixin-bot/blob/main/PROTOCOL.md Retrieves configuration, including a typing ticket for sending typing indicators. ```APIDOC ## POST /ilink/bot/getconfig ### Description Retrieves configuration, including a typing ticket for sending typing indicators. ### Method POST ### Endpoint /ilink/bot/getconfig ### Parameters #### Request Body - **ilink_user_id** (string) - Required - The user ID. - **context_token** (string) - Required - The context token from an inbound message. - **base_info** (object) - Required - Contains channel_version. #### Headers - **Content-Type** (string) - Required - application/json - **AuthorizationType** (string) - Required - ilink_bot_token - **Authorization** (string) - Required - Bearer - **X-WECHAT-UIN** (string) - Required - base64(String(randomUint32)) ### Response #### Success Response (200) - **typing_ticket** (string) - Base64-encoded typing ticket. #### Response Example { "typing_ticket": "" } ``` -------------------------------- ### GET /ilink/bot/get_qrcode_status Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/endpoints.md Polls the QR scan status for a given QR code token. The status transitions from `wait` to `scaned` to `confirmed`, or `expired` if the QR code is no longer valid. ```APIDOC ## GET /ilink/bot/get_qrcode_status?qrcode= ### Description Poll the QR scan status for a given QR code token. The status transitions from `wait` to `scaned` to `confirmed`, or `expired` if the QR code is no longer valid. ### Method GET ### Endpoint `/ilink/bot/get_qrcode_status?qrcode=` ### Parameters #### Query Parameters - **qrcode** (string) - Required - The QR code token to poll. ### Request Headers - `iLink-App-ClientVersion: 1` ### Request Body None ### Response #### Success Response (200) - **status** (string) - QR state machine: `"wait" | "scaned" | "confirmed" | "expired"`. - **bot_token** (string, optional) - Present on `confirmed`; Bearer token for all business calls. - **ilink_bot_id** (string, optional) - Bot account (`...@im.bot`). - **ilink_user_id** (string, optional) - Authorizing user (`...@im.wechat`). - **baseurl** (string, optional) - Business API base to use from now on. #### Response Example ```json { "status": "confirmed", "bot_token": "...", "ilink_bot_id": "...", "ilink_user_id": "...", "baseurl": "..." } ``` ``` -------------------------------- ### WeixinBot constructor Source: https://github.com/epiral/weixin-bot/blob/main/_autodocs/api-reference/nodejs-weixinbot.md Creates a WeixinBot instance. The constructor performs no I/O; credentials are resolved lazily by the first operation that needs them. ```typescript constructor(options: WeixinBotOptions = {}) ```