dsh-qq-tools 0.9.2 → 0.9.5

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 (2) hide show
  1. package/lib/shared.js +44 -21
  2. package/package.json +4 -1
package/lib/shared.js CHANGED
@@ -450,6 +450,29 @@ export function formatIncomingLine(name, userId, text, messageId) {
450
450
  return `${who}(${qq}):${text || '(空)'}` + (messageId != null ? `(消息 #${messageId})` : '')
451
451
  }
452
452
 
453
+ // 回复风格:群聊要像随手打字,短、分条、不刷屏地 @ 和引用。
454
+ export const CHAT_STYLE = '回复像群友随手打字:想连发几条就**多次调用同一个工具**(一次调用只发一条),每条一句短话(十几二十个字),最多三次调用;'
455
+ + '不要句号收尾,不要写长段落,不要 Markdown,不要把昵称或 QQ 号写进正文,不要每句都 @ 或引用——'
456
+ + '只有确实要指代某条消息时才传 reply_to,只有必须提醒某个人时才传 at_user_ids。'
457
+
458
+ const CHAT_BUBBLE_CHARS = 80
459
+
460
+ // 一条消息的清理:压掉复制来的对齐空格和多行,去掉正文开头重复的 @昵称。
461
+ function cleanBubble(value) {
462
+ return String(value || '')
463
+ .replace(/\r/g, '')
464
+ .replace(/[\u3000\t ]+/g, ' ')
465
+ .replace(/\s*\n\s*/g, ' ')
466
+ .trim()
467
+ .replace(/^@[^\s@]{1,40}\s+/, '')
468
+ .slice(0, CHAT_BUBBLE_CHARS)
469
+ }
470
+
471
+ // 一次工具调用只发一条:想分几条说就让模型多次调用工具。
472
+ export function chatText(args) {
473
+ return cleanBubble(args && args.message)
474
+ }
475
+
453
476
 
