diff --git a/db/appConfig/index.ts b/db/appConfig/index.ts index eaaaa14..31268c9 100644 --- a/db/appConfig/index.ts +++ b/db/appConfig/index.ts @@ -1,10 +1,6 @@ -import { RecordModel } from "pocketbase"; import pbClient from "../pbClient"; import { managePb404 } from "../../utils/pbTools"; - -export interface AppConfigRecordModel extends RecordModel { - value: string; -} +import { DB } from "../../types"; /** * 获取配置 @@ -12,7 +8,7 @@ export interface AppConfigRecordModel extends RecordModel { * @returns */ const get = async (key: string) => { - const config = await managePb404( + const config = await managePb404( async () => await await pbClient.collection("config").getFirstListItem(`key='${key}'`) ); diff --git a/db/messageGroup/index.ts b/db/messageGroup/index.ts index cdaec2d..9bc48d9 100644 --- a/db/messageGroup/index.ts +++ b/db/messageGroup/index.ts @@ -1,23 +1,9 @@ +import { DB } from "../../types"; import { managePb404 } from "../../utils/pbTools"; import pbClient from "../pbClient"; -export interface PBMessageGroup { - collectionId: string; - collectionName: string; - updated: string; - created: string; - desc: string; - id: string; - name: string; - email?: string[]; - chat_id?: string[]; - open_id?: string[]; - union_id?: string[]; - user_id?: string[]; -} - const getOne = (groupId: string) => - managePb404( + managePb404( async () => await pbClient.collection("message_group").getOne(groupId) ); diff --git a/index.ts b/index.ts index 03d860a..ca39de1 100644 --- a/index.ts +++ b/index.ts @@ -1,5 +1,6 @@ import { manageBotReq } from "./routes/bot"; import { manageMessageReq } from "./routes/message"; +import { manageMicroAppReq } from "./routes/microApp"; import { initSchedule } from "./schedule"; initSchedule(); @@ -14,6 +15,8 @@ Bun.serve({ if (url.pathname === "/bot") return await manageBotReq(req); // 消息代理发送 if (url.pathname === "/message") return await manageMessageReq(req); + // 小程序 + if (url.pathname.startsWith("/micro_app")) return await manageMicroAppReq(req); // 其他 return new Response("hello, glade to see you!"); } catch (error: any) { diff --git a/routes/bot/activeMsg.ts b/routes/bot/actionMsg.ts similarity index 65% rename from routes/bot/activeMsg.ts rename to routes/bot/actionMsg.ts index 3b4a567..8f20614 100644 --- a/routes/bot/activeMsg.ts +++ b/routes/bot/actionMsg.ts @@ -1,9 +1,14 @@ import { sleep } from "bun"; import { getActionType, getIsActionMsg } from "../../utils/msgTools"; -import { LarkUserAction } from "../../types"; -import lark from "../../service/lark"; -const makeChatIdCard = async (body: LarkUserAction) => { +import service from "../../services"; +import { LarkAction } from "../../types"; + +/** + * 返回ChatId卡片 + * @param {LarkAction.Data} body + */ +const makeChatIdCard = async (body: LarkAction.Data) => { await sleep(500); return JSON.stringify({ type: "template", @@ -25,9 +30,9 @@ const ACTION_MAP = { /** * 处理按钮点击事件 - * @param {LarkUserAction} body + * @param {LarkAction.Data} body */ -const manageBtnClick = async (body: LarkUserAction) => { +const manageBtnClick = async (body: LarkAction.Data) => { const { action } = body?.action?.value as { action: keyof typeof ACTION_MAP; }; @@ -37,15 +42,15 @@ const manageBtnClick = async (body: LarkUserAction) => { const card = await func(body); if (!card) return; // 更新飞书的卡片 - await lark.updateCard(body.open_message_id, card); + await service.lark.message.update(body.open_message_id, card); }; /** * 处理Action消息 - * @param {LarkUserAction} body + * @param {LarkAction.Data} body * @returns {boolean} 是否在本函数中处理了消息 */ -export const manageActionMsg = (body: LarkUserAction) => { +export const manageActionMsg = (body: LarkAction.Data) => { // 过滤非Action消息 if (!getIsActionMsg(body)) { return false; diff --git a/routes/bot/eventMsg.ts b/routes/bot/eventMsg.ts index 44da930..3762015 100644 --- a/routes/bot/eventMsg.ts +++ b/routes/bot/eventMsg.ts @@ -1,6 +1,5 @@ -import { fetchCIMonitor, fetchReportCollector } from "../../service"; -import lark from "../../service/lark"; -import { LarkMessageEvent } from "../../types"; +import service from "../../services"; +import { LarkEvent } from "../../types"; import { getChatId, getChatType, @@ -12,10 +11,10 @@ import { /** * 是否为P2P或者群聊并且艾特了小煎蛋 - * @param {LarkMessageEvent} body + * @param {LarkEvent.Data} body * @returns {boolean} 是否为P2P或者群聊并且艾特了小煎蛋 */ -const getIsP2pOrGroupAtBot = (body: LarkMessageEvent) => { +const getIsP2pOrGroupAtBot = (body: LarkEvent.Data) => { const isP2p = getChatType(body) === "p2p"; const isAtBot = getMentions(body)?.some?.( (mention) => mention.name === "小煎蛋" @@ -25,10 +24,10 @@ const getIsP2pOrGroupAtBot = (body: LarkMessageEvent) => { /** * 过滤出非法消息,如果发表情包就直接发回去 - * @param {LarkMessageEvent} body + * @param {LarkEvent.Data} body * @returns {boolean} 是否为非法消息 */ -const filterIllegalMsg = (body: LarkMessageEvent) => { +const filterIllegalMsg = (body: LarkEvent.Data) => { // 没有chatId的消息不处理 const chatId = getChatId(body); if (!chatId) return true; @@ -49,7 +48,7 @@ const filterIllegalMsg = (body: LarkMessageEvent) => { // 发表情包就直接发回去 if (msgType === "sticker") { const content = body?.event?.message?.content; - lark.sendMsg("chat_id", chatId, "sticker", content); + service.lark.message.send("chat_id", chatId, "sticker", content); } // 非表情包只在私聊或者群聊中艾特小煎蛋时才回复 @@ -57,7 +56,7 @@ const filterIllegalMsg = (body: LarkMessageEvent) => { const content = JSON.stringify({ text: "哇!这是什么东东?我只懂普通文本啦![可爱]", }); - lark.sendMsg("chat_id", chatId, "text", content); + service.lark.message.send("chat_id", chatId, "text", content); } // 非纯文本,全不放行 @@ -81,7 +80,7 @@ const manageIdMsg = async (chatId: string) => { }, }, }); - lark.sendMsg("chat_id", chatId, "interactive", content); + service.lark.message.send("chat_id", chatId, "interactive", content); }; /** @@ -89,7 +88,7 @@ const manageIdMsg = async (chatId: string) => { * @param body - 消息体 * @returns */ -const manageCMDMsg = (body: LarkMessageEvent) => { +const manageCMDMsg = (body: LarkEvent.Data) => { const text = getMsgText(body); const chatId = getChatId(body); if (text === "/id") { @@ -97,16 +96,16 @@ const manageCMDMsg = (body: LarkMessageEvent) => { return true; } if (text === "/ci") { - fetchCIMonitor(chatId); + service.attach.ciMonitor(chatId); return true; } if (text.includes("share") && text.includes("简报")) { - fetchReportCollector(body); + service.attach.reportCollector(body); // 这个用时比较久,先发一条提醒用户收到了请求 const content = JSON.stringify({ text: "正在为您收集简报,请稍等片刻~", }); - lark.sendMsg("chat_id", chatId, "text", content); + service.lark.message.send("chat_id", chatId, "text", content); return true; } return false; @@ -114,9 +113,9 @@ const manageCMDMsg = (body: LarkMessageEvent) => { /** * 回复引导消息 - * @param {LarkMessageEvent} body + * @param {LarkEvent.Data} body */ -const replyGuideMsg = async (body: LarkMessageEvent) => { +const replyGuideMsg = async (body: LarkEvent.Data) => { const chatId = getChatId(body); const content = JSON.stringify({ type: "template", @@ -131,7 +130,7 @@ const replyGuideMsg = async (body: LarkMessageEvent) => { }, }, }); - await lark.sendMsg("chat_id", chatId, "interactive", content); + await service.lark.message.send("chat_id", chatId, "interactive", content); }; /** @@ -139,7 +138,7 @@ const replyGuideMsg = async (body: LarkMessageEvent) => { * @param {LarkUserAction} body * @returns {boolean} 是否在本函数中处理了消息 */ -export const manageEventMsg = (body: LarkMessageEvent) => { +export const manageEventMsg = (body: LarkEvent.Data) => { // 过滤非Event消息 if (!getIsEventMsg(body)) { return false; diff --git a/routes/bot/index.ts b/routes/bot/index.ts index a49c572..4c3df46 100644 --- a/routes/bot/index.ts +++ b/routes/bot/index.ts @@ -1,4 +1,4 @@ -import { manageActionMsg } from "./activeMsg"; +import { manageActionMsg } from "./actionMsg"; import { manageEventMsg } from "./eventMsg"; export const manageBotReq = async (req: Request) => { diff --git a/routes/message/index.ts b/routes/message/index.ts index b8db01f..5687b23 100644 --- a/routes/message/index.ts +++ b/routes/message/index.ts @@ -1,24 +1,9 @@ import db from "../../db"; -import lark from "../../service/lark"; -import { MsgType, ReceiveIDType } from "../../types"; +import service from "../../services"; +import { LarkServer, MsgProxy } from "../../types"; -interface BaseMsg { - msg_type: MsgType; - content: string; -} -interface GroupMsg extends BaseMsg { - group_id: string; -} - -interface NormalMsg extends BaseMsg { - receive_id: string; - receive_id_type: ReceiveIDType; -} - -type MessageReqJson = GroupMsg & NormalMsg; - -const validateMessageReq = (body: MessageReqJson) => { +const validateMessageReq = (body: MsgProxy.Body) => { if (!body.group_id && !body.receive_id) { return new Response("group_id or receive_id is required", { status: 400 }); } @@ -35,7 +20,7 @@ const validateMessageReq = (body: MessageReqJson) => { }; export const manageMessageReq = async (req: Request) => { - const body = (await req.json()) as MessageReqJson; + const body = (await req.json()) as MsgProxy.Body; // 校验参数 const validateRes = validateMessageReq(body); if (validateRes) return validateRes; @@ -68,11 +53,11 @@ export const manageMessageReq = async (req: Request) => { const { chat_id, open_id, union_id, user_id, email } = group; // 构造发送消息函数 - const makeSendFunc = (receive_id_type: ReceiveIDType) => { + const makeSendFunc = (receive_id_type: LarkServer.ReceiveIDType) => { return (receive_id: string) => { sendList.push( - lark - .sendMsg(receive_id_type, receive_id, body.msg_type, finalContent) + service.lark.message + .send(receive_id_type, receive_id, body.msg_type, finalContent) .then((res) => { sendRes[receive_id_type][receive_id] = res; }) @@ -90,8 +75,8 @@ export const manageMessageReq = async (req: Request) => { if (body.receive_id && body.receive_id_type) { sendList.push( - lark - .sendMsg( + service.lark.message + .send( body.receive_id_type, body.receive_id, body.msg_type, diff --git a/routes/message/readme.md b/routes/message/readme.md deleted file mode 100644 index b62a325..0000000 --- a/routes/message/readme.md +++ /dev/null @@ -1 +0,0 @@ -# 批量发送消息,给已经订阅的用户和群组发送消息 diff --git a/routes/microApp/index.ts b/routes/microApp/index.ts new file mode 100644 index 0000000..d25d301 --- /dev/null +++ b/routes/microApp/index.ts @@ -0,0 +1,7 @@ +export const manageMicroAppReq = async (req: Request) => { + const url = new URL(req.url); + const body = (await req.json()) as any; + console.log("🚀 ~ manageMicroAppReq ~ body:", body); + + return new Response("hello, glade to see you!"); +}; diff --git a/routes/microApp/login.ts b/routes/microApp/login.ts new file mode 100644 index 0000000..e69de29 diff --git a/service/index.ts b/service/index.ts deleted file mode 100644 index 4d29755..0000000 --- a/service/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { LarkMessageEvent } from "../types"; - -/** - * 请求 CI 监控 - */ -export const fetchCIMonitor = async (chat_id: string) => { - try { - const res = await fetch( - `https://ci-monitor.xiaomiwh.cn/ci?chat_id=${chat_id}` - ); - return ((await res.json()) as string) || ""; - } catch { - return ""; - } -}; - -/** - * 请求简报收集器 - * @param body - * @returns - */ -export const fetchReportCollector = async (body: LarkMessageEvent) => { - const url = "https://report.imoaix.cn/report"; - // 将body作为请求体,post到url - try { - const res = await fetch(url, { - method: "POST", - body: JSON.stringify(body), - headers: { - "Content-Type": "application/json", - }, - }); - return ((await res.json()) as string) || ""; - } catch { - return ""; - } -}; diff --git a/service/lark.ts b/service/lark.ts deleted file mode 100644 index 6934340..0000000 --- a/service/lark.ts +++ /dev/null @@ -1,125 +0,0 @@ -import db from "../db"; -import { MsgType, ReceiveIDType, ServerResponse } from "../types"; - -/** - * 发送消息 - * @param func fetch - * @returns - */ -const manageFetch = async (func: Function) => { - try { - const res = await func(); - const data = (await res.json()) as ServerResponse; - console.log("🚀 ~ manageFetch ~ data:", data); - return data; - } catch (error) { - console.log("🚀 ~ manageFetch ~ error:", error); - return { - code: 1, - data: null, - msg: "sendMsg fetch error", - }; - } -}; - -/** - * 获取header - * @returns header - */ -const getHeaders = async () => { - const tenant_access_token = await db.tenantAccessToken.get(); - return { - "Content-Type": "application/json", - Authorization: `Bearer ${tenant_access_token}`, - }; -}; - -/** - * 发送卡片 - * @param {ReceiveIDType} receive_id_type 消息接收者id类型 open_id/user_id/union_id/email/chat_id - * @param {string} receive_id 消息接收者的ID,ID类型应与查询参数receive_id_type 对应 - * @param {MsgType} msg_type 消息类型 包括:text、post、image、file、audio、media、sticker、interactive、share_chat、share_user - * @param {string} content 消息内容,JSON结构序列化后的字符串。不同msg_type对应不同内容 - */ -const sendMsg = async ( - receive_id_type: ReceiveIDType, - receive_id: string, - msg_type: MsgType, - content: string -) => { - const URL = `https://open.f.mioffice.cn/open-apis/im/v1/messages?receive_id_type=${receive_id_type}`; - const headers = await getHeaders(); - return await manageFetch(() => - fetch(URL, { - method: "POST", - headers, - body: JSON.stringify({ receive_id, msg_type, content }), - }) - ); -}; - -/** - * 更新卡片 - * @param {string} message_id 消息id - * @param {string} content 消息内容,JSON结构序列化后的字符串。不同msg_type对应不同内容 - */ -const updateCard = async (message_id: string, content: string) => { - const URL = `https://open.f.mioffice.cn/open-apis/im/v1/messages/${message_id}`; - const headers = await getHeaders(); - return await manageFetch(() => - fetch(URL, { - method: "PATCH", - headers, - body: JSON.stringify({ content }), - }) - ); -}; - -/** - * 发送某人可见的卡片 - * @param {string} chat_id 对话流ID - * @param {string} open_id 消息接收者的ID - * @param {MsgType} msg_type 消息类型 包括:text、post、image、file、audio、media、sticker、interactive、share_chat、share_user - * @param {*} card 消息卡片的描述内容,注意不是String - */ -const sendEphemeralMsg = async ( - chat_id: string, - open_id: string, - msg_type: MsgType, - card: any -) => { - const URL = `https://open.f.mioffice.cn/open-apis/ephemeral/v1/send`; - const headers = await getHeaders(); - return await manageFetch(() => - fetch(URL, { - method: "POST", - headers, - body: JSON.stringify({ chat_id, open_id, msg_type, card }), - }) - ); -}; - -/** - * 删除某人可见的卡片 - * @param message_id 消息id - */ -const delEphemeralMsg = async (message_id: string) => { - const URL = `https://open.f.mioffice.cn/open-apis/ephemeral/v1/delete`; - const headers = await getHeaders(); - return await manageFetch(() => - fetch(URL, { - method: "POST", - headers, - body: JSON.stringify({ message_id }), - }) - ); -}; - -const lark = { - sendMsg, - updateCard, - sendEphemeralMsg, - delEphemeralMsg, -}; - -export default lark; diff --git a/services/attach/index.ts b/services/attach/index.ts new file mode 100644 index 0000000..32bd0f7 --- /dev/null +++ b/services/attach/index.ts @@ -0,0 +1,37 @@ +import { LarkEvent } from "../../types"; +import netTool from "../netTool"; + +/** + * 请求 CI 监控 + */ +const ciMonitor = async (chat_id: string) => { + const URL = `https://ci-monitor.xiaomiwh.cn/ci?chat_id=${chat_id}`; + try { + const res = await netTool.get(URL); + return (res as string) || ""; + } catch { + return ""; + } +}; + +/** + * 请求简报收集器 + * @param body + * @returns + */ +const reportCollector = async (body: LarkEvent.Data) => { + const URL = "https://report.imoaix.cn/report"; + try { + const res = await netTool.post(URL, body); + return (res as string) || ""; + } catch { + return ""; + } +}; + +const attach = { + ciMonitor, + reportCollector, +}; + +export default attach; diff --git a/services/index.ts b/services/index.ts new file mode 100644 index 0000000..578da2d --- /dev/null +++ b/services/index.ts @@ -0,0 +1,9 @@ +import attach from "./attach"; +import lark from "./lark"; + +const service = { + attach, + lark, +}; + +export default service; diff --git a/services/lark/index.ts b/services/lark/index.ts new file mode 100644 index 0000000..a7871b6 --- /dev/null +++ b/services/lark/index.ts @@ -0,0 +1,10 @@ +import message from "./message"; +import user from "./user"; + + +const lark = { + message, + user, +}; + +export default lark; diff --git a/services/lark/larkNetTool.ts b/services/lark/larkNetTool.ts new file mode 100644 index 0000000..8492095 --- /dev/null +++ b/services/lark/larkNetTool.ts @@ -0,0 +1,63 @@ +import db from "../../db"; +import { LarkServer } from "../../types"; +import netTool from "../netTool"; + +const larkNetTool = async ({ + url, + method, + params, + data, + headers, +}: { + url: string; + method: string; + params?: any; + data?: any; + headers?: any; +}): Promise => { + const headersWithAuth = { + Authorization: `Bearer ${await db.tenantAccessToken.get()}`, + ...headers, + }; + return netTool({ + url, + method, + params, + data, + headers: headersWithAuth, + }).catch((error) => { + console.error("网络请求异常", error); + return { + code: 1, + data: null, + msg: "网络请求异常", + } as T; + }); +}; + +larkNetTool.get = ( + url: string, + params?: any, + headers?: any +): Promise => larkNetTool({ url, method: "get", params, headers }); + +larkNetTool.post = ( + url: string, + data?: any, + params?: any, + headers?: any +): Promise => larkNetTool({ url, method: "post", data, params, headers }); + +larkNetTool.del = ( + url: string, + data: any, + headers?: any +): Promise => larkNetTool({ url, method: "delete", data, headers }); + +larkNetTool.patch = ( + url: string, + data: any, + headers?: any +): Promise => larkNetTool({ url, method: "patch", data, headers }); + +export default larkNetTool; diff --git a/services/lark/message.ts b/services/lark/message.ts new file mode 100644 index 0000000..89e82aa --- /dev/null +++ b/services/lark/message.ts @@ -0,0 +1,40 @@ +import { LarkServer } from "../../types/larkServer"; +import larkNetTool from "./larkNetTool"; + +/** + * 发送卡片 + * @param {LarkServer.ReceiveIDType} receive_id_type 消息接收者id类型 open_id/user_id/union_id/email/chat_id + * @param {string} receive_id 消息接收者的ID,ID类型应与查询参数receive_id_type 对应 + * @param {MsgType} msg_type 消息类型 包括:text、post、image、file、audio、media、sticker、interactive、share_chat、share_user + * @param {string} content 消息内容,JSON结构序列化后的字符串。不同msg_type对应不同内容 + */ +const send = async ( + receive_id_type: LarkServer.ReceiveIDType, + receive_id: string, + msg_type: LarkServer.MsgType, + content: string +) => { + const URL = `https://open.f.mioffice.cn/open-apis/im/v1/messages?receive_id_type=${receive_id_type}`; + return larkNetTool.post(URL, { + receive_id, + msg_type, + content, + }); +}; + +/** + * 更新卡片 + * @param {string} message_id 消息id + * @param {string} content 消息内容,JSON结构序列化后的字符串。不同msg_type对应不同内容 + */ +const update = async (message_id: string, content: string) => { + const URL = `https://open.f.mioffice.cn/open-apis/im/v1/messages/${message_id}`; + return larkNetTool.patch(URL, { content }); +}; + +const message = { + send, + update, +}; + +export default message; diff --git a/services/lark/user.ts b/services/lark/user.ts new file mode 100644 index 0000000..469e9b5 --- /dev/null +++ b/services/lark/user.ts @@ -0,0 +1,45 @@ +import { LarkServer } from "../../types/larkServer"; +import larkNetTool from "./larkNetTool"; + +/** + * 登录凭证校验 + * @param code + * @returns + */ +const code2Session = async (code: string) => { + const URL = `https://open.f.mioffice.cn/open-apis/mina/v2/tokenLoginValidate`; + return larkNetTool.post(URL, { code }); +}; + +/** + * 获取用户信息 + * @param user_id + * @returns + */ +const getUser = async (user_id: string) => { + const URL = `https://open.f.mioffice.cn/open-apis/contact/v3/users/${user_id}`; + return larkNetTool.get(URL, { + user_id_type: "user_id", + }); +}; + +/** + * 批量获取用户信息 + * @param user_ids + * @returns + */ +const getMultiUser = async (user_ids: string[]) => { + const URL = `https://open.f.mioffice.cn/open-apis/user/v1/batch_get`; + return larkNetTool.get(URL, { + user_ids, + user_id_type: "user_id", + }); +}; + +const user = { + code2Session, + getMultiUser, + getUser, +}; + +export default user; diff --git a/services/netTool.ts b/services/netTool.ts new file mode 100644 index 0000000..01eb280 --- /dev/null +++ b/services/netTool.ts @@ -0,0 +1,53 @@ +interface NetGetParams { + url: string; + method: string; + params?: any; + data?: any; + headers?: any; +} + +const netTool = ({ + url, + method, + params, + data, + headers, +}: NetGetParams): Promise => { + let fullUrl = url; + if (params) { + const queryString = new URLSearchParams(params).toString(); + fullUrl = `${url}?${queryString}`; + } + + return fetch(fullUrl, { + method, + body: JSON.stringify(data), + headers: { + "Content-Type": "application/json", + ...headers, + }, + }).then((response) => { + if (!response.ok) { + throw new Error("网络响应异常"); + } + return response.json() as Promise; + }); +}; + +netTool.get = (url: string, params?: any, headers?: any): Promise => + netTool({ url, method: "get", params, headers }); + +netTool.post = ( + url: string, + data?: any, + params?: any, + headers?: any +): Promise => netTool({ url, method: "post", data, params, headers }); + +netTool.del = (url: string, data: any, headers?: any): Promise => + netTool({ url, method: "delete", data, headers }); + +netTool.patch = (url: string, data: any, headers?: any): Promise => + netTool({ url, method: "patch", data, headers }); + +export default netTool; diff --git a/types/db.ts b/types/db.ts new file mode 100644 index 0000000..658b703 --- /dev/null +++ b/types/db.ts @@ -0,0 +1,21 @@ +import { RecordModel } from "pocketbase"; + +export namespace DB { + export interface AppConfig extends RecordModel { + value: string; + } + export interface MessageGroup extends RecordModel { + collectionId: string; + collectionName: string; + updated: string; + created: string; + desc: string; + id: string; + name: string; + email?: string[]; + chat_id?: string[]; + open_id?: string[]; + union_id?: string[]; + user_id?: string[]; + } +} diff --git a/types/index.ts b/types/index.ts index 28d6d74..15ec450 100644 --- a/types/index.ts +++ b/types/index.ts @@ -1,466 +1,7 @@ -/** - * 用户信息 - */ -export interface User { - /** - * id - */ - id: string; - /** - * 用户名 - * @example zhaoyingbo - */ - userId: string; - /** - * open_id - */ - openId: string; - /** - * 提醒列表 - */ - remindList: string[]; -} +import type { DB } from "./db"; +import type { LarkAction } from "./larkAction"; +import type { LarkEvent } from "./larkEvent"; +import type { LarkServer } from "./larkServer"; +import type { MsgProxy } from "./msgProxy"; -/** - * 提醒列表 - */ -export interface Remind { - /** - * id - */ - id: string; - /** - * 所有者信息,绑定用户表的id - */ - owner: string; - /** - * 消息Id - */ - messageId: string; - /** - * 接收者类型 - */ - subscriberType: "open_id" | "user_id" | "union_id" | "email" | "chat_id"; - /** - * 接收者Id - */ - subscriberId: string; - /** - * 是否需要回复,不需要回复的也不会重复提醒 - */ - needReply: boolean; - /** - * 延迟时间 - */ - delayTime: number; - /** - * 卡片信息,用于绘制初始卡片、确认卡片、取消卡片、延迟卡片 - */ - cardInfo: { - /** - * 提醒标题,必须要有 - */ - title: string; - /** - * 插图key - */ - imageKey?: string; - /** - * 提醒内容,为空不显示 - */ - content?: string; - /** - * 确认文本,为空不显示,为需要回复卡片时,如果为空则默认为“完成” - */ - confirmText?: string; - /** - * 取消文本,为空不显示 - */ - cancelText?: string; - /** - * 延迟文本,为空不显示 - */ - delayText?: string; - } | null; - /** - * 卡片模板信息 - */ - templateInfo: { - /** - * 卡片模板ID,会注入变量 - * ${owner} 所有者 - * ${remindTime} 提醒时间 - */ - pendingTemplateId: string; - /** - * 交互之后的卡片模板ID,如果有这个就不会用下边三个但是都会注入变量 - * ${owner} 所有者 - * ${remindTime} 提醒时间 - * ${result} 交互结果,会读卡片按钮绑定的变量text,如果没有则是绑定的result对应的 已确认、已取消、已延迟 - * ${interactTime} 交互时间 - */ - interactedTemplateId: string; - /** - * 确认之后的卡片模板ID - */ - confirmedTemplateId: string; - /** - * 取消之后的卡片模板ID - */ - cancelededTemplateId: string; - /** - * 延迟之后的卡片模板ID - */ - delayedTemplateId: string; - } | null; - - /** - * 提醒时间 - */ - remindTimes: RemindTime[]; - - /** - * 是否启用 - */ - enabled: boolean; - /** - * 下次提醒的时间,格式为yyyy-MM-dd HH:mm - */ - nextRemindTime: string; - /** - * 下次提醒时间的中文 - */ - nextRemindTimeCHS: string; -} - -/** - * 提醒时间 - * 为了支持多个时间点提醒,将时间存成数组 - */ -export interface RemindTime { - /** - * 重复类型 - * single: 一次性 - * daily: 每天 - * weekly: 每周 - * monthly: 每月 - * yearly: 每年 - * workday: 工作日 - * holiday: 节假日 - */ - frequency: - | "single" - | "daily" - | "weekly" - | "monthly" - | "yearly" - | "workday" - | "holiday"; - /** - * 提醒时间,格式为HH:mm, single类型时仅作展示用,类型为yyyy-MM-dd HH:mm - */ - time: string; - /** - * 星期几[1-7],当frequency为weekly时有效 - */ - daysOfWeek: number[]; - /** - * 每月的几号[1-31],当frequency为monthly时有效 - */ - daysOfMonth: number[]; - /** - * 每年的哪天提醒,当frequency为 yearly 时有效,格式为MM-dd - */ - dayOfYear: string; -} - -/** - * 提醒记录 - * 记录提醒时间,回答结果等 - */ -export interface RemindRecord { - /** - * 记录Id - */ - id: string; - /** - * 关联的提醒Id - */ - remindId: string; - /** - * 发送的卡片Id - */ - messageId: string; - /** - * 提醒状态 - * pending: 待确认 - * delay: 已延迟 - * confirmed: 已确认 - * canceled: 已取消 - */ - status: "pending" | "delayed" | "confirmed" | "canceled"; - /** - * 本次提醒时间,格式为yyyy-MM-dd HH:mm - */ - remindTime: string; - /** - * 用户交互的时间,格式为yyyy-MM-dd HH:mm - */ - interactTime: string; - /** - * 用户回答的结果,类似每天 07:00 - */ - result: object; -} - -/** - * 消息事件头 - */ -export interface Header { - /** - * 事件ID - * @example 0f8ab23b60993cf8dd15c8cde4d7b0f5 - */ - event_id: string; - /** - * token - * @example tV9djUKSjzVnekV7xTg2Od06NFTcsBnj - */ - token: string; - /** - * 创建时间戳 - * @example 1693565712117 - */ - create_time: string; - /** - * 事件类型 - * @example im.message.receive_v1 - */ - event_type: string; - /** - * tenant_key - * @example 2ee61fe50f4f1657 - */ - tenant_key: string; - /** - * app_id - * @example cli_a1eff35b43b89063 - */ - app_id: string; -} - -/** - * 用户ID信息 - */ -export interface UserIdInfo { - /** - * 用户标记 - * @example ou_032f507d08f9a7f28b042fcd086daef5 - */ - open_id: string; - /** - * 用户标记 - * @example on_7111660fddd8302ce47bf1999147c011 - */ - union_id: string; - /** - * 用户名 - * @example zhaoyingbo - */ - user_id: string; -} - -/** - * 被AT的人的信息 - */ -export interface Mention { - /** - * 被艾特的人的ID信息 - */ - id: UserIdInfo; - /** - * 对应到文本内的内容 - * @example "@_user_1" - */ - key: string; - /** - * 用户名 - * @example 小煎蛋 - */ - name: string; - /** - * 应用ID - * @example 2ee61fe50f4f1657 - */ - tenant_key: string; -} - -/** - * 消息内容信息 - */ -export interface Message { - /** - * 对话流ID - * @example oc_433b1cb7a9dbb7ebe70a4e1a59cb8bb1 - */ - chat_id: string; - /** - * 消息类型 - * @example group | p2p - */ - chat_type: "group" | "p2p"; - /** - * JSON字符串文本内容 - * @example "{\"text\":\"@_user_1 测试\"}" - */ - content: string; - /** - * 消息发送时间戳 - * @example 1693565711996 - */ - create_time: string; - /** - * 被艾特的人信息 - */ - mentions?: Mention[]; - /** - * 当前消息的ID - * @example om_038fc0eceed6224a1abc1cdaa4266405 - */ - message_id: string; - /** - * 消息类型 - * @example text、post、image、file、audio、media、sticker、interactive、share_chat、share_user - */ - message_type: string; -} - -/** - * 消息发送者信息 - */ -export interface Sender { - /** - * id 相关信息 - */ - sender_id: UserIdInfo; - /** - * 发送者类型 - * @example user - */ - sender_type: string; - /** - * 应用ID - * @example 2ee61fe50f4f1657 - */ - tenant_key: string; -} - -/** - * 事件详情 - */ -export interface Event { - message: Message; - sender: Sender; -} - -/** - * 事件订阅信息 - */ -export interface LarkMessageEvent { - /** - * 协议版本 - * @example 2.0 - */ - schema: string; - /** - * 事件头 - */ - header: Header; - /** - * 事件详情 - */ - event: Event; -} - -/** - * 用户Action信息 - */ -export interface LarkUserAction { - /** - * open_id - */ - open_id: string; - /** - * 用户名 - * @example zhaoyingbo - */ - user_id: string; - /** - * 当前消息的ID - * @example om_038fc0eceed6224a1abc1cdaa4266405 - */ - open_message_id: string; - /** - * 对话流ID - * @example oc_433b1cb7a9dbb7ebe70a4e1a59cb8bb1 - */ - open_chat_id: string; - /** - * 应用ID - * @example 2ee61fe50f4f1657 - */ - tenant_key: string; - /** - * token - * @example tV9djUKSjzVnekV7xTg2Od06NFTcsBnj - */ - token: string; - /** - * 事件结果 - */ - action: { - /** - * 传的参数 - */ - value: any; - /** - * 标签名 - * @example picker_datetime - */ - tag: string; - /** - * 选择的事件 - * @example 2023-09-03 10:35 +0800 - */ - option: string; - /** - * 时区 - */ - timezone: string; - }; -} - -export type ReceiveIDType = - | "open_id" - | "user_id" - | "union_id" - | "email" - | "chat_id"; - -export type MsgType = - | "text" - | "post" - | "image" - | "file" - | "audio" - | "media" - | "sticker" - | "interactive" - | "share_chat" - | "share_user"; - -export interface ServerResponse { - code: number; - data: any; - msg: string; -} +export { DB, LarkAction, LarkEvent, LarkServer, MsgProxy }; diff --git a/types/larkAction.ts b/types/larkAction.ts new file mode 100644 index 0000000..050c68f --- /dev/null +++ b/types/larkAction.ts @@ -0,0 +1,56 @@ +export namespace LarkAction { + export interface Data { + /** + * open_id + */ + open_id: string; + /** + * 用户名 + * @example zhaoyingbo + */ + user_id: string; + /** + * 当前消息的ID + * @example om_038fc0eceed6224a1abc1cdaa4266405 + */ + open_message_id: string; + /** + * 对话流ID + * @example oc_433b1cb7a9dbb7ebe70a4e1a59cb8bb1 + */ + open_chat_id: string; + /** + * 应用ID + * @example 2ee61fe50f4f1657 + */ + tenant_key: string; + /** + * token + * @example tV9djUKSjzVnekV7xTg2Od06NFTcsBnj + */ + token: string; + /** + * 事件结果 + */ + action: { + /** + * 传的参数 + */ + value: any; + /** + * 标签名 + * @example picker_datetime + */ + tag: string; + /** + * 选择的事件 + * @example 2023-09-03 10:35 +0800 + */ + option: string; + /** + * 时区 + */ + timezone: string; + }; + } +} diff --git a/types/larkEvent.ts b/types/larkEvent.ts new file mode 100644 index 0000000..7830649 --- /dev/null +++ b/types/larkEvent.ts @@ -0,0 +1,163 @@ +export namespace LarkEvent { + /** + * 消息事件头 + */ + export interface Header { + /** + * 事件ID + * @example 0f8ab23b60993cf8dd15c8cde4d7b0f5 + */ + event_id: string; + /** + * token + * @example tV9djUKSjzVnekV7xTg2Od06NFTcsBnj + */ + token: string; + /** + * 创建时间戳 + * @example 1693565712117 + */ + create_time: string; + /** + * 事件类型 + * @example im.message.receive_v1 + */ + event_type: string; + /** + * tenant_key + * @example 2ee61fe50f4f1657 + */ + tenant_key: string; + /** + * app_id + * @example cli_a1eff35b43b89063 + */ + app_id: string; + } + /** + * 被AT的人的信息 + */ + export interface Mention { + /** + * 被艾特的人的ID信息 + */ + id: UserIdInfo; + /** + * 对应到文本内的内容 + * @example "@_user_1" + */ + key: string; + /** + * 用户名 + * @example 小煎蛋 + */ + name: string; + /** + * 应用ID + * @example 2ee61fe50f4f1657 + */ + tenant_key: string; + } + /** + * 消息内容信息 + */ + export interface Message { + /** + * 对话流ID + * @example oc_433b1cb7a9dbb7ebe70a4e1a59cb8bb1 + */ + chat_id: string; + /** + * 消息类型 + * @example group | p2p + */ + chat_type: "group" | "p2p"; + /** + * JSON字符串文本内容 + * @example "{\"text\":\"@_user_1 测试\"}" + */ + content: string; + /** + * 消息发送时间戳 + * @example 1693565711996 + */ + create_time: string; + /** + * 被艾特的人信息 + */ + mentions?: Mention[]; + /** + * 当前消息的ID + * @example om_038fc0eceed6224a1abc1cdaa4266405 + */ + message_id: string; + /** + * 消息类型 + * @example text、post、image、file、audio、media、sticker、interactive、share_chat、share_user + */ + message_type: string; + } + + /** + * 用户ID信息 + */ + export interface UserIdInfo { + /** + * 用户标记 + * @example ou_032f507d08f9a7f28b042fcd086daef5 + */ + open_id: string; + /** + * 用户标记 + * @example on_7111660fddd8302ce47bf1999147c011 + */ + union_id: string; + /** + * 用户名 + * @example zhaoyingbo + */ + user_id: string; + } + /** + * 消息发送者信息 + */ + export interface Sender { + /** + * id 相关信息 + */ + sender_id: UserIdInfo; + /** + * 发送者类型 + * @example user + */ + sender_type: string; + /** + * 应用ID + * @example 2ee61fe50f4f1657 + */ + tenant_key: string; + } + /** + * 事件详情 + */ + export interface Event { + message: Message; + sender: Sender; + } + + export interface Data { + /** + * 协议版本 + * @example 2.0 + */ + schema: string; + /** + * 事件头 + */ + header: Header; + /** + * 事件详情 + */ + event: Event; + } +} diff --git a/types/larkServer.ts b/types/larkServer.ts new file mode 100644 index 0000000..b24a68e --- /dev/null +++ b/types/larkServer.ts @@ -0,0 +1,78 @@ +export namespace LarkServer { + export interface UserSession { + /** + * 访问令牌 + */ + access_token: string; + + /** + * 员工ID + */ + employee_id: string; + + /** + * 令牌过期时间 + */ + expires_in: number; + + /** + * 开放ID + */ + open_id: string; + + /** + * 刷新令牌 + */ + refresh_token: string; + + /** + * 会话密钥 + */ + session_key: string; + + /** + * 租户密钥 + */ + tenant_key: string; + + /** + * 联盟ID + */ + union_id: string; + } + + export interface BaseRes { + code: number; + data: any; + msg: string; + } + + export interface UserSessionRes extends BaseRes { + data: UserSession; + } + + export interface UserInfoRes extends BaseRes { + data: { + user: any; + }; + } + + export type ReceiveIDType = + | "open_id" + | "user_id" + | "union_id" + | "email" + | "chat_id"; + + export type MsgType = + | "text" + | "post" + | "image" + | "file" + | "audio" + | "media" + | "sticker" + | "interactive" + | "share_chat" + | "share_user"; +} diff --git a/types/msgProxy.ts b/types/msgProxy.ts new file mode 100644 index 0000000..f9b48ee --- /dev/null +++ b/types/msgProxy.ts @@ -0,0 +1,16 @@ +import { LarkServer } from "./larkServer"; + +export namespace MsgProxy { + export interface BaseBody { + msg_type: LarkServer.MsgType; + content: string; + } + export interface GroupBody extends BaseBody { + group_id: string; + } + export interface NormalBody extends BaseBody { + receive_id: string; + receive_id_type: LarkServer.ReceiveIDType; + } + export type Body = GroupBody & NormalBody; +} diff --git a/types/remind.ts b/types/remind.ts new file mode 100644 index 0000000..0c7299f --- /dev/null +++ b/types/remind.ts @@ -0,0 +1,214 @@ +/** + * 用户信息 + */ +export interface User { + /** + * id + */ + id: string; + /** + * 用户名 + * @example zhaoyingbo + */ + userId: string; + /** + * open_id + */ + openId: string; + /** + * 提醒列表 + */ + remindList: string[]; +} + +/** + * 提醒列表 + */ +export interface Remind { + /** + * id + */ + id: string; + /** + * 所有者信息,绑定用户表的id + */ + owner: string; + /** + * 消息Id + */ + messageId: string; + /** + * 接收者类型 + */ + subscriberType: "open_id" | "user_id" | "union_id" | "email" | "chat_id"; + /** + * 接收者Id + */ + subscriberId: string; + /** + * 是否需要回复,不需要回复的也不会重复提醒 + */ + needReply: boolean; + /** + * 延迟时间 + */ + delayTime: number; + /** + * 卡片信息,用于绘制初始卡片、确认卡片、取消卡片、延迟卡片 + */ + cardInfo: { + /** + * 提醒标题,必须要有 + */ + title: string; + /** + * 插图key + */ + imageKey?: string; + /** + * 提醒内容,为空不显示 + */ + content?: string; + /** + * 确认文本,为空不显示,为需要回复卡片时,如果为空则默认为“完成” + */ + confirmText?: string; + /** + * 取消文本,为空不显示 + */ + cancelText?: string; + /** + * 延迟文本,为空不显示 + */ + delayText?: string; + } | null; + /** + * 卡片模板信息 + */ + templateInfo: { + /** + * 卡片模板ID,会注入变量 + * ${owner} 所有者 + * ${remindTime} 提醒时间 + */ + pendingTemplateId: string; + /** + * 交互之后的卡片模板ID,如果有这个就不会用下边三个但是都会注入变量 + * ${owner} 所有者 + * ${remindTime} 提醒时间 + * ${result} 交互结果,会读卡片按钮绑定的变量text,如果没有则是绑定的result对应的 已确认、已取消、已延迟 + * ${interactTime} 交互时间 + */ + interactedTemplateId: string; + /** + * 确认之后的卡片模板ID + */ + confirmedTemplateId: string; + /** + * 取消之后的卡片模板ID + */ + cancelededTemplateId: string; + /** + * 延迟之后的卡片模板ID + */ + delayedTemplateId: string; + } | null; + + /** + * 提醒时间 + */ + remindTimes: RemindTime[]; + + /** + * 是否启用 + */ + enabled: boolean; + /** + * 下次提醒的时间,格式为yyyy-MM-dd HH:mm + */ + nextRemindTime: string; + /** + * 下次提醒时间的中文 + */ + nextRemindTimeCHS: string; +} + +/** + * 提醒时间 + * 为了支持多个时间点提醒,将时间存成数组 + */ +export interface RemindTime { + /** + * 重复类型 + * single: 一次性 + * daily: 每天 + * weekly: 每周 + * monthly: 每月 + * yearly: 每年 + * workday: 工作日 + * holiday: 节假日 + */ + frequency: + | "single" + | "daily" + | "weekly" + | "monthly" + | "yearly" + | "workday" + | "holiday"; + /** + * 提醒时间,格式为HH:mm, single类型时仅作展示用,类型为yyyy-MM-dd HH:mm + */ + time: string; + /** + * 星期几[1-7],当frequency为weekly时有效 + */ + daysOfWeek: number[]; + /** + * 每月的几号[1-31],当frequency为monthly时有效 + */ + daysOfMonth: number[]; + /** + * 每年的哪天提醒,当frequency为 yearly 时有效,格式为MM-dd + */ + dayOfYear: string; +} + +/** + * 提醒记录 + * 记录提醒时间,回答结果等 + */ +export interface RemindRecord { + /** + * 记录Id + */ + id: string; + /** + * 关联的提醒Id + */ + remindId: string; + /** + * 发送的卡片Id + */ + messageId: string; + /** + * 提醒状态 + * pending: 待确认 + * delay: 已延迟 + * confirmed: 已确认 + * canceled: 已取消 + */ + status: "pending" | "delayed" | "confirmed" | "canceled"; + /** + * 本次提醒时间,格式为yyyy-MM-dd HH:mm + */ + remindTime: string; + /** + * 用户交互的时间,格式为yyyy-MM-dd HH:mm + */ + interactTime: string; + /** + * 用户回答的结果,类似每天 07:00 + */ + result: object; +} diff --git a/utils/msgTools.ts b/utils/msgTools.ts index 6dc10d2..51c3655 100644 --- a/utils/msgTools.ts +++ b/utils/msgTools.ts @@ -1,63 +1,63 @@ -import { LarkMessageEvent, LarkUserAction } from "../types"; +import { LarkAction, LarkEvent } from "../types"; /** * 是否为事件消息 - * @param {LarkMessageEvent} body + * @param {LarkEvent.Data} body */ -export const getIsEventMsg = (body: LarkMessageEvent) => { +export const getIsEventMsg = (body: LarkEvent.Data) => { return body?.header?.event_type === "im.message.receive_v1"; }; /** * 获取事件文本类型 - * @param {LarkMessageEvent} body + * @param {LarkEvent.Data} body * @returns */ -export const getMsgType = (body: LarkMessageEvent) => { +export const getMsgType = (body: LarkEvent.Data) => { return body?.event?.message?.message_type; }; /** * 获取对话流Id - * @param {LarkMessageEvent} body + * @param {LarkEvent.Data} body * @returns */ -export const getChatId = (body: LarkMessageEvent) => { +export const getChatId = (body: LarkEvent.Data) => { return body?.event?.message?.chat_id; }; /** * 获取用户Id - * @param {LarkMessageEvent} body + * @param {LarkEvent.Data} body * @returns */ -export const getUserId = (body: LarkMessageEvent) => { +export const getUserId = (body: LarkEvent.Data) => { return body?.event?.sender?.sender_id?.user_id; }; /** * 是否为Action消息 - * @param {LarkUserAction} body + * @param {LarkAction.Data} body */ -export const getIsActionMsg = (body: LarkUserAction) => { +export const getIsActionMsg = (body: LarkAction.Data) => { return body?.action; }; /** * 获取Action类型 - * @param {LarkUserAction} body + * @param {LarkAction.Data} body * @returns {string} Action类型 */ -export const getActionType = (body: LarkUserAction) => { +export const getActionType = (body: LarkAction.Data) => { return body?.action?.tag; }; /** * 获取文本内容并剔除艾特信息 - * @param {LarkMessageEvent} body + * @param {LarkEvent.Data} body * @returns {string} 文本内容 */ -export const getMsgText = (body: LarkMessageEvent) => { +export const getMsgText = (body: LarkEvent.Data) => { try { const { text }: { text: string } = JSON.parse( body?.event?.message?.content @@ -74,18 +74,18 @@ export const getMsgText = (body: LarkMessageEvent) => { /** * 获取聊天类型 - * @param {LarkMessageEvent} body + * @param {LarkEvent.Data} body * @returns {string} 聊天类型 */ -export const getChatType = (body: LarkMessageEvent) => { +export const getChatType = (body: LarkEvent.Data) => { return body?.event?.message?.chat_type; }; /** * 获取艾特信息 - * @param {LarkMessageEvent} body + * @param {LarkEvent.Data} body * @returns {Array} 艾特信息 */ -export const getMentions = (body: LarkMessageEvent) => { +export const getMentions = (body: LarkEvent.Data) => { return body?.event?.message?.mentions; };