2manslkh/line-client/SKILL.md
LINE messaging integration via Chrome extension gateway. Send/read LINE messages, manage contacts, groups, profile, and reactions. Authenticate with QR code login. Provides HMAC-signed API access through the Chrome extension gateway (line-chrome-gw.line-apps.com).
npx skillsauth add openclaw/skills line-clientInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Full LINE messaging client via the Chrome extension gateway JSON API.
/data/workspace/line-client (github.com/2manslkh/line-api)src/chrome_client.py → LineChromeClientsrc/auth/qr_login.py → QRLoginsrc/hmac/signer.js (Node.js, auto-starts on port 18944)~/.line-client/tokens.json~/.line-client/sqr_certlstm.wasm + lstmSandbox.js (required, in repo root)import json
from pathlib import Path
from src.chrome_client import LineChromeClient
tokens = json.loads((Path.home() / ".line-client" / "tokens.json").read_text())
client = LineChromeClient(auth_token=tokens["auth_token"])
# Send a message
client.send_message("U...", "Hello!")
# Get profile
profile = client.get_profile()
Tokens expire in ~7 days. If expired (APIError(10051)), re-run QR login.
QR login requires user interaction: scan QR on phone + enter PIN.
from src.hmac import HmacSigner
from src.auth.qr_login import QRLogin
import qrcode
signer = HmacSigner(mode="server")
login = QRLogin(signer)
result = login.run(
on_qr=lambda url: send_qr_image_to_user(qrcode.make(url)),
on_pin=lambda pin: send_pin_to_user_IMMEDIATELY(pin), # TIME SENSITIVE!
on_status=lambda msg: print(msg),
)
# result.auth_token, result.mid, result.refresh_token
Critical: The PIN must reach the user within ~60 seconds. Send it the instant on_pin fires.
createSession → session IDcreateQrCode → callback URL (append ?secret={curve25519_pubkey}&e2eeVersion=1)checkQrCodeVerified — poll until scan (uses X-Line-Session-ID, no origin header)verifyCertificate — MUST be called even if it fails (required state transition!)createPinCode → 6-digit PIN (skip if cert verified in step 4)checkPinCodeVerified — poll until user enters PINqrCodeLoginV2 → JWT token + certificate + refresh tokenpython scripts/qr_login_server.py /tmp/qr.png
Emits JSON events on stdout: {"event": "qr", "path": "...", "url": "..."}, {"event": "pin", "pin": "123456"}, {"event": "done", "mid": "U..."}.
| Method | Args | Description |
|--------|------|-------------|
| get_profile() | — | Get your own profile (displayName, mid, statusMessage, etc.) |
| get_contact(mid) | mid: str | Get a single contact's profile |
| get_contacts(mids) | mids: list[str] | Get multiple contacts |
| get_all_contact_ids() | — | List all friend MIDs |
| find_contact_by_userid(userid) | userid: str | Search by LINE ID |
| find_and_add_contact_by_mid(mid) | mid: str | Add friend by MID |
| find_contacts_by_phone(phones) | phones: list[str] | Search by phone numbers |
| add_friend_by_mid(mid) | mid: str | Add friend (RelationService) |
| get_blocked_contact_ids() | — | List blocked MIDs |
| get_blocked_recommendation_ids() | — | List blocked recommendations |
| block_contact(mid) | mid: str | Block a contact |
| unblock_contact(mid) | mid: str | Unblock a contact |
| block_recommendation(mid) | mid: str | Block a friend suggestion |
| update_contact_setting(mid, flag, value) | mid, flag: int, value: str | Update contact setting (e.g. mute) |
| get_favorite_mids() | — | List favorited contact MIDs |
| get_recommendation_ids() | — | List friend suggestions |
| Method | Args | Description |
|--------|------|-------------|
| send_message(to, text, ...) | to: str, text: str, reply_to: str (opt) | Send a text message. Supports replies via reply_to=message_id |
| unsend_message(message_id) | message_id: str | Unsend/delete a sent message |
| get_recent_messages(chat_id, count=50) | chat_id: str | Get latest messages in a chat |
| get_previous_messages(chat_id, end_seq, count=50) | chat_id, end_seq: int | Paginated history (older messages) |
| get_messages_by_ids(message_ids) | message_ids: list[str] | Fetch specific messages |
| get_message_boxes(count=50) | — | Get chat list with last message (inbox view) |
| get_message_boxes_by_ids(chat_ids) | chat_ids: list[str] | Get specific chats with last message |
| get_message_read_range(chat_ids) | chat_ids: list[str] | Get read receipt info |
| send_chat_checked(chat_id, last_message_id) | chat_id, last_message_id: str | Mark messages as read |
| send_chat_removed(chat_id, last_message_id) | chat_id, last_message_id: str | Remove chat from inbox |
| send_postback(to, postback_data) | to, postback_data: str | Send postback (bot interactions) |
| Method | Args | Description |
|--------|------|-------------|
| get_chats(chat_ids, with_members=True, with_invitees=True) | chat_ids: list[str] | Get chat/group details |
| get_all_chat_mids() | — | List all chat MIDs (groups + invites) |
| create_chat(name, target_mids) | name: str, target_mids: list[str] | Create a new group chat |
| accept_chat_invitation(chat_id) | chat_id: str | Accept group invite |
| reject_chat_invitation(chat_id) | chat_id: str | Reject group invite |
| invite_into_chat(chat_id, mids) | chat_id: str, mids: list[str] | Invite users to group |
| cancel_chat_invitation(chat_id, mids) | chat_id: str, mids: list[str] | Cancel pending invites |
| delete_other_from_chat(chat_id, mids) | chat_id: str, mids: list[str] | Kick members from group |
| leave_chat(chat_id) | chat_id: str | Leave a group chat |
| update_chat(chat_id, updates) | chat_id: str, updates: dict | Update group name/settings |
| set_chat_hidden_status(chat_id, hidden) | chat_id: str, hidden: bool | Archive/unarchive a chat |
| get_rooms(room_ids) | room_ids: list[str] | Get legacy room info |
| invite_into_room(room_id, mids) | room_id: str, mids: list[str] | Invite to legacy room |
| leave_room(room_id) | room_id: str | Leave legacy room |
| Method | Args | Description |
|--------|------|-------------|
| react(message_id, reaction_type) | message_id: str, type: int | React to a message. Types: 2=like, 3=love, 4=laugh, 5=surprised, 6=sad, 7=angry |
| cancel_reaction(message_id) | message_id: str | Remove your reaction |
| Method | Args | Description |
|--------|------|-------------|
| update_profile_attributes(attr, value, meta={}) | attr: int, value: str | Update profile. Attrs: 2=DISPLAY_NAME, 16=STATUS_MESSAGE, 4=PICTURE_STATUS |
| update_status_message(message) | message: str | Shortcut: update status message |
| update_display_name(name) | name: str | Shortcut: update display name |
| get_settings() | — | Get all account settings |
| get_settings_attributes(attr_bitset) | attr_bitset: int | Get specific settings |
| update_settings_attributes(attr_bitset, settings) | attr_bitset: int, settings: dict | Update settings |
| Method | Args | Description |
|--------|------|-------------|
| get_last_op_revision() | — | Get latest operation revision number |
| fetch_ops(count=50) | — | Fetch pending operations (may long-poll) |
| poll() | — | Generator yielding operations as they arrive |
| on_message(handler) | handler: Callable(msg, client) | Start polling thread, calls handler on new messages. Op types: 26=SEND_MESSAGE, 27=RECEIVE_MESSAGE |
| stop() | — | Stop the polling thread |
| Method | Args | Description |
|--------|------|-------------|
| get_server_time() | — | Get LINE server timestamp |
| get_configurations() | — | Get server configurations |
| get_rsa_key_info() | — | Get RSA key for auth |
| issue_channel_token(channel_id) | channel_id: str | Issue channel token (LINE Login/LIFF) |
| get_buddy_detail(mid) | mid: str | Get official account info |
| report_abuse(mid, category=0, reason="") | mid: str | Report a user |
| add_friend_by_mid(mid) | mid: str | Add friend (RelationService) |
| logout() | — | Logout and invalidate token |
LINE identifies entities by MID:
U... or u... → User (toType=0)C... or c... → Group chat (toType=2)R... or r... → Room (toType=1)The client auto-detects toType from the MID prefix when sending messages.
All API calls require X-Hmac header. The WASM signer handles this automatically:
path + body → base64 → X-Hmacfrom src.chrome_client import APIError
try:
client.send_message(mid, "test")
except APIError as e:
print(e.code, e.api_message)
# 10051 = session expired / invalid
# 10052 = HTTP error from backend
# 10102 = invalid arguments
User's Phone (LINE app)
↕ (scan QR / enter PIN)
LINE Servers (line-chrome-gw.line-apps.com)
↕ (JSON REST + X-Hmac signing)
LineChromeClient (this repo)
↕ (WASM HMAC via Node.js signer)
lstm.wasm + lstmSandbox.js
The Chrome Gateway translates JSON ↔ Thrift internally. We never deal with Thrift binary — everything is clean JSON.
tools
Use when the user wants to connect to, test, or use the McDonalds service at mcp.mcd.cn, including checking authentication, probing MCP endpoints, listing tools, or calling McDonalds MCP tools through a reusable local CLI.
development
Web scraping platform — Twitter/X data, Vinted marketplace, and general web scraping API
development
SlowMist AI Agent Security Review — comprehensive security framework for skills, repositories, URLs, on-chain addresses, and products (Claude Code version)
data-ai
去除中文文本中的 AI 写作痕迹,使其读起来自然。基于维基百科 AI 写作特征指南,检测 24 种 AI 模式。触发词:humanizer-cn、去除 AI 痕迹、去除 AI 写作痕迹、中文文本人性化。