Back to skills
SkillHub ClubShip Full StackFull StackBackendIntegration

line-client

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

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
3,114
Hot score
99
Updated
March 20, 2026
Overall rating
C0.0
Composite score
0.0
Best-practice grade
B81.2

Install command

npx @skill-hub/cli install openclaw-skills-line-api

Repository

openclaw/skills

Skill path: skills/2manslkh/line-api

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

Open repository

Best for

Primary workflow: Ship Full Stack.

Technical facets: Full Stack, Backend, Integration.

Target audience: everyone.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: openclaw.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install line-client into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/openclaw/skills before adding line-client to shared team environments
  • Use line-client for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: line-client
description: 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).
---

# 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](https://github.com/2manslkh/line-api))
- **Main client:** `src/chrome_client.py` → `LineChromeClient`
- **QR login:** `src/auth/qr_login.py` → `QRLogin`
- **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
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
```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

| 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

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

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


---

## Skill Companion Files

> Additional files collected from the skill directory layout.

### _meta.json

```json
{
  "owner": "2manslkh",
  "slug": "line-api",
  "displayName": "Line Client",
  "latest": {
    "version": "0.2.1",
    "publishedAt": 1771942904848,
    "commit": "https://github.com/openclaw/skills/commit/5dde22e2bc9e332e7f9a726b9a051e24821c5e2a"
  },
  "history": []
}

```

line-client | SkillHub