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.py → INLINECODE2 - QR login:
src/auth/qr_login.py → INLINECODE4 - HMAC signer:
src/hmac/signer.js (Node.js, auto-starts on port 18944) - Token storage: INLINECODE6
- Certificate cache: INLINECODE7
- WASM files:
lstm.wasm + lstmSandbox.js (required, in repo root)
Quick Start
CODEBLOCK0
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.
CODEBLOCK1
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 - INLINECODE13 → callback URL (append
?secret={curve25519_pubkey}&e2eeVersion=1) - INLINECODE15 — poll until scan (uses
X-Line-Session-ID, no origin header) verifyCertificate — MUST be called even if it fails (required state transition!)- INLINECODE19 → 6-digit PIN (skip if cert verified in step 4)
- INLINECODE20 — poll until user enters PIN
- INLINECODE21 → JWT token + certificate + refresh token
Server-Side Login Script
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 |
|---|
| INLINECODE25 | — | Get your own profile (displayName, mid, statusMessage, etc.) |
| INLINECODE26 |
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 |
|---|
| INLINECODE41 | to: str, text: str, replyto: str (opt) | Send a text message. Supports replies via INLINECODE42 |
| INLINECODE43 |
messageid: 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, endseq: 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, lastmessage_id: str | Mark messages as read |
|
send_chat_removed(chat_id, last_message_id) | chat
id, lastmessage_id: str | Remove chat from inbox |
|
send_postback(to, postback_data) | to, postback_data: str | Send postback (bot interactions) |
Chats & Groups
| Method | Args | Description |
|---|
| INLINECODE53 | chat_ids: list[str] | Get chat/group details |
| INLINECODE54 |
— | 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 |
|---|
| INLINECODE67 | messageid: str, type: int | React to a message. Types: 2=like, 3=love, 4=laugh, 5=surprised, 6=sad, 7=angry |
| INLINECODE68 |
messageid: str | Remove your reaction |
Profile & Settings
| Method | Args | Description |
|---|
| INLINECODE69 | attr: int, value: str | Update profile. Attrs: 2=DISPLAYNAME, 16=STATUSMESSAGE, 4=PICTURE_STATUS |
| INLINECODE70 |
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 |
|---|
| INLINECODE75 | — | Get latest operation revision number |
| INLINECODE76 |
— | 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=RECEIVEMESSAGE |
|
stop() | — | Stop the polling thread |
Other Services
| Method | Args | Description |
|---|
| INLINECODE80 | — | Get LINE server timestamp |
| INLINECODE81 |
— | 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) - INLINECODE90 or
c... → Group chat (toType=2) - INLINECODE92 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 → INLINECODE97 - Server mode: ~13ms/sign (Node.js HTTP server on port 18944, auto-started)
- Subprocess mode: ~2s/sign (fallback)
Error Handling
CODEBLOCK3
Architecture
CODEBLOCK4
The Chrome Gateway translates JSON ↔ Thrift internally. We never deal with Thrift binary — everything is clean JSON.
LINE 客户端技能
通过 Chrome 扩展网关 JSON API 实现的完整 LINE 消息客户端。
仓库与文件
- - 仓库: /data/workspace/line-client (github.com/2manslkh/line-api)
- 主客户端: src/chromeclient.py → LineChromeClient
- 二维码登录: src/auth/qrlogin.py → QRLogin
- HMAC 签名器: src/hmac/signer.js(Node.js,自动启动于端口 18944)
- 令牌存储: ~/.line-client/tokens.json
- 证书缓存: ~/.line-client/sqrcert
- WASM 文件: lstm.wasm + lstmSandbox.js(必需,位于仓库根目录)
快速开始
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(authtoken=tokens[authtoken])
发送消息
client.send_message(U..., 你好!)
获取个人资料
profile = client.get_profile()
令牌约 7 天过期。如果过期(APIError(10051)),重新运行二维码登录。
二维码登录(身份验证)
二维码登录需要用户交互:扫描手机上的二维码 + 输入 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(
onqr=lambda url: sendqrimageto_user(qrcode.make(url)),
onpin=lambda pin: sendpintouser_IMMEDIATELY(pin), # 时间敏感!
on_status=lambda msg: print(msg),
)
result.authtoken, result.mid, result.refreshtoken
关键: PIN 码必须在约 60 秒内送达用户。在 on_pin 触发时立即发送。
二维码登录状态机
- 1. createSession → 会话 ID
- createQrCode → 回调 URL(追加 ?secret={curve25519_pubkey}&e2eeVersion=1)
- checkQrCodeVerified — 轮询直到扫描(使用 X-Line-Session-ID,无 origin 头)
- verifyCertificate — 即使失败也必须调用(必需的状态转换!)
- createPinCode → 6 位 PIN 码(如果步骤 4 中证书已验证则跳过)
- checkPinCodeVerified — 轮询直到用户输入 PIN 码
- qrCodeLoginV2 → JWT 令牌 + 证书 + 刷新令牌
服务端登录脚本
bash
python scripts/qr
loginserver.py /tmp/qr.png
向标准输出发送 JSON 事件:{event: qr, path: ..., url: ...},{event: pin, pin: 123456},{event: done, mid: U...}。
所有 API 方法
联系人与好友
| 方法 | 参数 | 描述 |
|---|
| getprofile() | — | 获取自己的个人资料(displayName、mid、statusMessage 等) |
| getcontact(mid) |
mid: str | 获取单个联系人的个人资料 |
| get_contacts(mids) | mids: list[str] | 获取多个联系人 |
| get
allcontact_ids() | — | 列出所有好友 MID |
| find
contactby_userid(userid) | userid: str | 通过 LINE ID 搜索 |
| find
andadd
contactby_mid(mid) | mid: str | 通过 MID 添加好友 |
| find
contactsby_phone(phones) | phones: list[str] | 通过手机号搜索 |
| add
friendby_mid(mid) | mid: str | 添加好友(RelationService) |
| get
blockedcontact_ids() | — | 列出已屏蔽的 MID |
| get
blockedrecommendation_ids() | — | 列出已屏蔽的推荐 |
| block_contact(mid) | mid: str | 屏蔽联系人 |
| unblock_contact(mid) | mid: str | 取消屏蔽联系人 |
| block_recommendation(mid) | mid: str | 屏蔽好友推荐 |
| update
contactsetting(mid, flag, value) | mid, flag: int, value: str | 更新联系人设置(例如静音) |
| get
favoritemids() | — | 列出收藏的联系人 MID |
| get
recommendationids() | — | 列出好友推荐 |
消息
| 方法 | 参数 | 描述 |
|---|
| sendmessage(to, text, ...) | to: str, text: str, replyto: str(可选) | 发送文本消息。支持通过 replyto=messageid 回复 |
| unsendmessage(messageid) |
message_id: str | 撤回/删除已发送的消息 |
| get
recentmessages(chat
id, count=50) | chatid: str | 获取聊天中的最新消息 |
| get
previousmessages(chat
id, endseq, count=50) | chat
id, endseq: int | 分页历史记录(更早的消息) |
| get
messagesby
ids(messageids) | message_ids: list[str] | 获取特定消息 |
| get
messageboxes(count=50) | — | 获取聊天列表及最后一条消息(收件箱视图) |
| get
messageboxes
byids(chat
ids) | chatids: list[str] | 获取特定聊天及最后一条消息 |
| get
messageread
range(chatids) | chat_ids: list[str] | 获取已读回执信息 |
| send
chatchecked(chat
id, lastmessage
id) | chatid, last
messageid: str | 标记消息为已读 |
| send
chatremoved(chat
id, lastmessage
id) | chatid, last
messageid: str | 从收件箱移除聊天 |
| send
postback(to, postbackdata) | to, postback_data: str | 发送回传(机器人交互) |
聊天与群组
| 方法 | 参数 | 描述 |
|---|
| getchats(chatids, withmembers=True, withinvitees=True) | chatids: list[str] | 获取聊天/群组详情 |
| getallchatmids() |
— | 列出所有聊天 MID(群组 + 邀请) |
| create
chat(name, targetmids) | name: str, target_mids: list[str] | 创建新群聊 |
| accept
chatinvitation(chat
id) | chatid: str | 接受群组邀请 |
| reject
chatinvitation(chat
id) | chatid: str | 拒绝群组邀请 |
| invite
intochat(chat
id, mids) | chatid: str, mids: list[str] | 邀请用户加入群组 |
| cancel
chatinvitation(chat
id, mids) | chatid: str, mids: list[str] | 取消待处理的邀请 |
| delete
otherfrom
chat(chatid, mids) | chat_id: str, mids: list[str] | 将成员踢出群组 |
| leave
chat(chatid) | chat_id: str | 离开群聊 |
| update
chat(chatid, updates) | chat_id: str, updates: dict | 更新群组名称/设置 |
| set
chathidden
status(chatid, hidden) | chat_id: str, hidden: bool | 归档/取消归档聊天 |
| get
rooms(roomids) | room_ids: list[str] | 获取旧版房间信息 |
| invite
intoroom(room
id, mids) | roomid: str, mids: list[str] | 邀请加入旧版房间 |
| leave
room(roomid) | room_id: str | 离开旧版房间 |
反应
| 方法 | 参数 | 描述 |
|---|
| react(messageid, reactiontype) | messageid: str, type: int | 对消息做出反应。类型:2=赞,3=喜爱,4=笑,5=惊讶,6=悲伤,7=愤怒 |
| cancelreaction(messageid) |
messageid: str | 移除你的反应 |
个人资料与设置
| 方法