@zhin.js/adapter-icqq 1.1.0 → 1.1.3

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 (85) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/README.md +15 -6
  3. package/adapters/{icqq.js → icqq/index.js} +6 -6
  4. package/adapters/{icqq.ts → icqq/index.ts} +10 -10
  5. package/agents/icqq/agent.json +20 -0
  6. package/agents/icqq/boundaries.md +3 -0
  7. package/agents/icqq/conventions.md +3 -0
  8. package/agents/icqq/skills/icqq-directory/SKILL.md +20 -0
  9. package/{tools/friend_list.js → agents/icqq/skills/icqq-directory/tools/friend_list/index.js} +2 -1
  10. package/{tools/friend_list.ts → agents/icqq/skills/icqq-directory/tools/friend_list/index.ts} +2 -1
  11. package/{tools/group_list.js → agents/icqq/skills/icqq-directory/tools/group_list/index.js} +2 -1
  12. package/{tools/group_list.ts → agents/icqq/skills/icqq-directory/tools/group_list/index.ts} +2 -1
  13. package/{tools/group_member_info.js → agents/icqq/skills/icqq-directory/tools/group_member_info/index.js} +3 -1
  14. package/{tools/group_member_info.ts → agents/icqq/skills/icqq-directory/tools/group_member_info/index.ts} +3 -1
  15. package/{tools/user_info.js → agents/icqq/skills/icqq-directory/tools/user_info/index.js} +1 -0
  16. package/{tools/user_info.ts → agents/icqq/skills/icqq-directory/tools/user_info/index.ts} +1 -0
  17. package/agents/icqq/skills/icqq-group-admin/SKILL.md +25 -0
  18. package/{tools/announce.js → agents/icqq/skills/icqq-group-admin/tools/announce/index.js} +2 -0
  19. package/{tools/announce.ts → agents/icqq/skills/icqq-group-admin/tools/announce/index.ts} +2 -0
  20. package/{tools/essence.js → agents/icqq/skills/icqq-group-admin/tools/essence/index.js} +3 -1
  21. package/{tools/essence.ts → agents/icqq/skills/icqq-group-admin/tools/essence/index.ts} +3 -1
  22. package/{tools/group_invite.js → agents/icqq/skills/icqq-group-admin/tools/group_invite/index.js} +1 -1
  23. package/{tools/group_invite.ts → agents/icqq/skills/icqq-group-admin/tools/group_invite/index.ts} +1 -1
  24. package/{tools/group_kick.js → agents/icqq/skills/icqq-group-admin/tools/group_kick/index.js} +4 -3
  25. package/{tools/group_kick.ts → agents/icqq/skills/icqq-group-admin/tools/group_kick/index.ts} +4 -3
  26. package/{tools/set_anonymous.js → agents/icqq/skills/icqq-group-admin/tools/set_anonymous/index.js} +3 -1
  27. package/{tools/set_anonymous.ts → agents/icqq/skills/icqq-group-admin/tools/set_anonymous/index.ts} +3 -1
  28. package/{tools/set_mute.js → agents/icqq/skills/icqq-group-admin/tools/set_mute/index.js} +3 -2
  29. package/{tools/set_mute.ts → agents/icqq/skills/icqq-group-admin/tools/set_mute/index.ts} +3 -2
  30. package/{tools/set_title.js → agents/icqq/skills/icqq-group-admin/tools/set_title/index.js} +3 -2
  31. package/{tools/set_title.ts → agents/icqq/skills/icqq-group-admin/tools/set_title/index.ts} +3 -2
  32. package/agents/icqq/skills/icqq-group-state/SKILL.md +17 -0
  33. package/{tools/group_files.js → agents/icqq/skills/icqq-group-state/tools/group_files/index.js} +3 -1
  34. package/{tools/group_files.ts → agents/icqq/skills/icqq-group-state/tools/group_files/index.ts} +3 -1
  35. package/{tools/list_muted.js → agents/icqq/skills/icqq-group-state/tools/list_muted/index.js} +2 -1
  36. package/{tools/list_muted.ts → agents/icqq/skills/icqq-group-state/tools/list_muted/index.ts} +2 -1
  37. package/agents/icqq/skills/icqq-interaction/SKILL.md +19 -0
  38. package/{tools/group_sign.js → agents/icqq/skills/icqq-interaction/tools/group_sign/index.js} +2 -1
  39. package/{tools/group_sign.ts → agents/icqq/skills/icqq-interaction/tools/group_sign/index.ts} +2 -1
  40. package/{tools/poke.js → agents/icqq/skills/icqq-interaction/tools/poke/index.js} +2 -1
  41. package/{tools/poke.ts → agents/icqq/skills/icqq-interaction/tools/poke/index.ts} +2 -1
  42. package/{tools/send_user_like.js → agents/icqq/skills/icqq-interaction/tools/send_user_like/index.js} +4 -3
  43. package/{tools/send_user_like.ts → agents/icqq/skills/icqq-interaction/tools/send_user_like/index.ts} +3 -3
  44. package/agents/icqq/system.md +3 -0
  45. package/commands/{endpoint/add/[[id]].js → icqq/endpoint/add/[[id]]/index.js} +1 -1
  46. package/commands/icqq/endpoint/add/[[id]]/index.ts +3 -0
  47. package/{lib/icqq-endpoint-commands.js → commands/icqq/endpoint/definition.js} +10 -5
  48. package/{src/icqq-endpoint-commands.ts → commands/icqq/endpoint/definition.ts} +9 -5
  49. package/commands/{endpoint/list.js → icqq/endpoint/list/index.js} +1 -1
  50. package/commands/icqq/endpoint/list/index.ts +3 -0
  51. package/commands/{endpoint/remove/[id].js → icqq/endpoint/remove/[id]/index.js} +1 -1
  52. package/commands/icqq/endpoint/remove/[id]/index.ts +3 -0
  53. package/lib/content-resolver.d.ts +10 -0
  54. package/lib/content-resolver.js +98 -0
  55. package/lib/cq-message.d.ts +1 -1
  56. package/lib/cq-message.js +1 -1
  57. package/lib/endpoint.d.ts +1 -2
  58. package/lib/endpoint.js +34 -77
  59. package/lib/icqq-inbound.js +40 -0
  60. package/lib/icqq-runtime-state.js +1 -1
  61. package/lib/index.d.ts +1 -1
  62. package/lib/outbound-media.d.ts +1 -1
  63. package/lib/outbound-media.js +1 -1
  64. package/lib/protocol.d.ts +4 -22
  65. package/lib/protocol.js +52 -55
  66. package/lib/types.d.ts +0 -6
  67. package/package.json +29 -23
  68. package/plugin.js +1 -1
  69. package/prompt-sections/platform/index.js +16 -0
  70. package/{agent/prompt-sections/platform.ts → prompt-sections/platform/index.ts} +3 -4
  71. package/schema.json +7 -1
  72. package/src/content-resolver.ts +131 -0
  73. package/src/cq-message.ts +1 -1
  74. package/src/endpoint.ts +43 -97
  75. package/src/icqq-inbound.ts +39 -0
  76. package/src/icqq-runtime-state.ts +1 -1
  77. package/src/index.ts +1 -1
  78. package/src/outbound-media.ts +2 -2
  79. package/src/protocol.ts +56 -80
  80. package/src/types.ts +0 -7
  81. package/commands/endpoint/add/[[id]].ts +0 -3
  82. package/commands/endpoint/list.ts +0 -3
  83. package/commands/endpoint/remove/[id].ts +0 -3
  84. package/lib/icqq-endpoint-commands.d.ts +0 -1
  85. /package/{agent/PERMITS.md → PERMITS.md} +0 -0
