linejs
@evex/linejs is a TypeScript-first client library for the LINE Messenger protocol. It speaks LINE's Thrift binary wire format directly, supports E2EE, polling for events, and ships hand-written wrappers for the methods CHRLINE-Patch covers.
This skill is a roadmap, not a manual. The library moves fast; rather than freezing details that will rot, this file points the agent at the canonical sources to read on demand.
Install
deno add jsr:@evex/linejs
# Types are also available standalone:
deno add jsr:@evex/linejs-types
Latest release: check JSR — https://jsr.io/@evex/linejs or curl -s https://jsr.io/@evex/linejs/meta.json | jq -r .latest. The skill prompt does not pin a version; pin in user code.
Minimum-viable bot
import { BaseClient } from "@evex/linejs/base";
import { FileStorage } from "@evex/linejs/storage";
const storage = new FileStorage("./storage.json");
const client = new BaseClient({ device: "DESKTOPWIN", storage });
client.on("qrcall", (url) => console.log("scan:", url));
client.on("pincall", (pin) => console.log("PIN:", pin));
client.on("update:authtoken", (t) => storage.set(".auth", t));
const cached = await storage.get(".auth");
await client.loginProcess.login(
typeof cached === "string" ? { authToken: cached } : {},
);
for await (const op of client.createPolling().listenTalkEvents()) {
if (op.type === "RECEIVE_MESSAGE" || op.type === "SEND_MESSAGE") {
const msg = await client.e2ee.decryptE2EEMessage(op.message);
if (msg.text === "!ping") {
await client.talk.sendMessage({
to: msg.to === client.profile?.mid ? msg.from : msg.to,
text: "pong!",
e2ee: !!op.message.chunks,
});
}
}
}
That's the smallest useful bot. For richer surfaces start with the Client wrapper in @evex/linejs (it builds on BaseClient) — it has getMyProfile(), updateMy{DisplayName,StatusMessage,Profile}(), fetchJoinedChats(), getChat(), getUser(), uploadMyProfileImage(), uploadMyProfileBackground() and more.
Roadmap — where to look for what
In all cases, looking at the latest code is best.
https://github.com/evex-dev/linejs
| You want to … |
Look at |
| log in (QR / email) |
packages/linejs/base/login/mod.ts — uses ForSecure RPCs as of v2.7; legacy createQrCode is server-rejected |
| send / receive messages |
packages/linejs/client/features/message/ and BaseClient.talk.sendMessage |
| any Thrift RPC LINE supports |
packages/linejs/base/service/*/mod.ts — one wrapper per LINE service (30+ services, 130+ methods) |
| change profile attributes |
client.updateMyProfile({...}) / see packages/linejs/client/features/profile.ts |
| rename / favourite / mute friends |
User.rename() / User.setFavorite() etc. — see packages/linejs/client/features/user/mod.ts |
| chat BGM |
Chat.getBgm() / Chat.setBgm() |
| LIFF share / token |
client.liff.shareMessages(chatMid, [...]) and client.liff.getToken({ liffId, chatMid }) — see packages/linejs/client/features/liff.ts for message builders (text / sticker / image / flex). Lower-level surface at client.liff.service / BaseClient.liff. |
| VOOM / timeline / Note REST |
client.voomRest({ path, channelToken }) — low-level wrapper around the gw.line.naver.jp/mh/api/v{34,40,52,57}/... gateway. Mint the channel token first: await client.base.channel.issueChannelToken({ channelId: VoomChannelId.TIMELINE }) (note: object arg, not positional string). DESKTOPWIN device cannot do this — use device: "ANDROIDSECONDARY". Once Bearer'd, the gateway moves from 401 to 504 — remaining downstream error tracked in #151. |
| calendar events on a contact |
User.fetchCalendarEvents() — note: server-gated on some client device types (DESKTOPWIN currently lacks it) |
| approve a PIN login on this device |
client.base.loginProcess.respondE2EELoginRequest({ verifier, publicKey, encryptedKeyChain, hashKeyChain, errorCode? }) — primary-side counterpart to confirmE2EELogin. Call when this client receives a PIN-login notification from another device the user wants to admit. |
| upload profile picture / cover |
client.uploadMyProfileImage(blob) / client.uploadMyProfileBackground(blob) |
| Agent I (LINE's chat-tab AI search) |
@evex/linejs-agent — wraps Yahoo's search-agent SSE backend that LINE Android opens in a WebView. Requires Yahoo session cookies; LINE does not mint them. |
| E2EE primitives |
packages/linejs/base/e2ee/mod.ts — AES-GCM-SIV, ECDH, decrypt encrypted QR identifier, key chain etc. |
| Thrift schema |
packages/types/thrift.ts (data) + packages/types/line_types.ts (TS interfaces). Both auto-synced from LINE Android APK by deno task apk:sync. |
When in doubt, read the source under packages/linejs/client/features/ — feature classes are short and read like documentation.
Known constraints
- LINE 26+ requires the ForSecure QR login (
createQrCodeForSecure + qrCodeLoginV2ForSecure). Legacy createQrCode is silently expired by the server. linejs v2.7+ uses ForSecure by default; older versions cannot log in.
map<i32, V> encoding was broken on JS-object keys before v2.7 — JS keys are strings, the encoder rejected them. Fixed in v2.7. If you're targeting older linejs, build the map as a NestedArray tuple.
- E2EE decryption is required for most chat messages — see
client.e2ee.decryptE2EEMessage(op.message). Don't read op.message.text directly.
- Agent I needs Yahoo cookies. The LINE Android app loads the WebView with whatever Yahoo session the user already has; programmatic use requires you to obtain those cookies yourself (Yahoo Japan login → extract cookies). linejs ships the call shape, not the auth.
- Device type matters for feature gating.
DESKTOPWIN lacks several APIs that ANDROID has (e.g. getContactsV3, getContactCalendarEvents). For pure bot work DESKTOPWIN is usually enough; if a method returns API method not capable, switch device type or use a legacy fallback.
Bugs / issues
Report to https://github.com/evex-dev/linejs/issues. Useful context to include:
- linejs version (
deno info jsr:@evex/linejs shows resolved version)
- device type and LINE app version you're targeting
- minimal repro: the smallest snippet that reproduces, plus the error from
Request internal failed: status=… headers=… body=<…> (v2.7+ includes the HTTP status / headers in the thrown message — very helpful for triage)
- if QR login–related: which step (
createSession, createQrCodeForSecure, checkQrCodeVerified, qrCodeLoginV2ForSecure) and the [login] ForSecure: maxCount=… intervalSec=… nonce=… debug line if it printed.
CHRLINE-Patch-compatible behaviour reports (e.g. "this RPC works in CHRLINE but not linejs") are also useful — name the method and we'll port it.
What this skill deliberately does NOT contain
- Frozen API signatures — they change. Read the source.
- A list of every wrapped method — there are 130+, see
packages/linejs/base/service/ for the live set.
- Reverse-engineering details for LINE Android — Join Our Discord.
1---2name: linejs3description: Helps a coding agent build LINE bots / clients with the `@evex/linejs` library — JSR package, Deno-first, runs on Node/Bun too. Use when the user asks to write LINE bots, automate LINE accounts, work with LINE Thrift APIs, or wrap the AI "Agent I" feature.4---56# linejs78`@evex/linejs` is a TypeScript-first client library for the LINE Messenger protocol. It speaks LINE's Thrift binary wire format directly, supports E2EE, polling for events, and ships hand-written wrappers for the methods CHRLINE-Patch covers.910This skill is a **roadmap**, not a manual. The library moves fast; rather than freezing details that will rot, this file points the agent at the canonical sources to read on demand.1112## Install1314```sh15deno add jsr:@evex/linejs16# Types are also available standalone:17deno add jsr:@evex/linejs-types18```1920Latest release: check JSR — `https://jsr.io/@evex/linejs` or `curl -s https://jsr.io/@evex/linejs/meta.json | jq -r .latest`. The skill prompt does **not** pin a version; pin in user code.2122## Minimum-viable bot2324```ts25import { BaseClient } from "@evex/linejs/base";26import { FileStorage } from "@evex/linejs/storage";2728const storage = new FileStorage("./storage.json");29const client = new BaseClient({ device: "DESKTOPWIN", storage });3031client.on("qrcall", (url) => console.log("scan:", url));32client.on("pincall", (pin) => console.log("PIN:", pin));33client.on("update:authtoken", (t) => storage.set(".auth", t));3435const cached = await storage.get(".auth");36await client.loginProcess.login(37 typeof cached === "string" ? { authToken: cached } : {},38);3940for await (const op of client.createPolling().listenTalkEvents()) {41 if (op.type === "RECEIVE_MESSAGE" || op.type === "SEND_MESSAGE") {42 const msg = await client.e2ee.decryptE2EEMessage(op.message);43 if (msg.text === "!ping") {44 await client.talk.sendMessage({45 to: msg.to === client.profile?.mid ? msg.from : msg.to,46 text: "pong!",47 e2ee: !!op.message.chunks,48 });49 }50 }51}52```5354That's the smallest useful bot. For richer surfaces start with the `Client` wrapper in `@evex/linejs` (it builds on `BaseClient`) — it has `getMyProfile()`, `updateMy{DisplayName,StatusMessage,Profile}()`, `fetchJoinedChats()`, `getChat()`, `getUser()`, `uploadMyProfileImage()`, `uploadMyProfileBackground()` and more.5556## Roadmap — where to look for what5758In all cases, looking at the latest code is best.59https://github.com/evex-dev/linejs6061| You want to … | Look at |62|---|---|63| log in (QR / email) | `packages/linejs/base/login/mod.ts` — uses ForSecure RPCs as of v2.7; legacy `createQrCode` is server-rejected |64| send / receive messages | `packages/linejs/client/features/message/` and `BaseClient.talk.sendMessage` |65| any Thrift RPC LINE supports | `packages/linejs/base/service/*/mod.ts` — one wrapper per LINE service (30+ services, 130+ methods) |66| change profile attributes | `client.updateMyProfile({...})` / see `packages/linejs/client/features/profile.ts` |67| rename / favourite / mute friends | `User.rename()` / `User.setFavorite()` etc. — see `packages/linejs/client/features/user/mod.ts` |68| chat BGM | `Chat.getBgm()` / `Chat.setBgm()` |69| LIFF share / token | `client.liff.shareMessages(chatMid, [...])` and `client.liff.getToken({ liffId, chatMid })` — see `packages/linejs/client/features/liff.ts` for message builders (`text` / `sticker` / `image` / `flex`). Lower-level surface at `client.liff.service` / `BaseClient.liff`. |70| VOOM / timeline / Note REST | `client.voomRest({ path, channelToken })` — low-level wrapper around the `gw.line.naver.jp/mh/api/v{34,40,52,57}/...` gateway. Mint the channel token first: `await client.base.channel.issueChannelToken({ channelId: VoomChannelId.TIMELINE })` (note: **object arg**, not positional string). `DESKTOPWIN` device cannot do this — use `device: "ANDROIDSECONDARY"`. Once Bearer'd, the gateway moves from 401 to 504 — remaining downstream error tracked in #151. |71| calendar events on a contact | `User.fetchCalendarEvents()` — note: server-gated on some client device types (DESKTOPWIN currently lacks it) |72| approve a PIN login on this device | `client.base.loginProcess.respondE2EELoginRequest({ verifier, publicKey, encryptedKeyChain, hashKeyChain, errorCode? })` — primary-side counterpart to `confirmE2EELogin`. Call when this client receives a PIN-login notification from another device the user wants to admit. |73| upload profile picture / cover | `client.uploadMyProfileImage(blob)` / `client.uploadMyProfileBackground(blob)` |74| **Agent I** (LINE's chat-tab AI search) | `@evex/linejs-agent` — wraps Yahoo's search-agent SSE backend that LINE Android opens in a WebView. Requires Yahoo session cookies; LINE does not mint them. |75| E2EE primitives | `packages/linejs/base/e2ee/mod.ts` — AES-GCM-SIV, ECDH, decrypt encrypted QR identifier, key chain etc. |76| Thrift schema | `packages/types/thrift.ts` (data) + `packages/types/line_types.ts` (TS interfaces). Both auto-synced from LINE Android APK by `deno task apk:sync`. |7778When in doubt, **read the source** under `packages/linejs/client/features/` — feature classes are short and read like documentation.7980## Known constraints8182- **LINE 26+ requires the ForSecure QR login** (`createQrCodeForSecure` + `qrCodeLoginV2ForSecure`). Legacy `createQrCode` is silently expired by the server. linejs v2.7+ uses ForSecure by default; older versions cannot log in.83- **`map<i32, V>` encoding** was broken on JS-object keys before v2.7 — JS keys are strings, the encoder rejected them. Fixed in v2.7. If you're targeting older linejs, build the map as a `NestedArray` tuple.84- **E2EE decryption is required** for most chat messages — see `client.e2ee.decryptE2EEMessage(op.message)`. Don't read `op.message.text` directly.85- **Agent I needs Yahoo cookies**. The LINE Android app loads the WebView with whatever Yahoo session the user already has; programmatic use requires you to obtain those cookies yourself (Yahoo Japan login → extract cookies). linejs ships the call shape, not the auth.86- **Device type matters for feature gating**. `DESKTOPWIN` lacks several APIs that `ANDROID` has (e.g. `getContactsV3`, `getContactCalendarEvents`). For pure bot work `DESKTOPWIN` is usually enough; if a method returns `API method not capable`, switch device type or use a legacy fallback.8788## Bugs / issues8990Report to **https://github.com/evex-dev/linejs/issues**. Useful context to include:9192- linejs version (`deno info jsr:@evex/linejs` shows resolved version)93- device type and LINE app version you're targeting94- minimal repro: the smallest snippet that reproduces, plus the error from `Request internal failed: status=… headers=… body=<…>` (v2.7+ includes the HTTP status / headers in the thrown message — very helpful for triage)95- if QR login–related: which step (`createSession`, `createQrCodeForSecure`, `checkQrCodeVerified`, `qrCodeLoginV2ForSecure`) and the `[login] ForSecure: maxCount=… intervalSec=… nonce=…` debug line if it printed.9697CHRLINE-Patch-compatible behaviour reports (e.g. "this RPC works in CHRLINE but not linejs") are also useful — name the method and we'll port it.9899## What this skill deliberately does NOT contain100101- Frozen API signatures — they change. Read the source.102- A list of every wrapped method — there are 130+, see `packages/linejs/base/service/` for the live set.103- Reverse-engineering details for LINE Android — Join Our Discord.