@rikaidev/yomi 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +81 -0
  3. package/PRIVACY.md +39 -0
  4. package/README.md +659 -0
  5. package/dist/auth/credential-store.js +395 -0
  6. package/dist/auth/keychain.js +256 -0
  7. package/dist/cli/login.js +142 -0
  8. package/dist/line/auth/protocol/e2ee-bootstrap.js +87 -0
  9. package/dist/line/auth/protocol/index.js +3 -0
  10. package/dist/line/auth/protocol/login-metadata.js +159 -0
  11. package/dist/line/auth/protocol/pwless-login-flow.js +83 -0
  12. package/dist/line/auth/pwless/index.js +163 -0
  13. package/dist/line/auth/pwless/steps.js +213 -0
  14. package/dist/line/client/auth-service/client.js +240 -0
  15. package/dist/line/client/e2ee-service/client.js +69 -0
  16. package/dist/line/client/e2ee-service/requests.js +92 -0
  17. package/dist/line/client/e2ee-service/shared-key-log.js +24 -0
  18. package/dist/line/client/e2ee-service/shared-key-payload.js +35 -0
  19. package/dist/line/client/index.js +213 -0
  20. package/dist/line/client/obs-media-client.js +140 -0
  21. package/dist/line/client/parsers.js +69 -0
  22. package/dist/line/client/shop-service/client.js +63 -0
  23. package/dist/line/client/sticker-meta.js +46 -0
  24. package/dist/line/client/sync-service/client.js +317 -0
  25. package/dist/line/client/talk-service/chat-query.js +122 -0
  26. package/dist/line/client/talk-service/command-client.js +242 -0
  27. package/dist/line/client/talk-service/contact-query.js +77 -0
  28. package/dist/line/client/talk-service/message-box-query.js +137 -0
  29. package/dist/line/client/talk-service/message-id-query.js +241 -0
  30. package/dist/line/client/talk-service/query-client.js +219 -0
  31. package/dist/line/client/talk-service/requests.js +383 -0
  32. package/dist/line/client/transport.js +138 -0
  33. package/dist/line/core/auth-session-logout.js +55 -0
  34. package/dist/line/core/auth-session-runtime.js +231 -0
  35. package/dist/line/core/auth-session-service.js +85 -0
  36. package/dist/line/core/chat-runtime-service.js +343 -0
  37. package/dist/line/core/client-runtime.js +17 -0
  38. package/dist/line/core/config.js +67 -0
  39. package/dist/line/core/constants.js +56 -0
  40. package/dist/line/core/contact-query-service.js +20 -0
  41. package/dist/line/core/directory-runtime-service.js +83 -0
  42. package/dist/line/core/e2ee/bootstrap/keychain.js +41 -0
  43. package/dist/line/core/e2ee/crypto/crypto-primitives.js +98 -0
  44. package/dist/line/core/e2ee/index.js +6 -0
  45. package/dist/line/core/e2ee/keys/decrypt.js +110 -0
  46. package/dist/line/core/e2ee/keys/encrypt.js +117 -0
  47. package/dist/line/core/e2ee/keys/group-key.js +260 -0
  48. package/dist/line/core/e2ee/keys/key-manager.js +231 -0
  49. package/dist/line/core/e2ee/keys/key-payload.js +130 -0
  50. package/dist/line/core/e2ee/keys/key-types.js +1 -0
  51. package/dist/line/core/e2ee/keys/message-crypto.js +146 -0
  52. package/dist/line/core/e2ee/keys/message-decrypt.js +390 -0
  53. package/dist/line/core/e2ee/keys/peer-key.js +191 -0
  54. package/dist/line/core/e2ee/recovery/bootstrap-keys.js +161 -0
  55. package/dist/line/core/e2ee/types.js +4 -0
  56. package/dist/line/core/group-query-service.js +20 -0
  57. package/dist/line/core/media-decrypt.js +78 -0
  58. package/dist/line/core/media-encrypt.js +97 -0
  59. package/dist/line/core/mention.js +89 -0
  60. package/dist/line/core/message-command-service.js +454 -0
  61. package/dist/line/core/message-query-service.js +161 -0
  62. package/dist/line/core/service.js +134 -0
  63. package/dist/line/core/session-state/index.js +243 -0
  64. package/dist/line/core/session-state/persist.js +192 -0
  65. package/dist/line/core/store.js +8 -0
  66. package/dist/line/core/thrift/binary/binary-reader.js +151 -0
  67. package/dist/line/core/thrift/binary/binary-writer.js +157 -0
  68. package/dist/line/core/thrift/binary/type-codec-dsl.js +42 -0
  69. package/dist/line/core/thrift/compact/compact-reader.js +180 -0
  70. package/dist/line/core/thrift/compact/compact-writer.js +161 -0
  71. package/dist/line/core/thrift/compact/type-codec-dsl.js +66 -0
  72. package/dist/line/core/thrift/fields/builders.js +124 -0
  73. package/dist/line/core/thrift/index.js +12 -0
  74. package/dist/line/core/thrift/message/decoder.js +55 -0
  75. package/dist/line/core/thrift/message/encoder.js +25 -0
  76. package/dist/line/core/thrift/primitives/varint.js +64 -0
  77. package/dist/line/core/thrift/struct/reader.js +172 -0
  78. package/dist/line/core/thrift/struct/writer.js +189 -0
  79. package/dist/line/core/thrift/types.js +23 -0
  80. package/dist/line/core/video-thumbnail.js +104 -0
  81. package/dist/mcp/handlers/contacts.js +166 -0
  82. package/dist/mcp/handlers/conversations.js +193 -0
  83. package/dist/mcp/handlers/groups.js +123 -0
  84. package/dist/mcp/handlers/index.js +15 -0
  85. package/dist/mcp/handlers/login.js +473 -0
  86. package/dist/mcp/handlers/media.js +69 -0
  87. package/dist/mcp/handlers/message-actions.js +62 -0
  88. package/dist/mcp/handlers/scope.js +120 -0
  89. package/dist/mcp/handlers/search.js +196 -0
  90. package/dist/mcp/handlers/send.js +523 -0
  91. package/dist/mcp/handlers/shared.js +84 -0
  92. package/dist/mcp/handlers/stickers.js +87 -0
  93. package/dist/mcp/media.js +228 -0
  94. package/dist/mcp/names.js +133 -0
  95. package/dist/mcp/phi-guard.js +105 -0
  96. package/dist/mcp/policy.js +34 -0
  97. package/dist/mcp/server.js +301 -0
  98. package/dist/mcp/tools.js +761 -0
  99. package/dist/mcp/ui/capability.js +26 -0
  100. package/dist/mcp/ui/login-app-protocol.js +118 -0
  101. package/dist/mcp/ui/login-app-styles.js +142 -0
  102. package/dist/mcp/ui/login-app-view.js +361 -0
  103. package/dist/mcp/ui/login-app.js +72 -0
  104. package/dist/mcp/ui/login-structured-content.js +1 -0
  105. package/dist/mcp/ui/resource.js +35 -0
  106. package/dist/mcp/ui/tools-with-ui.js +40 -0
  107. package/dist/search/bigram.js +86 -0
  108. package/dist/search/capture.js +140 -0
  109. package/dist/search/collector.js +176 -0
  110. package/dist/search/default-embedder.js +101 -0
  111. package/dist/search/embedder.js +13 -0
  112. package/dist/search/scope.js +142 -0
  113. package/dist/search/sqlite.js +141 -0
  114. package/dist/search/store.js +469 -0
  115. package/dist/util/data-dir.js +55 -0
  116. package/dist/util/log.js +86 -0
  117. package/dist/version.js +7 -0
  118. package/package.json +63 -0
  119. package/run.mjs +71 -0
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Normalize bigint-like thrift scalars into numbers when possible.
3
+ *
4
+ * @param value - Raw thrift scalar.
5
+ * @returns Parsed number or original fallback.
6
+ */
7
+ function normalizeNumericValue(value) {
8
+ if (typeof value === 'bigint') {
9
+ return Number(value);
10
+ }
11
+ return value || null;
12
+ }
13
+ /**
14
+ * Extract profile fields from one raw contact struct.
15
+ *
16
+ * @param contact - Raw contact thrift struct.
17
+ * @returns Normalized profile fields.
18
+ */
19
+ function mapContactProfileFields(contact) {
20
+ return {
21
+ displayName: contact[22] || null,
22
+ phoneticName: contact[23] || null,
23
+ pictureStatus: contact[24] || null,
24
+ thumbnailUrl: contact[25] || null,
25
+ statusMessage: contact[26] || null,
26
+ displayNameOverridden: contact[27] || null,
27
+ picturePath: contact[37] || null,
28
+ statusMessageContentMetadata: contact[43] || {},
29
+ profileId: contact[49] || null,
30
+ };
31
+ }
32
+ /**
33
+ * Extract capability flags from one raw contact struct.
34
+ *
35
+ * @param contact - Raw contact thrift struct.
36
+ * @returns Capability flags.
37
+ */
38
+ function mapContactCapabilityFields(contact) {
39
+ return {
40
+ capableVoiceCall: Boolean(contact[31]),
41
+ capableVideoCall: Boolean(contact[32]),
42
+ };
43
+ }
44
+ /**
45
+ * Map one raw contact thrift struct into the app shape.
46
+ *
47
+ * @param contact - Raw contact thrift struct.
48
+ * @returns Normalized contact or null.
49
+ */
50
+ export function mapContactStruct(contact) {
51
+ if (!contact || typeof contact !== 'object') {
52
+ return null;
53
+ }
54
+ return {
55
+ mid: contact[1] || null,
56
+ createdTime: normalizeNumericValue(contact[2]),
57
+ type: contact[10] ?? null,
58
+ status: contact[11] ?? null,
59
+ relation: contact[21] ?? null,
60
+ favoriteTime: normalizeNumericValue(contact[28]),
61
+ ...mapContactCapabilityFields(contact),
62
+ ...mapContactProfileFields(contact),
63
+ };
64
+ }
65
+ /**
66
+ * Map a contact list, dropping invalid entries.
67
+ *
68
+ * @param contacts - Raw contact list.
69
+ * @returns Normalized contact list.
70
+ */
71
+ export function mapContactList(contacts) {
72
+ return Array.isArray(contacts)
73
+ ? contacts
74
+ .map((contact) => mapContactStruct(contact))
75
+ .filter(Boolean)
76
+ : [];
77
+ }
@@ -0,0 +1,137 @@
1
+ import { createCliLogger } from '../../../util/log.js';
2
+ import { parseMessages } from '../parsers.js';
3
+ const lineLog = createCliLogger('LINE');
4
+ /**
5
+ * Normalize bigint-like thrift scalars into numbers when possible.
6
+ *
7
+ * @param value - Raw thrift scalar.
8
+ * @returns Parsed number or original fallback.
9
+ */
10
+ function normalizeNumericValue(value) {
11
+ if (typeof value === 'bigint') {
12
+ return Number(value);
13
+ }
14
+ return value || null;
15
+ }
16
+ /**
17
+ * Map one last-delivered-message cursor into the normalized shape.
18
+ *
19
+ * @param cursor - Raw cursor payload.
20
+ * @returns Normalized cursor or null.
21
+ */
22
+ function mapLastDeliveredMessageId(cursor) {
23
+ if (!cursor || typeof cursor !== 'object') {
24
+ return null;
25
+ }
26
+ return {
27
+ deliveredTime: normalizeNumericValue(cursor[1]),
28
+ messageId: cursor[2] != null ? String(cursor[2]) : null,
29
+ };
30
+ }
31
+ /**
32
+ * Build the request field array for getMessageBoxes.
33
+ *
34
+ * @param options - Listing options.
35
+ * @returns Thrift request field tuples.
36
+ */
37
+ export function buildMessageBoxRequest(options = {}) {
38
+ const request = [];
39
+ if (options.minChatId) {
40
+ request.push([11, 1, options.minChatId]);
41
+ }
42
+ if (options.maxChatId) {
43
+ request.push([11, 2, options.maxChatId]);
44
+ }
45
+ if (options.activeOnly != null) {
46
+ request.push([2, 3, options.activeOnly]);
47
+ }
48
+ if (options.messageBoxCountLimit != null) {
49
+ request.push([8, 4, options.messageBoxCountLimit]);
50
+ }
51
+ if (options.withUnreadCount != null) {
52
+ request.push([2, 5, options.withUnreadCount]);
53
+ }
54
+ if (options.lastMessagesPerMessageBoxCount != null) {
55
+ request.push([8, 6, options.lastMessagesPerMessageBoxCount]);
56
+ }
57
+ if (options.unreadOnly != null) {
58
+ request.push([2, 7, options.unreadOnly]);
59
+ }
60
+ return request;
61
+ }
62
+ /**
63
+ * Build the end-message cursor field list for previous-message requests.
64
+ *
65
+ * @param request - Previous-message request.
66
+ * @returns Thrift field tuples.
67
+ */
68
+ export function buildEndMessageIdFields(request) {
69
+ const endMessageIdFields = [];
70
+ if (request?.endMessageId?.deliveredTime != null) {
71
+ endMessageIdFields.push([10, 1, request.endMessageId.deliveredTime]);
72
+ }
73
+ if (request?.endMessageId?.messageId != null) {
74
+ endMessageIdFields.push([10, 2, request.endMessageId.messageId]);
75
+ }
76
+ return endMessageIdFields;
77
+ }
78
+ /**
79
+ * Map one raw message-box thrift struct into the app shape.
80
+ *
81
+ * @param box - Raw message-box thrift struct.
82
+ * @returns Normalized message-box or null.
83
+ */
84
+ function mapMessageBoxStruct(box) {
85
+ if (!box || typeof box !== 'object') {
86
+ return null;
87
+ }
88
+ return {
89
+ id: box[1] || null,
90
+ midType: box[2] ?? null,
91
+ lastDeliveredMessageId: mapLastDeliveredMessageId(box[4]),
92
+ unreadCount: normalizeNumericValue(box[6]) || 0,
93
+ lastMessages: Array.isArray(box[7]) ? parseMessages(box[7]) : [],
94
+ };
95
+ }
96
+ /**
97
+ * Map a message-box list, dropping invalid entries.
98
+ *
99
+ * @param boxes - Raw message-box list.
100
+ * @returns Normalized message-box list.
101
+ */
102
+ export function mapMessageBoxList(boxes) {
103
+ return Array.isArray(boxes)
104
+ ? boxes
105
+ .map((box) => mapMessageBoxStruct(box))
106
+ .filter(Boolean)
107
+ : [];
108
+ }
109
+ /**
110
+ * Emit one debug log for previous-message responses.
111
+ *
112
+ * @param request - Previous-message request.
113
+ * @param messages - Parsed messages.
114
+ */
115
+ export function logPreviousMessagesResponse(request, messages) {
116
+ lineLog.debug('talk.get_previous_messages.response', {
117
+ box: request.messageBoxId,
118
+ count: request.messagesCount,
119
+ parsed: messages.length,
120
+ });
121
+ }
122
+ /**
123
+ * Emit one debug log for recent-message responses.
124
+ *
125
+ * @param chatId - Chat MID.
126
+ * @param count - Requested message count.
127
+ * @param raw - Raw response payload.
128
+ * @param messages - Parsed messages.
129
+ */
130
+ export function logRecentMessagesResponse(chatId, count, raw, messages) {
131
+ lineLog.debug('talk.get_recent_messages.response', {
132
+ chat: chatId,
133
+ count,
134
+ parsed: messages.length,
135
+ raw_type: Array.isArray(raw) ? 'array' : typeof raw,
136
+ });
137
+ }
@@ -0,0 +1,241 @@
1
+ import { Buffer } from 'node:buffer';
2
+ import https from 'node:https';
3
+ import { LINE_APP_CONFIG } from '../../core/config.js';
4
+ import { parseMessages } from '../parsers.js';
5
+ const writeI32 = (value) => {
6
+ const buffer = Buffer.alloc(4);
7
+ buffer.writeUInt32BE(value >>> 0);
8
+ return buffer;
9
+ };
10
+ const writeI16 = (value) => {
11
+ const buffer = Buffer.alloc(2);
12
+ buffer.writeInt16BE(value);
13
+ return buffer;
14
+ };
15
+ const writeI64 = (value) => {
16
+ const buffer = Buffer.alloc(8);
17
+ buffer.writeBigInt64BE(BigInt(value));
18
+ return buffer;
19
+ };
20
+ const writeString = (value) => {
21
+ const body = Buffer.from(value, 'utf8');
22
+ return Buffer.concat([writeI32(body.length), body]);
23
+ };
24
+ const writeFieldHeader = (type, id) => {
25
+ return Buffer.concat([Buffer.from([type]), writeI16(id)]);
26
+ };
27
+ let writeValue;
28
+ const writeStruct = (fields) => {
29
+ return Buffer.concat([
30
+ ...fields.map(([type, id, value]) => Buffer.concat([writeFieldHeader(type, id), writeValue(type, value)])),
31
+ Buffer.from([0]),
32
+ ]);
33
+ };
34
+ writeValue = (type, value) => {
35
+ if (type === 10) {
36
+ return writeI64(value);
37
+ }
38
+ if (type === 11) {
39
+ return writeString(String(value));
40
+ }
41
+ if (type === 12) {
42
+ return writeStruct(value);
43
+ }
44
+ if (type === 15) {
45
+ const [elementType, items] = value;
46
+ return Buffer.concat([
47
+ Buffer.from([elementType]),
48
+ writeI32(items.length),
49
+ ...items.map((item) => writeValue(elementType, item)),
50
+ ]);
51
+ }
52
+ throw new Error(`Unsupported TBinary write type: ${type}`);
53
+ };
54
+ const encodeBinaryCallMessage = (method, seqId, fields) => {
55
+ return Buffer.concat([
56
+ writeI32(0x80010001),
57
+ writeString(method),
58
+ writeI32(seqId),
59
+ writeStruct(fields),
60
+ ]);
61
+ };
62
+ const readI32 = (buffer, state) => {
63
+ const value = buffer.readInt32BE(state.position);
64
+ state.position += 4;
65
+ return value;
66
+ };
67
+ const readU32 = (buffer, state) => {
68
+ const value = buffer.readUInt32BE(state.position);
69
+ state.position += 4;
70
+ return value;
71
+ };
72
+ const readI16 = (buffer, state) => {
73
+ const value = buffer.readInt16BE(state.position);
74
+ state.position += 2;
75
+ return value;
76
+ };
77
+ const readI64 = (buffer, state) => {
78
+ const value = buffer.readBigInt64BE(state.position);
79
+ state.position += 8;
80
+ return value.toString();
81
+ };
82
+ const readString = (buffer, state) => {
83
+ const length = readI32(buffer, state);
84
+ const value = buffer
85
+ .subarray(state.position, state.position + length)
86
+ .toString('utf8');
87
+ state.position += length;
88
+ return value;
89
+ };
90
+ let readValue;
91
+ const readStruct = (buffer, state) => {
92
+ const fields = {};
93
+ while (state.position < buffer.length) {
94
+ const type = buffer[state.position];
95
+ state.position += 1;
96
+ if (type === 0) {
97
+ break;
98
+ }
99
+ const id = readI16(buffer, state);
100
+ fields[id] = readValue(buffer, state, type);
101
+ }
102
+ return fields;
103
+ };
104
+ readValue = (buffer, state, type) => {
105
+ if (type === 2) {
106
+ const value = Boolean(buffer[state.position]);
107
+ state.position += 1;
108
+ return value;
109
+ }
110
+ if (type === 3) {
111
+ const value = buffer[state.position];
112
+ state.position += 1;
113
+ return value;
114
+ }
115
+ if (type === 8) {
116
+ return readI32(buffer, state);
117
+ }
118
+ if (type === 10) {
119
+ return readI64(buffer, state);
120
+ }
121
+ if (type === 11) {
122
+ return readString(buffer, state);
123
+ }
124
+ if (type === 12) {
125
+ return readStruct(buffer, state);
126
+ }
127
+ if (type === 13) {
128
+ const keyType = buffer[state.position];
129
+ const valueType = buffer[state.position + 1];
130
+ state.position += 2;
131
+ const length = readI32(buffer, state);
132
+ const entries = {};
133
+ for (let index = 0; index < length; index += 1) {
134
+ entries[String(readValue(buffer, state, keyType))] = readValue(buffer, state, valueType);
135
+ }
136
+ return entries;
137
+ }
138
+ if (type === 15) {
139
+ const elementType = buffer[state.position];
140
+ state.position += 1;
141
+ const length = readI32(buffer, state);
142
+ const items = [];
143
+ for (let index = 0; index < length; index += 1) {
144
+ items.push(readValue(buffer, state, elementType));
145
+ }
146
+ return items;
147
+ }
148
+ throw new Error(`Unsupported TBinary read type: ${type}`);
149
+ };
150
+ const decodeBinaryResponseMessage = (buffer) => {
151
+ const state = { position: 0 };
152
+ readU32(buffer, state);
153
+ const method = readString(buffer, state);
154
+ const seqId = readI32(buffer, state);
155
+ const fields = readStruct(buffer, state);
156
+ return { fields, method, seqId };
157
+ };
158
+ const postBinary = (host, path, body, token) => {
159
+ return new Promise((resolve, reject) => {
160
+ const request = https.request({
161
+ headers: {
162
+ accept: 'application/x-thrift',
163
+ 'Content-Length': body.length,
164
+ 'Content-Type': 'application/x-thrift',
165
+ 'User-Agent': LINE_APP_CONFIG.userAgent,
166
+ 'X-Line-Access': token,
167
+ 'X-Line-Application': LINE_APP_CONFIG.lineApp,
168
+ 'x-lal': 'ja_JP',
169
+ 'x-lpv': '1',
170
+ },
171
+ hostname: host,
172
+ method: 'POST',
173
+ path,
174
+ port: 443,
175
+ timeout: 30000,
176
+ }, (response) => {
177
+ const chunks = [];
178
+ response.on('data', (chunk) => chunks.push(chunk));
179
+ response.on('end', () => {
180
+ const nextToken = response.headers['x-line-next-access'];
181
+ resolve({
182
+ body: Buffer.concat(chunks),
183
+ nextToken: typeof nextToken === 'string' ? nextToken : undefined,
184
+ statusCode: response.statusCode,
185
+ });
186
+ });
187
+ });
188
+ request.on('error', reject);
189
+ request.on('timeout', () => {
190
+ request.destroy(new Error('timeout'));
191
+ });
192
+ request.write(body);
193
+ request.end();
194
+ });
195
+ };
196
+ const buildMessageIdStruct = (messageId) => {
197
+ return [
198
+ [10, 1, messageId.deliveredTime],
199
+ [10, 2, messageId.messageId],
200
+ ];
201
+ };
202
+ /**
203
+ * Fetch message payloads by MessageBoxV2 ids through the legacy /S3 endpoint.
204
+ *
205
+ * LINE only accepts one messagesByIds request part per call on this route.
206
+ *
207
+ * @param runtime - Active LINE client runtime.
208
+ * @param messageBoxId - Conversation id.
209
+ * @param messageIds - MessageBoxV2 ids to resolve.
210
+ * @returns Parsed LINE messages.
211
+ */
212
+ export async function fetchMessagesByIds(runtime, messageBoxId, messageIds) {
213
+ const messages = [];
214
+ for (const messageId of messageIds) {
215
+ const requestPart = [
216
+ [11, 1, messageBoxId],
217
+ [15, 2, [12, [buildMessageIdStruct(messageId)]]],
218
+ ];
219
+ const body = encodeBinaryCallMessage('getMessagesByIds', runtime.seq++, [
220
+ [15, 2, [12, [requestPart]]],
221
+ ]);
222
+ const response = await postBinary(runtime.host, '/S3', body, runtime.authToken);
223
+ if (response.nextToken) {
224
+ runtime.authToken = response.nextToken;
225
+ runtime.emit?.('tokenRotated', response.nextToken);
226
+ }
227
+ if (!response.body.length) {
228
+ continue;
229
+ }
230
+ const decoded = decodeBinaryResponseMessage(response.body);
231
+ const exception = decoded.fields?.[1];
232
+ if (exception) {
233
+ throw new Error(`getMessagesByIds failed: ${exception[2] || JSON.stringify(exception)}`);
234
+ }
235
+ const rawMessages = decoded.fields?.[0];
236
+ if (Array.isArray(rawMessages)) {
237
+ messages.push(...parseMessages(rawMessages));
238
+ }
239
+ }
240
+ return messages;
241
+ }
@@ -0,0 +1,219 @@
1
+ /**
2
+ * LINE TalkService query capability.
3
+ *
4
+ * Profile, contacts, chats, and message-box retrieval.
5
+ */
6
+ import { parseMessages } from '../parsers.js';
7
+ import { fetchChats } from './chat-query.js';
8
+ import { mapContactList } from './contact-query.js';
9
+ import { buildEndMessageIdFields, buildMessageBoxRequest, logPreviousMessagesResponse, logRecentMessagesResponse, mapMessageBoxList, } from './message-box-query.js';
10
+ import { fetchMessagesByIds } from './message-id-query.js';
11
+ import { buildDownloadMessageContentRequest, buildGetAllChatMidsRequest, buildGetAllContactIdsRequest, buildGetMessageBoxesRequest, buildMidListRequest, buildMidLookupRequest, buildPreviousMessagesRequest, buildRecentMessagesRequest, } from './requests.js';
12
+ /** Sync reason constant matching CHRLINE reference. */
13
+ const SYNC_REASON = 7;
14
+ /** Sync reason used by getPreviousMessagesV2WithRequest for secondary-device history sync. */
15
+ const PREVIOUS_MESSAGES_SYNC_REASON = 4;
16
+ /**
17
+ * Create the TalkService query capability bound to one LINE client runtime.
18
+ *
19
+ * @param runtime - Mutable LINE client runtime.
20
+ * @returns Talk query methods bound to the runtime.
21
+ */
22
+ export function createTalkQueryClient(runtime) {
23
+ return {
24
+ /**
25
+ * Retrieve the authenticated user's profile.
26
+ *
27
+ * @returns The user's profile object.
28
+ */
29
+ async getProfile() {
30
+ const result = await runtime.sendTalk('getProfile', []);
31
+ if (result.fields?.[1]) {
32
+ const err = result.fields[1];
33
+ throw new Error(`getProfile failed: code=${err[1]} ${err[2] || 'Unknown error'}`);
34
+ }
35
+ const profile = result.fields?.[0];
36
+ if (!profile || typeof profile !== 'object') {
37
+ throw new Error('getProfile: no profile data in response');
38
+ }
39
+ runtime.profile = {
40
+ mid: profile[1] || null,
41
+ displayName: profile[20] || null,
42
+ picturePath: profile[22] || null,
43
+ statusMessage: profile[24] || null,
44
+ };
45
+ return runtime.profile;
46
+ },
47
+ /**
48
+ * Retrieve all contact IDs from the server.
49
+ *
50
+ * @returns An array of contact IDs.
51
+ */
52
+ async getAllContactIds() {
53
+ const result = await runtime.sendTalk('getAllContactIds', buildGetAllContactIdsRequest());
54
+ const ids = result.fields?.[0];
55
+ return Array.isArray(ids) ? ids : [];
56
+ },
57
+ /**
58
+ * Retrieve one raw contact by MID.
59
+ *
60
+ * @param mid - Contact MID.
61
+ * @returns Contact object or null.
62
+ */
63
+ async getContact(mid) {
64
+ const result = await runtime.sendTalk('getContact', buildMidLookupRequest(mid));
65
+ return result.fields?.[0] || null;
66
+ },
67
+ /**
68
+ * Retrieve and normalize multiple contacts.
69
+ *
70
+ * @param mids - Contact MIDs.
71
+ * @returns Normalized contact objects.
72
+ */
73
+ async getContacts(mids) {
74
+ const result = await runtime.sendTalk('getContacts', buildMidListRequest(mids));
75
+ return mapContactList(result.fields?.[0]);
76
+ },
77
+ /**
78
+ * Retrieve all member and invited chat MIDs.
79
+ *
80
+ * @returns Member/invited chat MID groups.
81
+ */
82
+ async getAllChatMids() {
83
+ const result = await runtime.sendTalk('getAllChatMids', buildGetAllChatMidsRequest(SYNC_REASON));
84
+ const response = result.fields?.[0];
85
+ return {
86
+ memberChats: response?.[1] || [],
87
+ invitedChats: response?.[2] || [],
88
+ };
89
+ },
90
+ /**
91
+ * Retrieve and normalize chats.
92
+ *
93
+ * @param chatMids - Chat MIDs.
94
+ * @param withMembers - Whether to include members.
95
+ * @returns Normalized chat objects.
96
+ */
97
+ async getChats(chatMids, withMembers = true) {
98
+ return fetchChats(runtime, SYNC_REASON, chatMids, withMembers);
99
+ },
100
+ /**
101
+ * Retrieve one group profile.
102
+ *
103
+ * @param groupId - Group MID.
104
+ * @returns Group object or null.
105
+ */
106
+ async getGroup(groupId) {
107
+ const result = await runtime.sendTalk('getGroup', buildMidLookupRequest(groupId));
108
+ return result.fields?.[0] || null;
109
+ },
110
+ /**
111
+ * Retrieve message-box metadata.
112
+ *
113
+ * @param options - Message-box listing options.
114
+ * @returns Normalized message-box result.
115
+ */
116
+ async getMessageBoxes(options = {}) {
117
+ const request = buildMessageBoxRequest(options);
118
+ const result = await runtime.sendTalk('getMessageBoxes', buildGetMessageBoxesRequest(request, SYNC_REASON));
119
+ const root = result.fields?.[0];
120
+ return {
121
+ messageBoxes: mapMessageBoxList(root?.[1]),
122
+ hasNext: Boolean(root?.[2]),
123
+ };
124
+ },
125
+ /**
126
+ * Retrieve previous messages through one message-box cursor.
127
+ *
128
+ * @param request - Previous-message request payload.
129
+ * @returns Parsed previous messages.
130
+ */
131
+ async getPreviousMessagesV2WithRequest(request) {
132
+ const endMessageIdFields = buildEndMessageIdFields(request);
133
+ const result = await runtime.sendTalk('getPreviousMessagesV2WithRequest', buildPreviousMessagesRequest(request, endMessageIdFields, PREVIOUS_MESSAGES_SYNC_REASON));
134
+ const raw = result.fields?.[0];
135
+ const messages = Array.isArray(raw) ? parseMessages(raw) : [];
136
+ logPreviousMessagesResponse(request, messages);
137
+ return messages;
138
+ },
139
+ /**
140
+ * Retrieve previous MessageBoxV2 ids for one conversation.
141
+ *
142
+ * @param request - Previous-message request payload.
143
+ * @returns Message id cursors and the server pagination flag.
144
+ */
145
+ async getPreviousMessageIds(request) {
146
+ const fields = [[11, 1, request.messageBoxId]];
147
+ const endMessageIdFields = buildEndMessageIdFields(request);
148
+ if (endMessageIdFields.length > 0) {
149
+ fields.push([12, 2, endMessageIdFields]);
150
+ }
151
+ fields.push([8, 4, request.messagesCount]);
152
+ const result = await runtime.sendTalk('getPreviousMessageIds', [
153
+ [12, 2, fields],
154
+ [8, 3, PREVIOUS_MESSAGES_SYNC_REASON],
155
+ ]);
156
+ const response = result.fields?.[0];
157
+ const ids = Array.isArray(response?.[1])
158
+ ? response[1]
159
+ .map((item) => ({
160
+ deliveredTime: Number(item?.[1] || 0),
161
+ messageId: String(item?.[2] || ''),
162
+ }))
163
+ .filter((item) => item.deliveredTime && item.messageId)
164
+ : [];
165
+ return {
166
+ ids,
167
+ hasPrevious: Boolean(response?.[2]),
168
+ };
169
+ },
170
+ /**
171
+ * Retrieve messages by MessageBoxV2 ids.
172
+ *
173
+ * @param messageBoxId - Conversation id.
174
+ * @param messageIds - MessageBoxV2 ids to resolve.
175
+ * @returns Parsed messages.
176
+ */
177
+ async getMessagesByIds(messageBoxId, messageIds) {
178
+ return fetchMessagesByIds(runtime, messageBoxId, messageIds);
179
+ },
180
+ /**
181
+ * Retrieve recent messages from one chat.
182
+ *
183
+ * @param chatId - Chat MID.
184
+ * @param count - Maximum number of messages.
185
+ * @returns Parsed message list.
186
+ */
187
+ async getRecentMessages(chatId, count = 50) {
188
+ const result = await runtime.sendTalk('getRecentMessagesV2', buildRecentMessagesRequest(chatId, count));
189
+ const raw = result.fields?.[0];
190
+ const messages = raw ? parseMessages(raw) : [];
191
+ logRecentMessagesResponse(chatId, count, raw, messages);
192
+ return messages;
193
+ },
194
+ /**
195
+ * Retrieve binary media content for one LINE message.
196
+ *
197
+ * @param messageId - LINE message identifier.
198
+ * @param requestId - Client request identifier.
199
+ * @returns Message content bytes.
200
+ */
201
+ async downloadMessageContent(messageId, requestId = `yomi-${Date.now()}`) {
202
+ const result = await runtime.sendTalk('downloadMessageContent', buildDownloadMessageContentRequest(requestId, messageId));
203
+ const content = result.fields?.[0];
204
+ return Buffer.isBuffer(content) ? content : Buffer.from(content || '');
205
+ },
206
+ /**
207
+ * Retrieve binary preview media content for one LINE message.
208
+ *
209
+ * @param messageId - LINE message identifier.
210
+ * @param requestId - Client request identifier.
211
+ * @returns Message preview bytes.
212
+ */
213
+ async downloadMessageContentPreview(messageId, requestId = `yomi-${Date.now()}`) {
214
+ const result = await runtime.sendTalk('downloadMessageContentPreview', buildDownloadMessageContentRequest(requestId, messageId));
215
+ const content = result.fields?.[0];
216
+ return Buffer.isBuffer(content) ? content : Buffer.from(content || '');
217
+ },
218
+ };
219
+ }