package/src/endpoint.ts CHANGED
@@ -5,29 +5,25 @@ import {
5
5
  type FileElem,
6
6
  type PttElem,
7
7
  type Sendable,
8
+ type ShareElem,
8
9
  type VideoElem,
9
10
  } from '@icqqjs/icqq';
10
11
  import { inspect } from 'node:util';
11
12
  import { AsyncLocalStorage } from 'node:async_hooks';
12
- import { Endpoint } from 'zhin.js/adapter';
13
- import type {
14
- EndpointControl,
15
- EndpointManagement,
16
- EndpointPendingRequest,
17
- EndpointSendRequest,
13
+ import {
14
+ Endpoint,
15
+ type EndpointControl,
16
+ type EndpointManagement,
17
+ type EndpointPendingRequest,
18
+ type EndpointSendRequest,
18
19
  } from 'zhin.js/adapter';
19
- import type { EndpointContentPort, EndpointContentResolveContext } from '@zhin.js/adapter';
20
+ import type { EndpointContentPort } from '@zhin.js/adapter';
20
21
  import { receiveOneBotLikeSideEvent, SystemEvent, toCanonicalSegments, type LoginAssist } from '@zhin.js/core';
21
- import type {
22
- ConversationMessage,
23
- ConversationReference,
24
- ForwardEntry,
25
- MessageRef,
26
- Segment,
27
- } from '@zhin.js/im-contract';
22
+ import type { MessageRef } from '@zhin.js/im-contract';
28
23
  import { formatCompact, getAdapterLogger, truncatePreview } from '@zhin.js/logger';
29
24
  import type { CapabilityId } from 'zhin.js';
30
25
  import { runIcqqLoginAssistStep } from './icqq-login-assist.js';
26
+ import { IcqqContentResolver } from './content-resolver.js';
31
27
  import {
32
28
  InboundMessageDeduper,
33
29
  findIcqqNestedMessageSource,
@@ -61,7 +57,6 @@ import {
61
57
  type ResolvedIcqqConfig,
62
58
  } from './protocol.js';
63
59
  import type { SystemMessage as IcqqSystemMessage } from './types.js';
64
- import { normalizeForwardMsgResponse } from './forward-msg.js';
65
60
 
66
61
  export interface IcqqEndpointOptions {
67
62
  readonly id: CapabilityId;
@@ -115,87 +110,8 @@ export class IcqqEndpoint extends Endpoint<Client> {
115
110
  #heldInbound: IcqqInboundMessage[] = [];
116
111
  readonly #inflightInbound = new Set<Promise<void>>();
117
112
  readonly #inboundOwner = new AsyncLocalStorage<symbol>();
118
- readonly #messageContent = new Map<string, ConversationMessage>();
119
-
120
- readonly content: EndpointContentPort = Object.freeze({
121
- resolve: async (reference: ConversationReference, context: EndpointContentResolveContext) => {
122
- context.signal.throwIfAborted();
123
- if (reference.kind === 'message') {
124
- const cached = this.#messageContent.get(reference.message.id);
125
- return cached
126
- ? Object.freeze({ status: 'resolved' as const, reference, value: cached })
127
- : Object.freeze({ status: 'not_found' as const, code: 'icqq_message_not_observed' });
128
- }
129
- if (reference.kind === 'forward') {
130
- try {
131
- const entries = await this.#resolveForwardEntries(
132
- reference.forwardId,
133
- context,
134
- { remainingEntries: context.maxEntries, path: new Set<string>() },
135
- 0,
136
- );
137
- if (entries.length === 0) return Object.freeze({ status: 'not_found' as const, code: 'icqq_forward_not_found' });
138
- return Object.freeze({ status: 'resolved' as const, reference, value: Object.freeze(entries) });
139
- } catch (error) {
140
- if (context.signal.aborted) throw error;
141
- return Object.freeze({
142
- status: 'failed' as const,
143
- code: 'icqq_forward_fetch_failed',
144
- message: error instanceof Error ? error.message : String(error),
145
- });
146
- }
147
- }
148
- if (reference.media.kind === 'url' || reference.media.kind === 'base64' || reference.media.kind === 'path') {
149
- return Object.freeze({ status: 'resolved' as const, reference, value: reference.media });
150
- }
151
- return Object.freeze({ status: 'unsupported' as const, code: 'icqq_media_reference_unsupported' });
152
- },
153
- });
154
-
155
- async #resolveForwardEntries(
156
- forwardId: string,
157
- context: EndpointContentResolveContext,
158
- state: { remainingEntries: number; readonly path: Set<string> },
159
- depth: number,
160
- ): Promise<readonly ForwardEntry[]> {
161
- context.signal.throwIfAborted();
162
- if (state.remainingEntries <= 0 || state.path.has(forwardId)) return Object.freeze([]);
163
- state.path.add(forwardId);
164
- try {
165
- // ICQQ does not expose a cancellable getForwardMsg API. Keep the caller's
166
- // generation lease until the native operation actually settles, then honor
167
- // cancellation before publishing any resolved content.
168
- const raw = await this.client.getForwardMsg(forwardId);
169
- context.signal.throwIfAborted();
170
- const entries = normalizeForwardMsgResponse(raw).slice(0, state.remainingEntries);
171
- state.remainingEntries -= entries.length;
172
- const expanded: ForwardEntry[] = [];
173
- for (const entry of entries) {
174
- context.signal.throwIfAborted();
175
- const segments: Segment[] = [];
176
- for (const segment of entry.segments) {
177
- if (segment.type !== 'forward' || depth >= context.maxDepth) {
178
- segments.push(segment);
179
- continue;
180
- }
181
- const nestedId = String((segment.data as { forward_id?: unknown }).forward_id ?? '').trim();
182
- if (!nestedId || state.path.has(nestedId) || state.remainingEntries <= 0) {
183
- segments.push(segment);
184
- continue;
185
- }
186
- const nested = await this.#resolveForwardEntries(nestedId, context, state, depth + 1);
187
- segments.push(Object.freeze({
188
- ...segment,
189
- data: Object.freeze({ ...segment.data, ...(nested.length > 0 ? { entries: nested } : {}) }),
190
- }));
191
- }
192
- expanded.push(Object.freeze({ ...entry, segments: Object.freeze(segments) }));
193
- }
194
- return Object.freeze(expanded);
195
- } finally {
196
- state.path.delete(forwardId);
197
- }
198
- }
113
+ readonly #contentResolver: IcqqContentResolver;
114
+ readonly content: EndpointContentPort;
199
115
 
200
116
  readonly management: EndpointManagement = Object.freeze<EndpointManagement>({
201
117
  listFriends: async () =>
@@ -275,6 +191,8 @@ export class IcqqEndpoint extends Endpoint<Client> {
275
191
  super();
276
192
  const nativeConfig = resolveNativeClientConfig(options.config);
277
193
  this.client = new ManagedIcqqClient(Number(options.config.id), nativeConfig);
194
+ this.#contentResolver = new IcqqContentResolver(this.client);
195
+ this.content = this.#contentResolver.port;
278
196
  this.#logger = getAdapterLogger('icqq', options.config.id);
279
197
  this.#options = options;
280
198
  this.endpointName = options.config.id;
@@ -436,6 +354,27 @@ export class IcqqEndpoint extends Endpoint<Client> {
436
354
  target: ReturnType<typeof icqqOutboundTarget>,
437
355
  message: Sendable,
438
356
  ): Promise<{ message_id?: unknown }> {
357
+ if (isIcqqShareElement(message)) {
358
+ switch (target.kind) {
359
+ case 'private':
360
+ await this.client.pickFriend(target.userId).share(message);
361
+ return {};
362
+ case 'group':
363
+ await this.client.pickGroup(target.groupId).share(message);
364
+ return {};
365
+ case 'temp':
366
+ await this.client.pickMember(target.groupId, target.userId).share(message);
367
+ return {};
368
+ case 'channel': {
369
+ const channel = this.client.pickGuild(target.guildId).channels.get(target.channelId);
370
+ if (!channel) {
371
+ throw new TypeError(`ICQQ guild channel is unavailable: ${target.guildId}/${target.channelId}`);
372
+ }
373
+ await channel.share(message);
374
+ return {};
375
+ }
376
+ }
377
+ }
439
378
  if (isIcqqFileElement(message)) {
440
379
  switch (target.kind) {
441
380
  case 'private': {
@@ -800,7 +739,7 @@ export class IcqqEndpoint extends Endpoint<Client> {
800
739
  const quoteId = resolveIcqqQuoteIdFromEvent(data);
801
740
  const quoted = messageLookupFromIcqqSource(findIcqqNestedMessageSource(data));
802
741
  if (quoteId && quoted) {
803
- this.#messageContent.set(quoteId, Object.freeze({
742
+ this.#contentResolver.remember(quoteId, Object.freeze({
804
743
  ref: Object.freeze({ conversation, id: quoteId }),
805
744
  ...(quoted.sender?.id ? { actor: Object.freeze({
806
745
  id: quoted.sender.id,
@@ -1014,6 +953,13 @@ export class IcqqEndpoint extends Endpoint<Client> {
1014
953
  }
1015
954
  }
1016
955
 
956
+ function isIcqqShareElement(message: Sendable): message is ShareElem {
957
+ return !Array.isArray(message)
958
+ && typeof message === 'object'
959
+ && message !== null
960
+ && message.type === 'share';
961
+ }
962
+
1017
963
  function formatIcqqSendablePreview(message: Sendable): string {
1018
964
  if (typeof message === 'string') return message;
1019
965
  try {
@@ -500,6 +500,45 @@ export function icqqElementsToSegments(
500
500
  });
501
501
  break;
502
502
  }
503
+ case "share": {
504
+ const url = typeof el.url === "string" ? el.url.trim() : "";
505
+ const title = typeof el.title === "string" ? el.title.trim() : "";
506
+ if (!url || !title) break;
507
+ const description = typeof el.summary === "string" && el.summary
508
+ ? el.summary
509
+ : undefined;
510
+ const image = typeof el.image === "string" && el.image ? el.image : undefined;
511
+ const audio = typeof el.audio === "string" && el.audio ? el.audio : undefined;
512
+ const content = typeof el.content === "string" && el.content ? el.content : undefined;
513
+ const rawConfig = el.config && typeof el.config === "object" && !Array.isArray(el.config)
514
+ ? el.config as Record<string, unknown>
515
+ : undefined;
516
+ const appid = Number(rawConfig?.appid);
517
+ const config = Number.isSafeInteger(appid) && appid > 0
518
+ ? {
519
+ appid,
520
+ ...(typeof rawConfig?.appname === "string" && rawConfig.appname
521
+ ? { package: rawConfig.appname }
522
+ : {}),
523
+ ...(typeof rawConfig?.appsign === "string" && rawConfig.appsign
524
+ ? { sign: rawConfig.appsign }
525
+ : {}),
526
+ }
527
+ : undefined;
528
+ out.push({
529
+ type: "share",
530
+ data: {
531
+ url,
532
+ title,
533
+ ...(description ? { description } : {}),
534
+ ...(image ? { image } : {}),
535
+ ...(audio ? { audio } : {}),
536
+ ...(content ? { content } : {}),
537
+ ...(config ? { config } : {}),
538
+ },
539
+ });
540
+ break;
541
+ }
503
542
  default:
504
543
  if (el.text != null && el.text !== "") {
505
544
  out.push({ type: "text", data: { text: String(el.text) } });
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * ICQQ 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
3
- * 由 plugin.ts setup() provide,adapter create 与 `icqq.endpoint` 命令共享(同一 owner generation)。
3
+ * 由 plugin.ts setup() provide,adapter create 与 `icqq endpoint` 命令共享(同一 owner generation)。
4
4
  */
5
5
  import { defineEndpointRuntimeStateToken } from 'zhin.js/adapter';
6
6
 
package/src/index.ts CHANGED
@@ -5,7 +5,7 @@ export {
5
5
  icqqInboundConversation,
6
6
  icqqOutboundTarget,
7
7
  type ActionResult,
8
- type IcqqAdapterConfig,
8
+ type IcqqEndpointConfig,
9
9
  type IcqqInboundMessage,
10
10
  type IcqqWireSegment,
11
11
  type ParsedIcqqSendTarget,
@@ -2,9 +2,9 @@ import * as fs from "node:fs";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
4
  import * as crypto from "node:crypto";
5
- import { isMediaRef } from "@zhin.js/core";
5
+ import { isMediaRef } from '@zhin.js/im-contract';
6
6
  import type { MessageSegment, SendContent } from "zhin.js";
7
- import type { IcqqEndpointConfig } from "./types.js";
7
+ import type { IcqqEndpointConfig } from "./protocol.js";
8
8
 
9
9
  export type IcqqOutboundMediaMode = "file" | "base64";
10
10
 
package/src/protocol.ts CHANGED
@@ -1,5 +1,4 @@
1
- import { pickCredential } from 'zhin.js/adapter';
2
- import type { MessageElem, Sendable } from '@icqqjs/icqq';
1
+ import type { MessageElem, Sendable, ShareElem } from '@icqqjs/icqq';
3
2
  import type { ConversationRef } from '@zhin.js/im-contract';
4
3
  import type { MessageSegment } from "zhin.js";
5
4
  import { resolveIcqqMediaFile } from "./cq-message.js";
@@ -231,9 +230,9 @@ export const Actions = {
231
230
  GET_NOTIFY: "get_notify",
232
231
  } as const;
233
232
 
234
- /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
235
- export interface IcqqAdapterConfig {
236
- readonly id?: string;
233
+ /** One expanded ICQQ endpoint configuration produced by AdapterIndex. */
234
+ export interface IcqqEndpointConfig {
235
+ readonly id: string;
237
236
  readonly autoReconnect?: boolean;
238
237
  readonly outboundMedia?: 'file' | 'base64';
239
238
  readonly password?: string;
@@ -247,24 +246,6 @@ export interface IcqqAdapterConfig {
247
246
  readonly autoServer?: boolean;
248
247
  readonly qqnt?: boolean;
249
248
  readonly ntLogin?: boolean;
250
- /** Transitional: legacy root `endpoints[]` with `context: icqq`. */
251
- readonly endpoints?: ReadonlyArray<{
252
- readonly context?: string;
253
- readonly id?: string;
254
- readonly autoReconnect?: boolean;
255
- readonly outboundMedia?: 'file' | 'base64';
256
- readonly password?: string;
257
- readonly platform?: number;
258
- readonly ver?: string;
259
- readonly dataDir?: string;
260
- readonly signApiAddr?: string;
261
- readonly ignoreSelf?: boolean;
262
- readonly resend?: boolean;
263
- readonly cacheGroupMember?: boolean;
264
- readonly autoServer?: boolean;
265
- readonly qqnt?: boolean;
266
- readonly ntLogin?: boolean;
267
- }>;
268
249
  }
269
250
 
270
251
  export interface ResolvedIcqqConfig {
@@ -311,27 +292,24 @@ export type ParsedIcqqSendTarget =
311
292
  | { readonly kind: 'temp'; readonly groupId: number; readonly userId: number }
312
293
  | { readonly kind: 'channel'; readonly guildId: string; readonly channelId: string };
313
294
 
314
- export function resolveIcqqConfig(config: IcqqAdapterConfig = {}): ResolvedIcqqConfig {
315
- const entry = config.endpoints?.find((item) => item.context === 'icqq');
316
- const id = pickCredential(config.id, entry?.id, process.env.ICQQ_ACCOUNT);
295
+ export function resolveIcqqConfig(config: IcqqEndpointConfig): ResolvedIcqqConfig {
296
+ const id = typeof config.id === 'string' ? config.id.trim() : '';
317
297
  if (!/^\d+$/.test(id)) {
318
- throw new TypeError(
319
- 'ICQQ adapter requires numeric id (QQ uin) via plugins.<key>.id or ICQQ_ACCOUNT',
320
- );
298
+ throw new TypeError('ICQQ endpoint requires a numeric id (QQ uin)');
321
299
  }
322
- const autoReconnect = config.autoReconnect ?? entry?.autoReconnect ?? true;
323
- const outboundMedia = config.outboundMedia ?? entry?.outboundMedia;
324
- const password = config.password ?? entry?.password;
325
- const platform = config.platform ?? entry?.platform ?? 2;
326
- const ver = config.ver ?? entry?.ver ?? '9.1.70';
327
- const dataDir = config.dataDir ?? entry?.dataDir ?? `data/icqq/${id}`;
328
- const signApiAddr = config.signApiAddr ?? entry?.signApiAddr;
329
- const ignoreSelf = config.ignoreSelf ?? entry?.ignoreSelf ?? true;
330
- const resend = config.resend ?? entry?.resend;
331
- const cacheGroupMember = config.cacheGroupMember ?? entry?.cacheGroupMember ?? true;
332
- const autoServer = config.autoServer ?? entry?.autoServer ?? true;
333
- const qqnt = config.qqnt ?? entry?.qqnt;
334
- const ntLogin = config.ntLogin ?? entry?.ntLogin;
300
+ const autoReconnect = config.autoReconnect ?? true;
301
+ const outboundMedia = config.outboundMedia;
302
+ const password = config.password;
303
+ const platform = config.platform ?? 2;
304
+ const ver = config.ver ?? '9.1.70';
305
+ const dataDir = config.dataDir ?? `data/icqq/${id}`;
306
+ const signApiAddr = config.signApiAddr;
307
+ const ignoreSelf = config.ignoreSelf ?? true;
308
+ const resend = config.resend;
309
+ const cacheGroupMember = config.cacheGroupMember ?? true;
310
+ const autoServer = config.autoServer ?? true;
311
+ const qqnt = config.qqnt;
312
+ const ntLogin = config.ntLogin;
335
313
  return {
336
314
  context: 'icqq',
337
315
  id,
@@ -411,6 +389,7 @@ const ICQQ_STANDALONE_ELEMENTS = new Set([
411
389
  'video',
412
390
  'file',
413
391
  'json',
392
+ 'share',
414
393
  'long_msg',
415
394
  ] as const);
416
395
 
@@ -497,7 +476,7 @@ export function formatOutboundBody(payload: unknown): Sendable {
497
476
  return { type: 'long_msg', resid };
498
477
  }
499
478
  case 'share':
500
- return formatMusicShareJson(item.data);
479
+ return formatShare(item.data);
501
480
  default: {
502
481
  const text = item.data?.text;
503
482
  if (typeof text === 'string' && text) return text;
@@ -528,48 +507,45 @@ function isWireSegment(value: unknown): value is IcqqWireSegment {
528
507
  && typeof (value as { type?: unknown }).type === 'string';
529
508
  }
530
509
 
531
- function formatMusicShareJson(data: Record<string, unknown> | undefined): MessageElem {
510
+ function formatShare(data: Record<string, unknown> | undefined): ShareElem {
532
511
  if (!data) throw new TypeError('ICQQ share segment requires data');
533
512
  const config = data.config as {
534
513
  appid?: number;
535
514
  package?: string;
536
- icon?: string;
537
515
  sign?: string;
538
516
  } | undefined;
539
- const title = String(data.title ?? '');
540
- const desc = String(data.artist ?? '');
541
- const jumpUrl = String(data.url ?? '');
542
- const musicUrl = String(data.audio ?? '');
543
- const preview = String(data.image ?? '');
544
- const tag = config?.appid === 100497308 ? 'QQ音乐'
545
- : config?.appid === 100495085 ? '网易云音乐'
546
- : config?.appid === 100243533 ? '酷我音乐'
547
- : config?.appid === 205141 ? '酷狗音乐'
548
- : '音乐';
549
- const json = {
550
- app: 'com.tencent.structmsg',
551
- view: 'music',
552
- ver: '0.0.0.1',
553
- desc: 'music',
554
- prompt: `[分享]${title}`,
555
- meta: {
556
- music: {
557
- action: '',
558
- android_pkg_name: config?.package ?? '',
559
- app_type: 1,
560
- appid: config?.appid ?? 0,
561
- ctime: 0,
562
- desc,
563
- jumpUrl,
564
- musicUrl,
565
- preview,
566
- sourceMsgId: '0',
567
- source_icon: config?.icon ?? '',
568
- source_url: '',
569
- tag,
570
- title,
571
- },
572
- },
517
+ const title = String(data.title ?? '').trim();
518
+ const url = String(data.url ?? '').trim();
519
+ if (!title) throw new TypeError('ICQQ share segment requires title');
520
+ if (!url) throw new TypeError('ICQQ share segment requires url');
521
+
522
+ const optionalString = (value: unknown): string | undefined => {
523
+ const text = String(value ?? '').trim();
524
+ return text || undefined;
525
+ };
526
+ const appid = Number(config?.appid);
527
+ const appname = optionalString(config?.package);
528
+ const appsign = optionalString(config?.sign);
529
+ const shareConfig = Number.isSafeInteger(appid) && appid > 0
530
+ ? {
531
+ appid,
532
+ ...(appname ? { appname } : {}),
533
+ ...(appsign ? { appsign } : {}),
534
+ }
535
+ : undefined;
536
+ const summary = optionalString(data.description) ?? optionalString(data.artist);
537
+ const content = optionalString(data.content);
538
+ const image = optionalString(data.image);
539
+ const audio = optionalString(data.audio);
540
+
541
+ return {
542
+ type: 'share',
543
+ title,
544
+ url,
545
+ ...(summary ? { summary } : {}),
546
+ ...(content ? { content } : {}),
547
+ ...(image ? { image } : {}),
548
+ ...(audio ? { audio } : {}),
549
+ ...(shareConfig ? { config: shareConfig } : {}),
573
550
  };
574
- return { type: 'json', data: json };
575
551
  }
package/src/types.ts CHANGED
@@ -11,13 +11,6 @@ export interface IcqqSenderInfo {
11
11
  title?: string;
12
12
  }
13
13
 
14
- export interface IcqqEndpointConfig {
15
- context: "icqq";
16
- name: `${number}`;
17
- autoReconnect?: boolean;
18
- outboundMedia?: "file" | "base64";
19
- }
20
-
21
14
  export interface FriendInfo {
22
15
  user_id: number;
23
16
  nickname: string;
@@ -1,3 +0,0 @@
1
- import { icqqEndpointCommands } from '../../../src/icqq-endpoint-commands.js';
2
-
3
- export default icqqEndpointCommands.add;
@@ -1,3 +0,0 @@
1
- import { icqqEndpointCommands } from '../../src/icqq-endpoint-commands.js';
2
-
3
- export default icqqEndpointCommands.list;
@@ -1,3 +0,0 @@
1
- import { icqqEndpointCommands } from '../../../src/icqq-endpoint-commands.js';
2
-
3
- export default icqqEndpointCommands.remove;
@@ -1 +0,0 @@
1
- export declare const icqqEndpointCommands: import("@zhin.js/adapter").EndpointCommands<Readonly<import("@zhin.js/command").CommandDefinition<unknown, unknown, import("@zhin.js/command").CommandMessage, string | undefined>>>;
File without changes