✓ Verified 💻 Development ✓ Enhanced Data

Line Client

LINE messaging integration via Chrome extension gateway.

Rating
4.4 (289 reviews)
Downloads
1,030 downloads
Version
1.0.0

Overview

LINE messaging integration via Chrome extension gateway.

Complete Documentation

View Source →

LINE Client Skill

Full LINE messaging client via the Chrome extension gateway JSON API.

Repo & Files

  • Repo: /data/workspace/line-client (github.com/2manslkh/line-api)
  • Main client: src/chrome_client.pyLineChromeClient
  • QR login: src/auth/qr_login.pyQRLogin
  • HMAC signer: src/hmac/signer.js (Node.js, auto-starts on port 18944)
  • Token storage: ~/.line-client/tokens.json
  • Certificate cache: ~/.line-client/sqr_cert
  • WASM files: lstm.wasm + lstmSandbox.js (required, in repo root)

Quick Start

python
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 (Authentication)

QR login requires user interaction: scan QR on phone + enter PIN.

python
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.

QR Login State Machine

  • createSession → session ID
  • createQrCode → 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 PIN
  • qrCodeLoginV2 → JWT token + certificate + refresh token

Server-Side Login Script

bash
python 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..."}.

All API Methods

Contacts & Friends

MethodArgsDescription
get_profile()Get your own profile (displayName, mid, statusMessage, etc.)
get_contact(mid)mid: strGet 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: strSearch by LINE ID
find_and_add_contact_by_mid(mid)mid: strAdd friend by MID
find_contacts_by_phone(phones)phones: list[str]Search by phone numbers
add_friend_by_mid(mid)mid: strAdd friend (RelationService)
get_blocked_contact_ids()List blocked MIDs
get_blocked_recommendation_ids()List blocked recommendations
block_contact(mid)mid: strBlock a contact
unblock_contact(mid)mid: strUnblock a contact
block_recommendation(mid)mid: strBlock a friend suggestion
update_contact_setting(mid, flag, value)mid, flag: int, value: strUpdate contact setting (e.g. mute)
get_favorite_mids()List favorited contact MIDs
get_recommendation_ids()List friend suggestions

Messages

MethodArgsDescription
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: strUnsend/delete a sent message
get_recent_messages(chat_id, count=50)chat_id: strGet latest messages in a chat
get_previous_messages(chat_id, end_seq, count=50)chat_id, end_seq: intPaginated 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: strMark messages as read
send_chat_removed(chat_id, last_message_id)chat_id, last_message_id: strRemove chat from inbox
send_postback(to, postback_data)to, postback_data: strSend postback (bot interactions)

Chats & Groups

MethodArgsDescription
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: strAccept group invite
reject_chat_invitation(chat_id)chat_id: strReject 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: strLeave a group chat
update_chat(chat_id, updates)chat_id: str, updates: dictUpdate group name/settings
set_chat_hidden_status(chat_id, hidden)chat_id: str, hidden: boolArchive/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: strLeave legacy room

Reactions

MethodArgsDescription
react(message_id, reaction_type)message_id: str, type: intReact to a message. Types: 2=like, 3=love, 4=laugh, 5=surprised, 6=sad, 7=angry
cancel_reaction(message_id)message_id: strRemove your reaction

Profile & Settings

MethodArgsDescription
update_profile_attributes(attr, value, meta={})attr: int, value: strUpdate profile. Attrs: 2=DISPLAY_NAME, 16=STATUS_MESSAGE, 4=PICTURE_STATUS
update_status_message(message)message: strShortcut: update status message
update_display_name(name)name: strShortcut: update display name
get_settings()Get all account settings
get_settings_attributes(attr_bitset)attr_bitset: intGet specific settings
update_settings_attributes(attr_bitset, settings)attr_bitset: int, settings: dictUpdate settings

Polling & Events

MethodArgsDescription
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

Other Services

MethodArgsDescription
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: strIssue channel token (LINE Login/LIFF)
get_buddy_detail(mid)mid: strGet official account info
report_abuse(mid, category=0, reason="")mid: strReport a user
add_friend_by_mid(mid)mid: strAdd friend (RelationService)
logout()Logout and invalidate token

MID Format

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.

HMAC Signing

All API calls require X-Hmac header. The WASM signer handles this automatically:

  • Derives key from version "3.7.1" + access token via proprietary KDF (in lstm.wasm)
  • Signs path + body → base64 → X-Hmac
  • Server mode: ~13ms/sign (Node.js HTTP server on port 18944, auto-started)
  • Subprocess mode: ~2s/sign (fallback)

Error Handling

python
from 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

Architecture

text
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.

Installation

Terminal bash

openclaw install line-client
    
Copied!

💻Code Examples

profile = client.get_profile()

profile--clientgetprofile.txt
Tokens expire in ~7 days. If expired (`APIError(10051)`), re-run QR login.

## QR Login (Authentication)

QR login requires user interaction: scan QR on phone + enter PIN.

# result.auth_token, result.mid, result.refresh_token

-resultauthtoken-resultmid-resultrefreshtoken.txt
**Critical:** The PIN must reach the user within ~60 seconds. Send it the instant `on_pin` fires.

### QR Login State Machine
1. `createSession` → session ID
2. `createQrCode` → callback URL (append `?secret={curve25519_pubkey}&e2eeVersion=1`)
3. `checkQrCodeVerified` — poll until scan (uses `X-Line-Session-ID`, no `origin` header)
4. **`verifyCertificate`** — MUST be called even if it fails (required state transition!)
5. `createPinCode` → 6-digit PIN (skip if cert verified in step 4)
6. `checkPinCodeVerified` — poll until user enters PIN
7. `qrCodeLoginV2` → JWT token + certificate + refresh token

### Server-Side Login Script

python scripts/qr_login_server.py /tmp/qr.png

python-scriptsqrloginserverpy-tmpqrpng.txt
Emits JSON events on stdout: `{"event": "qr", "path": "...", "url": "..."}`, `{"event": "pin", "pin": "123456"}`, `{"event": "done", "mid": "U..."}`.

## All API Methods

### Contacts & Friends

| 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 |

### Messages

| 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) |

### Chats & Groups

| 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 |

### Reactions

| 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 |

### Profile & Settings

| 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 |

### Polling & Events

| 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 |

### Other Services

| 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 |

## MID Format

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.

## HMAC Signing

All API calls require `X-Hmac` header. The WASM signer handles this automatically:
- Derives key from version "3.7.1" + access token via proprietary KDF (in lstm.wasm)
- Signs `path + body` → base64 → `X-Hmac`
- Server mode: ~13ms/sign (Node.js HTTP server on port 18944, auto-started)
- Subprocess mode: ~2s/sign (fallback)

## Error Handling
example.py
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()
example.py
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
example.py
from 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
example.txt
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

Tags

#web_and-frontend-development #cli #integration

Quick Info

Category Development
Model Claude 3.5
Complexity One-Click
Author 2manslkh
Last Updated 3/10/2026
🚀
Optimized for
Claude 3.5
🧠

Ready to Install?

Get started with this skill in seconds

openclaw install line-client