@napuketto/adapter 0.7.3 → 0.8.0

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.
@@ -22,6 +22,7 @@ var MilkyApi = class {
22
22
  webApi;
23
23
  groupNotifyApi;
24
24
  richMediaApi;
25
+ recentContactApi;
25
26
  self;
26
27
  groupCache;
27
28
  qqVersion;
@@ -41,6 +42,7 @@ var MilkyApi = class {
41
42
  this.webApi = opts.webApi;
42
43
  this.groupNotifyApi = opts.groupNotifyApi;
43
44
  this.richMediaApi = opts.richMediaApi;
45
+ this.recentContactApi = opts.recentContactApi;
44
46
  this.self = opts.self;
45
47
  this.groupCache = opts.groupCache;
46
48
  this.qqVersion = opts.qqVersion;
@@ -193,6 +195,62 @@ function toFriendEntity(friend, uin, nick) {
193
195
  };
194
196
  }
195
197
  //#endregion
198
+ //#region src/milky/helper/media-uri.ts
199
+ /**
200
+ * 发送媒体 URI 解析(Milky OutgoingSegment uri → 本地路径)
201
+ *
202
+ * 规范支持 file:// / http(s):// / base64:// 三种(image/record/video 共用):
203
+ * - file:// → 平台路径(segments.ts 的同步面已处理)
204
+ * - base64:// → 解码落盘 cacheDir/milky/,file-type 探测扩展名后改名
205
+ * - http(s):// → @napuketto/media downloadUrl 落盘 cacheDir/milky/
206
+ * 未配置 mediaDir 时远程 URI 报错(不发静默空消息)。
207
+ */
208
+ /** file:// 前缀(fileURLToPath 失败时的裸剥离兜底)。 */
209
+ const FILE_URI_PREFIX_RE$1 = /^file:\/\//;
210
+ /** 解析远程媒体 URI 为本地绝对路径(base64:// / http(s)://;file:// 走同步面)。 */
211
+ async function resolveMediaUri(uri, mediaDir) {
212
+ if (mediaDir === void 0 || mediaDir === "") throw kernelError("远程媒体发送需要配置 [accounts.milky] cacheDir(下载缓存目录)", "INVALID_PARAM");
213
+ if (uri.startsWith("base64://")) return await writeBase64(uri.slice(9), mediaDir);
214
+ const dest = join(mediaDir, `${randomUUID()}${inferExtension(null, uri)}`);
215
+ await downloadUrl(uri, dest);
216
+ return dest;
217
+ }
218
+ /**
219
+ * 动作参数 URI(image_uri / file_uri)→ 本地路径:file:// 同步展开(fileURLToPath
220
+ * 失败裸剥离兜底,与 segments.ts 同步面同规则);http(s):// / base64:// 复用
221
+ * resolveMediaUri 落盘;裸路径(go-cqhttp 惯例)原样。
222
+ */
223
+ async function resolveUriToPath(uri, mediaDir) {
224
+ if (uri.startsWith("file://")) try {
225
+ return fileURLToPath(uri);
226
+ } catch {
227
+ return uri.replace(FILE_URI_PREFIX_RE$1, "");
228
+ }
229
+ if (uri.startsWith("http://") || uri.startsWith("https://") || uri.startsWith("base64://")) return await resolveMediaUri(uri, mediaDir);
230
+ return uri;
231
+ }
232
+ /** base64 载荷 → 落盘(先写 .bin,探测到扩展名再改名)。 */
233
+ async function writeBase64(payload, mediaDir) {
234
+ let bytes;
235
+ try {
236
+ bytes = Buffer.from(payload, "base64");
237
+ } catch {
238
+ throw kernelError("base64:// 载荷解码失败", "INVALID_PARAM");
239
+ }
240
+ if (bytes.length === 0) throw kernelError("base64:// 载荷为空", "INVALID_PARAM");
241
+ await mkdir(mediaDir, { recursive: true });
242
+ const raw = join(mediaDir, `${randomUUID()}.bin`);
243
+ await writeFile(raw, bytes);
244
+ try {
245
+ const { ext } = await detectFileType(raw);
246
+ const final = join(mediaDir, `${randomUUID()}${ext}`);
247
+ await rename(raw, final);
248
+ return final;
249
+ } catch {
250
+ return raw;
251
+ }
252
+ }
253
+ //#endregion
196
254
  //#region src/milky/helper/request.ts
197
255
  /** uid → uin(core 共享解析;ctx 携带映射)。 */
198
256
  function toUin$1(uid, ctx) {
@@ -634,6 +692,88 @@ var RejectGroupInvitationAction = class extends HandleGroupNotifyBase {
634
692
  return await this.respond(payload.invitation_seq, false);
635
693
  }
636
694
  };
695
+ /** send_group_nudge:发送群戳一戳(self.uid 缺失报 INVALID_PARAM)。 */
696
+ var SendGroupNudgeAction = class extends MilkyApiAction {
697
+ name = "send_group_nudge";
698
+ schema = z.object({
699
+ ...groupIdField$1,
700
+ ...userIdField
701
+ });
702
+ async _handle(payload) {
703
+ const receiverUid = await this.api.resolveUidFromUin(payload.user_id);
704
+ const peer = await this.api.resolvePeer("group", payload.group_id);
705
+ await this.api.msgApi.sendNudge(peer, {
706
+ receiverUid,
707
+ callerUid: requireSelfUid$1(this.api)
708
+ });
709
+ return {};
710
+ }
711
+ };
712
+ /** get_group_announcements:获取群公告列表(bulletin 形状待校准,宽松映射)。 */
713
+ var GetGroupAnnouncementsAction = class extends MilkyApiAction {
714
+ name = "get_group_announcements";
715
+ schema = z.object({ ...groupIdField$1 });
716
+ async _handle(payload) {
717
+ const groupCode = String(payload.group_id);
718
+ const bulletins = await this.api.groupApi.getGroupAnnouncements(groupCode);
719
+ return { announcements: await Promise.all(bulletins.map((b) => bulletinToEntity(b, payload.group_id, this.api))) };
720
+ }
721
+ };
722
+ /** send_group_announcement:发群公告(image_uri 落盘后经上传+发布两步)。 */
723
+ var SendGroupAnnouncementAction = class extends MilkyApiAction {
724
+ name = "send_group_announcement";
725
+ schema = z.object({
726
+ ...groupIdField$1,
727
+ content: z.string().min(1),
728
+ image_uri: optStr$1
729
+ });
730
+ async _handle(payload) {
731
+ const groupCode = String(payload.group_id);
732
+ let imagePath;
733
+ if (payload.image_uri !== void 0 && payload.image_uri !== null && payload.image_uri !== "") imagePath = await resolveUriToPath(payload.image_uri, this.api.mediaDir);
734
+ await this.api.groupApi.publishGroupAnnouncement(groupCode, payload.content, imagePath);
735
+ return {};
736
+ }
737
+ };
738
+ /** delete_group_announcement:删除群公告。 */
739
+ var DeleteGroupAnnouncementAction = class extends MilkyApiAction {
740
+ name = "delete_group_announcement";
741
+ schema = z.object({
742
+ ...groupIdField$1,
743
+ announcement_id: z.string().min(1)
744
+ });
745
+ async _handle(payload) {
746
+ await this.api.groupApi.deleteGroupAnnouncement(String(payload.group_id), payload.announcement_id);
747
+ return {};
748
+ }
749
+ };
750
+ /** set_group_avatar:设置群头像(image_uri → 本地路径)。 */
751
+ var SetGroupAvatarAction = class extends MilkyApiAction {
752
+ name = "set_group_avatar";
753
+ schema = z.object({
754
+ ...groupIdField$1,
755
+ image_uri: z.string().min(1)
756
+ });
757
+ async _handle(payload) {
758
+ const imagePath = await resolveUriToPath(payload.image_uri, this.api.mediaDir);
759
+ await this.api.groupApi.setGroupAvatar(String(payload.group_id), imagePath);
760
+ return {};
761
+ }
762
+ };
763
+ /** set_group_member_special_title:设置专属头衔(uin → uid)。 */
764
+ var SetGroupMemberSpecialTitleAction = class extends MilkyApiAction {
765
+ name = "set_group_member_special_title";
766
+ schema = z.object({
767
+ ...groupIdField$1,
768
+ ...userIdField,
769
+ special_title: z.string()
770
+ });
771
+ async _handle(payload) {
772
+ const uid = await this.api.resolveUidFromUin(payload.user_id);
773
+ await this.api.groupApi.setMemberSpecialTitle(String(payload.group_id), uid, payload.special_title);
774
+ return {};
775
+ }
776
+ };
637
777
  /** ---------- 内部辅助 ---------- */
638
778
  /** 通知查找数量(单屏全量,OB11 DEFAULT_COUNT 同值)。 */
639
779
  const NOTIFY_SEARCH_COUNT = 100;
@@ -758,179 +898,34 @@ function notifyToEntity(notify, isFiltered, uidToUin) {
758
898
  if (entity.type === "join_request") entity.is_filtered = isFiltered;
759
899
  return entity;
760
900
  }
761
- //#endregion
762
- //#region src/milky/action/group-file.ts
763
- const groupIdField = { group_id: z.number().int().min(10001) };
764
- const fileIdField = { file_id: z.string().min(1) };
765
- const optStr = z.string().nullable().optional();
766
- /** get_group_files:获取群文件列表(根目录或指定文件夹;过滤 parent 匹配项)。 */
767
- var GetGroupFilesAction = class extends MilkyApiAction {
768
- name = "get_group_files";
769
- schema = z.object({
770
- ...groupIdField,
771
- parent_folder_id: optStr
772
- });
773
- async _handle(payload) {
774
- const groupCode = String(payload.group_id);
775
- const parent = payload.parent_folder_id ?? "";
776
- const items = await this.api.richMediaApi.getGroupFileList(groupCode, parent);
777
- const files = [];
778
- const folders = [];
779
- for (const item of items) if (item.fileInfo !== void 0) files.push(fileToEntity(item.fileInfo, payload.group_id));
780
- else if (item.folderInfo !== void 0) folders.push(folderToEntity(item.folderInfo, payload.group_id));
781
- return {
782
- files,
783
- folders
784
- };
785
- }
786
- };
787
- /** move_group_file:移动文件(target_folder_id 空 = 移到根目录;parent 入参仅 schema 兼容)。 */
788
- var MoveGroupFileAction = class extends MilkyApiAction {
789
- name = "move_group_file";
790
- schema = z.object({
791
- ...groupIdField,
792
- ...fileIdField,
793
- parent_folder_id: optStr,
794
- target_folder_id: optStr
795
- });
796
- async _handle(payload) {
797
- await this.api.richMediaApi.moveGroupFile(String(payload.group_id), [payload.file_id], payload.target_folder_id ?? "");
798
- return {};
799
- }
800
- };
801
- /** rename_group_file:重命名文件。 */
802
- var RenameGroupFileAction = class extends MilkyApiAction {
803
- name = "rename_group_file";
804
- schema = z.object({
805
- ...groupIdField,
806
- ...fileIdField,
807
- parent_folder_id: optStr,
808
- new_file_name: z.string().min(1)
809
- });
810
- async _handle(payload) {
811
- await this.api.richMediaApi.renameGroupFile(String(payload.group_id), payload.file_id, payload.new_file_name);
812
- return {};
813
- }
814
- };
815
- /** delete_group_file:删除文件。 */
816
- var DeleteGroupFileAction = class extends MilkyApiAction {
817
- name = "delete_group_file";
818
- schema = z.object({
819
- ...groupIdField,
820
- ...fileIdField
821
- });
822
- async _handle(payload) {
823
- await this.api.richMediaApi.deleteGroupFile(String(payload.group_id), [payload.file_id]);
824
- return {};
825
- }
826
- };
827
- /**
828
- * persist_group_file:转存文件(RichMediaApi.transGroupFile,转存语义待对齐)。
829
- */
830
- var PersistGroupFileAction = class extends MilkyApiAction {
831
- name = "persist_group_file";
832
- schema = z.object({
833
- ...groupIdField,
834
- ...fileIdField
835
- });
836
- async _handle(payload) {
837
- await this.api.richMediaApi.transGroupFile(String(payload.group_id), payload.file_id);
838
- return {};
839
- }
840
- };
841
- /** create_group_folder:创建文件夹(返回 folder_id)。 */
842
- var CreateGroupFolderAction = class extends MilkyApiAction {
843
- name = "create_group_folder";
844
- schema = z.object({
845
- ...groupIdField,
846
- folder_name: z.string().min(1)
847
- });
848
- async _handle(payload) {
849
- return { folder_id: await this.api.richMediaApi.createGroupFolder(String(payload.group_id), payload.folder_name) };
850
- }
851
- };
852
- /** delete_group_folder:删除文件夹。 */
853
- var DeleteGroupFolderAction = class extends MilkyApiAction {
854
- name = "delete_group_folder";
855
- schema = z.object({
856
- ...groupIdField,
857
- folder_id: z.string().min(1)
858
- });
859
- async _handle(payload) {
860
- await this.api.richMediaApi.deleteGroupFolder(String(payload.group_id), payload.folder_id);
861
- return {};
862
- }
863
- };
864
- /** GroupFileItemInfo → GroupFileEntity(fileSize 原生为字符串;无源字段兜底)。 */
865
- function fileToEntity(info, groupId) {
866
- return {
867
- group_id: groupId,
868
- file_id: info.fileId,
869
- file_name: info.fileName,
870
- parent_folder_id: info.parent ?? "",
871
- file_size: Number(info.fileSize) || 0,
872
- uploaded_time: Number(info.uploadTime) || 0,
873
- expire_time: null,
874
- uploader_id: Number(info.uploaderUin) || 0,
875
- downloaded_times: 0
876
- };
901
+ /** self.uid 必备(nudge 发送的 callerUid;缺配置报 INVALID_PARAM)。 */
902
+ function requireSelfUid$1(api) {
903
+ const uid = api.self.uid;
904
+ if (uid === void 0 || uid === "") throw kernelError("登录身份缺 uid(无法发起戳一戳)", "INVALID_PARAM");
905
+ return uid;
877
906
  }
878
- /** GroupFolderInfo → GroupFolderEntity(元数据无类型字段防御读取,缺省 0)。 */
879
- function folderToEntity(info, groupId) {
880
- const num = (key) => {
881
- const v = info[key];
882
- return typeof v === "number" ? v : Number(v) || 0;
883
- };
907
+ /** GroupBulletin → 群公告实体(senderUid → uin 反查;无源字段兜底,待校准)。 */
908
+ async function bulletinToEntity(b, groupId, api) {
884
909
  return {
885
910
  group_id: groupId,
886
- folder_id: info.folderId,
887
- parent_folder_id: info.folderUid ?? "",
888
- folder_name: info.folderName,
889
- created_time: num("createTime"),
890
- last_modified_time: num("modifyTime"),
891
- creator_id: 0,
892
- file_count: num("totalFileCount")
911
+ announcement_id: b.announcementId ?? "",
912
+ user_id: await senderUinOf(b, api),
913
+ time: Number(b.publishTime) || 0,
914
+ content: b.content ?? "",
915
+ image_url: picUrlOf(b)
893
916
  };
894
917
  }
895
- //#endregion
896
- //#region src/milky/helper/media-uri.ts
897
- /**
898
- * 发送媒体 URI 解析(Milky OutgoingSegment uri → 本地路径)
899
- *
900
- * 规范支持 file:// / http(s):// / base64:// 三种(image/record/video 共用):
901
- * - file:// → 平台路径(segments.ts 的同步面已处理)
902
- * - base64:// → 解码落盘 cacheDir/milky/,file-type 探测扩展名后改名
903
- * - http(s):// → @napuketto/media downloadUrl 落盘 cacheDir/milky/
904
- * 未配置 mediaDir 时远程 URI 报错(不发静默空消息)。
905
- */
906
- /** 解析远程媒体 URI 为本地绝对路径(base64:// / http(s)://;file:// 走同步面)。 */
907
- async function resolveMediaUri(uri, mediaDir) {
908
- if (mediaDir === void 0 || mediaDir === "") throw kernelError("远程媒体发送需要配置 [accounts.milky] cacheDir(下载缓存目录)", "INVALID_PARAM");
909
- if (uri.startsWith("base64://")) return await writeBase64(uri.slice(9), mediaDir);
910
- const dest = join(mediaDir, `${randomUUID()}${inferExtension(null, uri)}`);
911
- await downloadUrl(uri, dest);
912
- return dest;
918
+ /** 公告发送者 uid → uin(缺省/转换失败 0 兜底)。 */
919
+ async function senderUinOf(b, api) {
920
+ const uid = b.senderUid;
921
+ if (typeof uid !== "string" || uid === "") return 0;
922
+ const uins = await api.uidToUin([uid]).catch(() => void 0);
923
+ return Number(uins?.get(uid)) || 0;
913
924
  }
914
- /** base64 载荷 → 落盘(先写 .bin,探测到扩展名再改名)。 */
915
- async function writeBase64(payload, mediaDir) {
916
- let bytes;
917
- try {
918
- bytes = Buffer.from(payload, "base64");
919
- } catch {
920
- throw kernelError("base64:// 载荷解码失败", "INVALID_PARAM");
921
- }
922
- if (bytes.length === 0) throw kernelError("base64:// 载荷为空", "INVALID_PARAM");
923
- await mkdir(mediaDir, { recursive: true });
924
- const raw = join(mediaDir, `${randomUUID()}.bin`);
925
- await writeFile(raw, bytes);
926
- try {
927
- const { ext } = await detectFileType(raw);
928
- const final = join(mediaDir, `${randomUUID()}${ext}`);
929
- await rename(raw, final);
930
- return final;
931
- } catch {
932
- return raw;
933
- }
925
+ /** 公告图片 url(缺省 null)。 */
926
+ function picUrlOf(b) {
927
+ const url = b.pic?.url;
928
+ return typeof url === "string" && url !== "" ? url : null;
934
929
  }
935
930
  //#endregion
936
931
  //#region src/milky/helper/segments.ts
@@ -1641,6 +1636,233 @@ function receiveSegmentsOf(msg, uidToUin) {
1641
1636
  function avatarUrlOf(uin) {
1642
1637
  return uin === "" ? "" : `https://q1.qlogo.cn/g?b=qq&nk=${uin}&s=640`;
1643
1638
  }
1639
+ /** get_custom_face_url_list:收藏表情链接(queryFavEmojiByDesc 形状待校准)。 */
1640
+ var GetCustomFaceUrlListAction = class extends MilkyApiAction {
1641
+ name = "get_custom_face_url_list";
1642
+ schema = z.object({});
1643
+ async _handle() {
1644
+ return { urls: await this.api.msgApi.queryFavEmojis() };
1645
+ }
1646
+ };
1647
+ /**
1648
+ * get_resource_temp_url:资源临时链接。
1649
+ * 已知差异(P0 模型):接收段 resource_id 即临时链接(本地路径优先 url 兜底),
1650
+ * 无独立资源索引——http(s)/base64 形态原样返回,其余 NOT_FOUND。
1651
+ */
1652
+ var GetResourceTempUrlAction = class extends MilkyApiAction {
1653
+ name = "get_resource_temp_url";
1654
+ schema = z.object({ resource_id: z.string().min(1) });
1655
+ async _handle(payload) {
1656
+ const id = payload.resource_id;
1657
+ if (id.startsWith("http://") || id.startsWith("https://") || id.startsWith("base64://")) return { url: id };
1658
+ throw kernelError(`资源 ${id} 无临时链接(resource_id 非链接形态)`, "NOT_FOUND");
1659
+ }
1660
+ };
1661
+ /** upload_private_file:上传私聊文件(file 元素消息链,file_id 取 fileUuid)。 */
1662
+ var UploadPrivateFileAction = class extends MilkyApiAction {
1663
+ name = "upload_private_file";
1664
+ schema = z.object({
1665
+ user_id: z.number().int().min(10001),
1666
+ file_uri: z.string().min(1),
1667
+ file_name: z.string().min(1)
1668
+ });
1669
+ async _handle(payload) {
1670
+ const path = await resolveUriToPath(payload.file_uri, this.api.mediaDir);
1671
+ const peer = await this.api.resolvePeer("friend", payload.user_id);
1672
+ return { file_id: await sendFileMessage(this.api, peer, path, payload.file_name) };
1673
+ }
1674
+ };
1675
+ /**
1676
+ * 发送文件消息并回查 file_id:sendMessage(canonical file 元素,kernel 走
1677
+ * prepareFileElement 链)→ fetchMsgsByMsgId 取 fileElement.fileUuid(缺省 msgId)。
1678
+ * upload_private/group_file 共用。
1679
+ */
1680
+ async function sendFileMessage(api, peer, path, fileName) {
1681
+ const { msgId } = await api.msgApi.sendMessage(peer, [{
1682
+ type: "file",
1683
+ path,
1684
+ name: fileName
1685
+ }]);
1686
+ const [sent] = await api.msgApi.fetchMsgsByMsgId(peer, [msgId]);
1687
+ const uuid = (sent?.elements.find((e) => e.fileElement !== void 0))?.fileElement?.fileUuid;
1688
+ return uuid !== void 0 && uuid !== "" ? uuid : msgId;
1689
+ }
1690
+ //#endregion
1691
+ //#region src/milky/action/group-file.ts
1692
+ const groupIdField = { group_id: z.number().int().min(10001) };
1693
+ const fileIdField = { file_id: z.string().min(1) };
1694
+ const optStr = z.string().nullable().optional();
1695
+ /** get_group_files:获取群文件列表(根目录或指定文件夹;过滤 parent 匹配项)。 */
1696
+ var GetGroupFilesAction = class extends MilkyApiAction {
1697
+ name = "get_group_files";
1698
+ schema = z.object({
1699
+ ...groupIdField,
1700
+ parent_folder_id: optStr
1701
+ });
1702
+ async _handle(payload) {
1703
+ const groupCode = String(payload.group_id);
1704
+ const parent = payload.parent_folder_id ?? "";
1705
+ const items = await this.api.richMediaApi.getGroupFileList(groupCode, parent);
1706
+ const files = [];
1707
+ const folders = [];
1708
+ for (const item of items) if (item.fileInfo !== void 0) files.push(fileToEntity(item.fileInfo, payload.group_id));
1709
+ else if (item.folderInfo !== void 0) folders.push(folderToEntity(item.folderInfo, payload.group_id));
1710
+ return {
1711
+ files,
1712
+ folders
1713
+ };
1714
+ }
1715
+ };
1716
+ /** move_group_file:移动文件(target_folder_id 空 = 移到根目录;parent 入参仅 schema 兼容)。 */
1717
+ var MoveGroupFileAction = class extends MilkyApiAction {
1718
+ name = "move_group_file";
1719
+ schema = z.object({
1720
+ ...groupIdField,
1721
+ ...fileIdField,
1722
+ parent_folder_id: optStr,
1723
+ target_folder_id: optStr
1724
+ });
1725
+ async _handle(payload) {
1726
+ await this.api.richMediaApi.moveGroupFile(String(payload.group_id), [payload.file_id], payload.target_folder_id ?? "");
1727
+ return {};
1728
+ }
1729
+ };
1730
+ /** rename_group_file:重命名文件。 */
1731
+ var RenameGroupFileAction = class extends MilkyApiAction {
1732
+ name = "rename_group_file";
1733
+ schema = z.object({
1734
+ ...groupIdField,
1735
+ ...fileIdField,
1736
+ parent_folder_id: optStr,
1737
+ new_file_name: z.string().min(1)
1738
+ });
1739
+ async _handle(payload) {
1740
+ await this.api.richMediaApi.renameGroupFile(String(payload.group_id), payload.file_id, payload.new_file_name);
1741
+ return {};
1742
+ }
1743
+ };
1744
+ /** delete_group_file:删除文件。 */
1745
+ var DeleteGroupFileAction = class extends MilkyApiAction {
1746
+ name = "delete_group_file";
1747
+ schema = z.object({
1748
+ ...groupIdField,
1749
+ ...fileIdField
1750
+ });
1751
+ async _handle(payload) {
1752
+ await this.api.richMediaApi.deleteGroupFile(String(payload.group_id), [payload.file_id]);
1753
+ return {};
1754
+ }
1755
+ };
1756
+ /**
1757
+ * persist_group_file:转存文件(RichMediaApi.transGroupFile,转存语义待对齐)。
1758
+ */
1759
+ var PersistGroupFileAction = class extends MilkyApiAction {
1760
+ name = "persist_group_file";
1761
+ schema = z.object({
1762
+ ...groupIdField,
1763
+ ...fileIdField
1764
+ });
1765
+ async _handle(payload) {
1766
+ await this.api.richMediaApi.transGroupFile(String(payload.group_id), payload.file_id);
1767
+ return {};
1768
+ }
1769
+ };
1770
+ /** create_group_folder:创建文件夹(返回 folder_id)。 */
1771
+ var CreateGroupFolderAction = class extends MilkyApiAction {
1772
+ name = "create_group_folder";
1773
+ schema = z.object({
1774
+ ...groupIdField,
1775
+ folder_name: z.string().min(1)
1776
+ });
1777
+ async _handle(payload) {
1778
+ return { folder_id: await this.api.richMediaApi.createGroupFolder(String(payload.group_id), payload.folder_name) };
1779
+ }
1780
+ };
1781
+ /** delete_group_folder:删除文件夹。 */
1782
+ var DeleteGroupFolderAction = class extends MilkyApiAction {
1783
+ name = "delete_group_folder";
1784
+ schema = z.object({
1785
+ ...groupIdField,
1786
+ folder_id: z.string().min(1)
1787
+ });
1788
+ async _handle(payload) {
1789
+ await this.api.richMediaApi.deleteGroupFolder(String(payload.group_id), payload.folder_id);
1790
+ return {};
1791
+ }
1792
+ };
1793
+ /** GroupFileItemInfo → GroupFileEntity(fileSize 原生为字符串;无源字段兜底)。 */
1794
+ function fileToEntity(info, groupId) {
1795
+ return {
1796
+ group_id: groupId,
1797
+ file_id: info.fileId,
1798
+ file_name: info.fileName,
1799
+ parent_folder_id: info.parent ?? "",
1800
+ file_size: Number(info.fileSize) || 0,
1801
+ uploaded_time: Number(info.uploadTime) || 0,
1802
+ expire_time: null,
1803
+ uploader_id: Number(info.uploaderUin) || 0,
1804
+ downloaded_times: 0
1805
+ };
1806
+ }
1807
+ /** GroupFolderInfo → GroupFolderEntity(元数据无类型字段防御读取,缺省 0)。 */
1808
+ function folderToEntity(info, groupId) {
1809
+ const num = (key) => {
1810
+ const v = info[key];
1811
+ return typeof v === "number" ? v : Number(v) || 0;
1812
+ };
1813
+ return {
1814
+ group_id: groupId,
1815
+ folder_id: info.folderId,
1816
+ parent_folder_id: info.folderUid ?? "",
1817
+ folder_name: info.folderName,
1818
+ created_time: num("createTime"),
1819
+ last_modified_time: num("modifyTime"),
1820
+ creator_id: 0,
1821
+ file_count: num("totalFileCount")
1822
+ };
1823
+ }
1824
+ /** upload_group_file:上传群文件(消息发送链;parent_folder_id 暂不参与定位)。 */
1825
+ var UploadGroupFileAction = class extends MilkyApiAction {
1826
+ name = "upload_group_file";
1827
+ schema = z.object({
1828
+ ...groupIdField,
1829
+ parent_folder_id: optStr,
1830
+ file_uri: z.string().min(1),
1831
+ file_name: z.string().min(1)
1832
+ });
1833
+ async _handle(payload) {
1834
+ const path = await resolveUriToPath(payload.file_uri, this.api.mediaDir);
1835
+ const peer = {
1836
+ chatType: ChatType.GROUP,
1837
+ peerUid: String(payload.group_id)
1838
+ };
1839
+ return { file_id: await sendFileMessage(this.api, peer, path, payload.file_name) };
1840
+ }
1841
+ };
1842
+ /** get_group_file_download_url:群文件下载链接(url 字段待实机确认,仅根目录)。 */
1843
+ var GetGroupFileDownloadUrlAction = class extends MilkyApiAction {
1844
+ name = "get_group_file_download_url";
1845
+ schema = z.object({
1846
+ ...groupIdField,
1847
+ ...fileIdField
1848
+ });
1849
+ async _handle(payload) {
1850
+ return { download_url: await this.api.richMediaApi.getGroupFileDownloadUrl(String(payload.group_id), payload.file_id) };
1851
+ }
1852
+ };
1853
+ /** rename_group_folder:重命名群文件夹(renameGroupFile 同款形态,待校准)。 */
1854
+ var RenameGroupFolderAction = class extends MilkyApiAction {
1855
+ name = "rename_group_folder";
1856
+ schema = z.object({
1857
+ ...groupIdField,
1858
+ folder_id: z.string().min(1),
1859
+ new_folder_name: z.string().min(1)
1860
+ });
1861
+ async _handle(payload) {
1862
+ await this.api.richMediaApi.renameGroupFolder(String(payload.group_id), payload.folder_id, payload.new_folder_name);
1863
+ return {};
1864
+ }
1865
+ };
1644
1866
  //#endregion
1645
1867
  //#region src/milky/action/profile.ts
1646
1868
  /**
@@ -1735,11 +1957,6 @@ var GetCSRFTokenAction = class extends MilkyApiAction {
1735
1957
  };
1736
1958
  //#endregion
1737
1959
  //#region src/milky/action/social.ts
1738
- /**
1739
- * Milky 社交类动作(P0:get_friend_list / get_group_list;
1740
- * P1 +5:send_profile_like / delete_friend / get_friend_requests /
1741
- * accept_friend_request / reject_friend_request)
1742
- */
1743
1960
  const emptySchema$1 = z.object({}).strict();
1744
1961
  /** get_friend_list:获取好友列表。 */
1745
1962
  var GetFriendListAction = class extends MilkyApiAction {
@@ -1839,6 +2056,84 @@ var RejectFriendRequestAction = class extends HandleFriendRequestBase {
1839
2056
  name = "reject_friend_request";
1840
2057
  accept = false;
1841
2058
  };
2059
+ /** send_friend_nudge:发送好友戳一戳(is_self=true 戳自己)。 */
2060
+ var SendFriendNudgeAction = class extends MilkyApiAction {
2061
+ name = "send_friend_nudge";
2062
+ schema = z.object({
2063
+ user_id: z.number().int().min(10001),
2064
+ is_self: optBoolNudge
2065
+ });
2066
+ async _handle(payload) {
2067
+ const callerUid = requireSelfUid(this.api);
2068
+ const receiverUid = payload.is_self === true ? callerUid : await this.api.resolveUidFromUin(payload.user_id);
2069
+ const peer = await this.api.resolvePeer("friend", payload.user_id);
2070
+ await this.api.msgApi.sendNudge(peer, {
2071
+ receiverUid,
2072
+ callerUid
2073
+ });
2074
+ return {};
2075
+ }
2076
+ };
2077
+ /** get_peer_pins:获取置顶会话(RecentContactApi isTop 过滤,实体经现有翻译)。 */
2078
+ var GetPeerPinsAction = class extends MilkyApiAction {
2079
+ name = "get_peer_pins";
2080
+ schema = z.object({});
2081
+ async _handle() {
2082
+ const pinned = await this.api.recentContactApi.getPinnedContacts();
2083
+ const friendUids = pinned.filter((p) => p.scene === "friend").map((p) => p.peerUid);
2084
+ const groupCodes = pinned.filter((p) => p.scene === "group").map((p) => p.peerUid);
2085
+ return {
2086
+ friends: await pinnedFriendEntities(this.api, friendUids),
2087
+ groups: await pinnedGroupEntities(this.api, groupCodes)
2088
+ };
2089
+ }
2090
+ };
2091
+ /** set_peer_pin:设置/取消会话置顶(is_pinned 缺省 true)。 */
2092
+ var SetPeerPinAction = class extends MilkyApiAction {
2093
+ name = "set_peer_pin";
2094
+ schema = z.object({
2095
+ message_scene: z.enum([
2096
+ "friend",
2097
+ "group",
2098
+ "temp"
2099
+ ]),
2100
+ peer_id: z.number().int().min(10001),
2101
+ is_pinned: optBoolNudge
2102
+ });
2103
+ async _handle(payload) {
2104
+ const peer = await this.api.resolvePeer(payload.message_scene, payload.peer_id);
2105
+ await this.api.msgApi.setContactTop(peer, payload.is_pinned ?? true);
2106
+ return {};
2107
+ }
2108
+ };
2109
+ /** 置顶好友实体(uid 匹配全量好友列表;未命中跳过)。 */
2110
+ async function pinnedFriendEntities(api, uids) {
2111
+ if (uids.length === 0) return [];
2112
+ const all = await api.friendApi.getFriendList();
2113
+ const byUid = new Map(all.map((f) => [f.uid, f]));
2114
+ return uids.flatMap((uid) => {
2115
+ const f = byUid.get(uid);
2116
+ return f !== void 0 ? [toFriendEntity(f, f.uin, f.nickname)] : [];
2117
+ });
2118
+ }
2119
+ /** 置顶群实体(groupCode 匹配群列表,缓存优先;未命中跳过)。 */
2120
+ async function pinnedGroupEntities(api, groupCodes) {
2121
+ if (groupCodes.length === 0) return [];
2122
+ const groups = api.groupCache !== void 0 ? await api.groupCache.listGroupsRefreshed() : await api.groupApi.getGroupList(true);
2123
+ const byCode = new Map(groups.map((g) => [g.groupCode, g]));
2124
+ return groupCodes.flatMap((code) => {
2125
+ const g = byCode.get(code);
2126
+ return g !== void 0 ? [groupListToEntity(g)] : [];
2127
+ });
2128
+ }
2129
+ /** self.uid 必备(nudge 发送的 callerUid)。 */
2130
+ function requireSelfUid(api) {
2131
+ const uid = api.self.uid;
2132
+ if (uid === void 0 || uid === "") throw kernelError("登录身份缺 uid(无法发起戳一戳)", "INVALID_PARAM");
2133
+ return uid;
2134
+ }
2135
+ /** 可选布尔(social.ts 本文件原无 optBool,本地定义避免全局重名)。 */
2136
+ const optBoolNudge = z.boolean().nullable().optional();
1842
2137
  //#endregion
1843
2138
  //#region src/milky/action/system.ts
1844
2139
  /**
@@ -1878,11 +2173,11 @@ var GetImplInfoAction = class extends MilkyApiAction {
1878
2173
  //#endregion
1879
2174
  //#region src/milky/action/index.ts
1880
2175
  /**
1881
- * Milky 动作注册表(P0 9 个 + P1 40 个 = 49 个)
2176
+ * Milky 动作注册表(P0 9 + P1 40 + P2 批次二 15 = 64 个)
1882
2177
  *
1883
- * P0 面 = docs/protocols/milky.md §9 P0 清单;P1 面 = §9 P1 清单(群管/精华/
1884
- * 群文件/好友操作/票据/历史与转发)。延后项(nudge 发送 / rename_folder /
1885
- * download_url / resource 反查 / 置顶 / 公告 / 上传)见 milky.md §6。
2178
+ * P0/P1 面 = docs/protocols/milky.md §9 清单;批次二 = §0.7(nudge / 公告 /
2179
+ * 头衔 / 群头像 / peer_pins / 上传 / download_url / rename_folder / 收藏表情 /
2180
+ * resource 反查)。
1886
2181
  */
1887
2182
  /** Milky 动作注册表(P0+P1 49 动作)。 */
1888
2183
  function createMilkyActionRegistry(opts) {
@@ -1937,6 +2232,21 @@ function createMilkyActionRegistry(opts) {
1937
2232
  registry.register(new PersistGroupFileAction(api));
1938
2233
  registry.register(new CreateGroupFolderAction(api));
1939
2234
  registry.register(new DeleteGroupFolderAction(api));
2235
+ registry.register(new SendFriendNudgeAction(api));
2236
+ registry.register(new GetPeerPinsAction(api));
2237
+ registry.register(new SetPeerPinAction(api));
2238
+ registry.register(new SendGroupNudgeAction(api));
2239
+ registry.register(new GetGroupAnnouncementsAction(api));
2240
+ registry.register(new SendGroupAnnouncementAction(api));
2241
+ registry.register(new DeleteGroupAnnouncementAction(api));
2242
+ registry.register(new SetGroupAvatarAction(api));
2243
+ registry.register(new SetGroupMemberSpecialTitleAction(api));
2244
+ registry.register(new GetCustomFaceUrlListAction(api));
2245
+ registry.register(new GetResourceTempUrlAction(api));
2246
+ registry.register(new UploadPrivateFileAction(api));
2247
+ registry.register(new UploadGroupFileAction(api));
2248
+ registry.register(new GetGroupFileDownloadUrlAction(api));
2249
+ registry.register(new RenameGroupFolderAction(api));
1940
2250
  return registry;
1941
2251
  }
1942
2252
  //#endregion