454
477
  function renderTool(_args, v) {
455
478
  if (!v || !v.ok) return [{ type: 'text', text: 'QQ 工具失败:' + String((v && v.error) || '未知错误') }]
@@ -924,17 +947,17 @@ export function makeSendTool(onSent) {
924
947
  return {
925
948
  name: 'qq_send_group_message',
926
949
  description:
927
- '在指定群里发一条消息。发之前先确认群号;只发到允许的群。' +
950
+ '在指定群里发一条消息。发之前先确认群号;只发到允许的群。想分几条说就**多次调用本工具**,一次一条。' +
928
951
  'reply_to 传消息前面的 #编号 可以引用那条消息;at_user_ids 传 QQ 号可以 @ 人,at_all=true 是 @全体成员。' +
929
- '普通闲聊不要用 Markdown;一句话短一点,像群友随手打的。' +
952
+ CHAT_STYLE +
930
953
  '不想说话就不要调用这个工具;调用后不要再重复说自己发了什么。',
931
954
  parameters: {
932
955
  type: 'object',
933
956
  properties: {
934
957
  group_id: { type: 'number', description: '群号' },
935
- message: { type: 'string', description: '要发到群里的文字;只 @ 不说话时可以留空' },
936
- reply_to: { type: 'string', description: '要引用的消息 ID(消息前面的 #编号),可选' },
937
- at_user_ids: { type: 'array', items: { type: 'number' }, description: '要 @ 的 QQ 号列表,可选' },
958
+ message: { type: 'string', description: '这一条的文字,一句短话;只 @ 不说话时可以留空' },
959
+ reply_to: { type: 'string', description: '要引用的消息 ID(消息前面的 #编号),可选', },
960
+ at_user_ids: { type: 'array', items: { type: 'number' }, description: '要 @ 的 QQ 号列表,可选', },
938
961
  at_all: { type: 'boolean', description: 'true = @全体成员,可选' },
939
962
  },
940
963
  required: ['group_id'],
@@ -944,7 +967,7 @@ export function makeSendTool(onSent) {
944
967
  async execute(args) {
945
968
  const cfg = loadCfg()
946
969
  const groupId = Number(args && args.group_id)
947
- const text = String((args && args.message) || '').trim()
970
+ const text = chatText(args)
948
971
  const atIds = [...new Set(
949
972
  (Array.isArray(args && args.at_user_ids) ? args.at_user_ids : [])
950
973
  .map((x) => Number(x)).filter((x) => Number.isInteger(x) && x > 0),
@@ -954,12 +977,12 @@ export function makeSendTool(onSent) {
954
977
  if (!text && !atIds.length && !atAll) return { ok: false, error: '消息是空的' }
955
978
  if (!allowedGroup(groupId, cfg)) return { ok: false, error: '这个群不在允许列表里' }
956
979
  try {
957
- const message = []
958
980
  const replyTo = args && args.reply_to
981
+ const message = []
959
982
  if (replyTo != null && replyTo !== '') message.push({ type: 'reply', data: { id: String(replyTo) } })
960
983
  if (atAll) message.push({ type: 'at', data: { qq: 'all' } })
961
984
  else for (const qq of atIds) message.push({ type: 'at', data: { qq: String(qq) } })
962
- if (text) message.push({ type: 'text', data: { text: text.slice(0, 1000) } })
985
+ if (text) message.push({ type: 'text', data: { text } })
963
986
  const data = await onebot('send_group_msg', { group_id: groupId, message }, cfg)
964
987
  try { if (typeof onSent === 'function') onSent(groupId, '发群消息') } catch (error) {}
965
988
  return {
@@ -1125,15 +1148,15 @@ export function makeSendPrivateTool() {
1125
1148
  return {
1126
1149
  name: 'qq_send_private_message',
1127
1150
  description:
1128
- '给某个 QQ 好友发一条私聊消息。user_id 从 qq_list_friends 或私聊唤醒提示里拿。' +
1151
+ '给某个 QQ 好友发一条私聊消息。user_id 从 qq_list_friends 或私聊唤醒提示里拿。想分几条说就多次调用本工具,一次一条。' +
1129
1152
  'reply_to 传消息前面的 #编号 可以引用那条消息。' +
1130
- '普通闲聊不要用 Markdown;一句话短一点,像聊天一样。' +
1153
+ CHAT_STYLE +
1131
1154
  '不想说话就不要调用;调用后不要再重复说自己发了什么。',
1132
1155
  parameters: {
1133
1156
  type: 'object',
1134
1157
  properties: {
1135
1158
  user_id: { type: 'number', description: '对方的 QQ 号' },
1136
- message: { type: 'string', description: '要发的文字,不要 Markdown' },
1159
+ message: { type: 'string', description: '这一条的文字,一句短话' },
1137
1160
  reply_to: { type: 'string', description: '要引用的消息 ID(消息前面的 #编号),可选' },
1138
1161
  group_id: { type: 'number', description: '临时会话的来源群号,可选;普通好友私聊不用传' },
1139
1162
  },
@@ -1144,16 +1167,16 @@ export function makeSendPrivateTool() {
1144
1167
  async execute(args) {
1145
1168
  const cfg = loadCfg()
1146
1169
  const userId = Number(args && args.user_id)
1147
- const text = String((args && args.message) || '').trim()
1170
+ const text = chatText(args)
1148
1171
  const groupId = args && args.group_id != null ? Number(args.group_id) : 0
1149
1172
  if (!Number.isFinite(userId)) return { ok: false, error: 'user_id 不是有效 QQ 号' }
1150
1173
  if (!text) return { ok: false, error: '消息是空的' }
1151
1174
  if (!allowedPrivate(userId, cfg)) return { ok: false, error: '这个 QQ 不在允许列表里' }
1152
1175
  try {
1153
- const message = []
1154
1176
  const replyTo = args && args.reply_to
1177
+ const message = []
1155
1178
  if (replyTo != null && replyTo !== '') message.push({ type: 'reply', data: { id: String(replyTo) } })
1156
- message.push({ type: 'text', data: { text: text.slice(0, 1000) } })
1179
+ message.push({ type: 'text', data: { text } })
1157
1180
  const body = { user_id: userId, message }
1158
1181
  if (groupId) body.group_id = groupId
1159
1182
  const data = await onebot('send_private_msg', body, cfg)
@@ -3034,13 +3057,13 @@ async function pollMentions(ctx) {
3034
3057
  if (hasActiveTool('qq_send_face')) sendTools.push('qq_send_face')
3035
3058
  const sendHint = sendTools.length ? '想说话可以用 ' + sendTools.join(' / ') + ' 回复;' : ''
3036
3059
  const quoteHint = hasActiveTool('qq_send_group_message')
3037
- ? '想引用某条消息就传 reply_to=消息编号;想 @ 人就传 at_user_ids=[QQ号],@全体传 at_all=true;'
3060
+ ? '确实要指代某条消息时才传 reply_to=消息编号;必须提醒某个人时才传 at_user_ids=[QQ号];'
3038
3061
  : ''
3039
3062
  const prompt =
3040
3063
  '【群聊 @ 提醒】有人 @ 了你:\n' +
3041
3064
  lines.join('\n') +
3042
3065
  '\n' + lookHint() + '再决定要不要回复、回哪个群。' + sendHint + quoteHint + '不想说话就安静待着;' +
3043
- '不要用 Markdown,不要提工具和私聊内容,短一点。'
3066
+ '不要提工具和私聊内容。' + CHAT_STYLE
3044
3067
  const message = {
3045
3068
  id: randomUUID(),
3046
3069
  role: 'user',
@@ -3058,9 +3081,9 @@ async function pollMentions(ctx) {
3058
3081
  '【群聊后续】你刚才在群「' + group + '」里说过话,现在又有人说话:\n' +
3059
3082
  lines2.join('\n') +
3060
3083
  '\n想接就接;' +
3061
- (hasActiveTool('qq_send_group_message') ? '想说话用 qq_send_group_message 发到这个群,引用某条传 reply_to=消息编号,@人传 at_user_ids=[QQ号],@全体传 at_all=true;' : '') +
3084
+ (hasActiveTool('qq_send_group_message') ? '想说话用 qq_send_group_message 发到这个群,确实要指代某条才传 reply_to=消息编号,必须提醒某人才传 at_user_ids=[QQ号];' : '') +
3062
3085
  (hasActiveTool('qq_send_face') ? '想回表情用 qq_send_face;' : '') +
3063
- '不想接就安静待着。不要用 Markdown,短一点。'
3086
+ '不想接就安静待着。' + CHAT_STYLE
3064
3087
  await agent.followup({
3065
3088
  id: randomUUID(),
3066
3089
  role: 'user',
@@ -3136,7 +3159,7 @@ async function handlePoke(ctx, ev) {
3136
3159
  if (hasActiveTool('qq_send_poke')) actions.push(`想戳回去用 qq_send_poke(group_id=${groupId}, user_id=${sender})`)
3137
3160
  prompt = `【戳一戳】群「${groupName}」里 ${name}(${sender}) 戳了你一下。`
3138
3161
  + (actions.length ? actions.join(';') + '。不想回就安静待着。' : '不用回复。')
3139
- + '不要用 Markdown,短一点。'
3162
+ + CHAT_STYLE
3140
3163
  } else {
3141
3164
  const actions = []
3142
3165
  if (hasActiveTool('qq_send_private_message')) actions.push(`想回就用 qq_send_private_message(user_id=${sender})`)
@@ -3144,7 +3167,7 @@ async function handlePoke(ctx, ev) {
3144
3167
  if (hasActiveTool('qq_send_poke')) actions.push(`想戳回去用 qq_send_poke(user_id=${sender})`)
3145
3168
  prompt = `【戳一戳】${sender} 在私聊里戳了你一下。`
3146
3169
  + (actions.length ? actions.join(';') + '。不想回就安静待着。' : '不用回复。')
3147
- + '不要用 Markdown,短一点。'
3170
+ + CHAT_STYLE
3148
3171
  }
3149
3172
  const sourceKind = groupId ? 'qq-poke' : 'qq-private'
3150
3173
  await agent.followup({
@@ -3200,7 +3223,7 @@ async function handlePrivateMessage(ctx, ev, admitImages) {
3200
3223
  const prompt =
3201
3224
  '【QQ ' + where + '】' + formatIncomingLine(name, userId, text, ev.message_id) +
3202
3225
  '\n' + (actions.length ? actions.join(';') + '。' : '') +
3203
- '不想回就安静待着;不要用 Markdown,不要提工具,短一点。'
3226
+ '不想回就安静待着;不要提工具。' + CHAT_STYLE
3204
3227
  const content = [{ type: 'text', text: prompt }]
3205
3228
  if (typeof admitImages === 'function') {
3206
3229
  // 和群/私聊记录一样:先保证最近两张动图能进来,剩下的名额给静图。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-qq-tools",
3
- "version": "0.9.2",
3
+ "version": "0.9.5",
4
4
  "description": "给大肥鱼会话用的 QQ 群/私聊工具:列群、看群记录、发群消息(可引用/@)、发表情、收藏表情(SnowLuma 收藏夹)、好友/私聊记录、发私聊消息、私聊唤醒、群成员、戳一戳感知/戳回去、更改头像、好友管理(资料/备注/分组/删好友/名片赞/好友申请)、QQ 空间说说(列表/详情/发表/评论/点赞/权限)、挂载会话选择。11 个工具组加 1 个设置组件,可在插件页逐个开关。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -98,3 +98,6 @@
98
98
  }
99
99
  }
100
100
  }
101
+
102
+
103
+