From 6e65581bbf1d8c2d9c9ed895ce799fa725b7bdbd Mon Sep 17 00:00:00 2001 From: zhaoyingbo Date: Thu, 25 Jul 2024 01:48:22 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8E=A5=E5=85=A5lint=20=E5=92=8C=20hu?= =?UTF-8?q?sky?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .devcontainer/devcontainer.json | 60 +++--- .devcontainer/initial.bash | 1 - .devcontainer/readme.md | 17 ++ .gitea/workflows/cicd.yaml | 114 +++++------ .gitignore | 2 +- .husky/commit-msg | 1 + .husky/pre-commit | 1 + .vscode/settings.json | 15 ++ bun.lockb | Bin 5293 -> 100473 bytes commitlint.config.js | 1 + db/apiKey/index.ts | 12 +- db/appInfo/index.ts | 22 +- db/index.ts | 30 +-- db/log/index.ts | 12 +- db/messageGroup/index.ts | 28 +-- db/pbClient.ts | 14 +- db/tenantAccessToken/index.ts | 82 ++++---- docker-compose.yml | 18 +- eslint.config.js | 22 ++ index.ts | 72 +++---- package.json | 27 ++- prettier.config.js | 6 + routes/bot/actionMsg.ts | 126 ++++++------ routes/bot/eventMsg.ts | 316 ++++++++++++++--------------- routes/bot/index.ts | 36 ++-- routes/message/index.ts | 276 ++++++++++++------------- routes/microApp/index.ts | 56 ++--- routes/sheet/index.ts | 48 ++--- schedule/accessToken.ts | 40 ++-- schedule/index.ts | 19 +- services/attach/index.ts | 28 +-- services/index.ts | 18 +- services/lark/drive.ts | 38 ++-- services/lark/index.ts | 12 +- services/lark/larkNetTool.ts | 38 ++-- services/lark/message.ts | 22 +- services/lark/sheet.ts | 18 +- services/lark/user.ts | 46 ++--- services/netTool.ts | 77 +++---- test/batchUser.ts | 8 +- test/getApiKey.ts | 6 +- test/insertSheet.ts | 6 +- test/sendMsg.ts | 6 +- thunder-tests/thunderActivity.json | 42 ++-- tsconfig.json | 44 ++-- types/db.ts | 64 +++--- types/index.ts | 12 +- types/larkAction.ts | 22 +- types/larkEvent.ts | 56 ++--- types/larkServer.ts | 64 +++--- types/msgProxy.ts | 16 +- types/remind.ts | 80 ++++---- types/sheetProxy.ts | 14 +- utils/msgTools.ts | 180 ++++++++-------- utils/pathTools.ts | 8 +- utils/pbTools.ts | 40 ++-- 56 files changed, 1260 insertions(+), 1179 deletions(-) delete mode 100644 .devcontainer/initial.bash create mode 100644 .devcontainer/readme.md create mode 100644 .husky/commit-msg create mode 100644 .husky/pre-commit create mode 100644 .vscode/settings.json create mode 100644 commitlint.config.js create mode 100644 eslint.config.js create mode 100644 prettier.config.js diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 61bd9e5..d305056 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,33 +1,27 @@ -{ - "name": "egg_server", - "image": "micr.cloud.mioffice.cn/zhaoyingbo/dev:bun", - "remoteUser": "bun", - "containerUser": "bun", - "forwardPorts": [3000], - "customizations": { - "vscode": { - "settings": { - "files.autoSave": "afterDelay", - "editor.guides.bracketPairs": true, - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true, - "github.copilot.chat.localeOverride": "zh-CN" - }, - "extensions": [ - "dbaeumer.vscode-eslint", - "esbenp.prettier-vscode", - "eamodio.gitlens", - "unifiedjs.vscode-mdx", - "litiany4.umijs-plugin-model", - "oderwat.indent-rainbow", - "jock.svg", - "ChakrounAnas.turbo-console-log", - "Gruntfuggly.todo-tree", - "MS-CEINTL.vscode-language-pack-zh-hans", - "GitHub.copilot", - "GitHub.copilot-chat" - ] - } - }, - "postCreateCommand": "bash -i /workspaces/egg_server/.devcontainer/initial.bash" -} +{ + "name": "ci_monitor", + "image": "mcr.microsoft.com/devcontainers/typescript-node:20", + "customizations": { + "vscode": { + "settings": { + "files.autoSave": "afterDelay", + "editor.guides.bracketPairs": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "always" + } + }, + "extensions": [ + "eamodio.gitlens", + "Gruntfuggly.todo-tree", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "ChakrounAnas.turbo-console-log", + "streetsidesoftware.code-spell-checker", + "MS-CEINTL.vscode-language-pack-zh-hans" + ] + } + }, + "onCreateCommand": "curl -fsSL https://bun.sh/install | bash" +} diff --git a/.devcontainer/initial.bash b/.devcontainer/initial.bash deleted file mode 100644 index 7c9e5ac..0000000 --- a/.devcontainer/initial.bash +++ /dev/null @@ -1 +0,0 @@ -echo "alias dev=\"cd /workspaces/egg_server && bun run dev\"" >> /home/bun/.bashrc \ No newline at end of file diff --git a/.devcontainer/readme.md b/.devcontainer/readme.md new file mode 100644 index 0000000..7cc7adf --- /dev/null +++ b/.devcontainer/readme.md @@ -0,0 +1,17 @@ +# Dev Container + +这是 Bun + Node.js 的开发容器,基于`mcr.microsoft.com/devcontainers/typescript-node:20`镜像 + +在宿主机上设置`.gitconfig`文件,以及`.ssh`文件夹,以便容器可以访问 git 仓库。 + +> 详见[官方文档](https://code.visualstudio.com/remote/advancedcontainers/sharing-git-credentials) + +# 资源 + +[devcontainer.json 字段定义](https://containers.dev/implementors/json_reference/) + +[devcontainer 官方文档](https://code.visualstudio.com/docs/remote/containers) + +[images](https://github.com/devcontainers/images) + +[features](https://github.com/devcontainers/features) diff --git a/.gitea/workflows/cicd.yaml b/.gitea/workflows/cicd.yaml index 327d201..fb6c342 100644 --- a/.gitea/workflows/cicd.yaml +++ b/.gitea/workflows/cicd.yaml @@ -1,57 +1,57 @@ -name: Egg CI/CD -on: [push] - -jobs: - build-image: - runs-on: ubuntu-latest - container: catthehacker/ubuntu:act-latest - steps: - - name: Check out repository code - uses: actions/checkout@v3 - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - registry: git.yingbo.im:333 - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - name: Build and push - uses: docker/build-push-action@v4 - with: - push: true - tags: git.yingbo.im:333/zhaoyingbo/egg_server:${{ github.sha }} - - deploy: - needs: build-image - runs-on: ubuntu-latest - container: catthehacker/ubuntu:act-latest - steps: - # 检出代码 - - name: Check out repository code - uses: actions/checkout@v3 - # 使用scp命令将docker-compose.yml文件上传到服务器 - - name: Upload docker-compose.yml to server - uses: appleboy/scp-action@master - with: - host: ${{ secrets.SERVER_HOST }} - username: ${{ secrets.SERVER_USERNAME }} - key: ${{ secrets.SERVER_KEY }} - port: ${{ secrets.SERVER_PORT }} - source: docker-compose.yml - target: /home/${{ secrets.SERVER_USERNAME }}/docker/egg_server - # 登录服务器,执行docker-compose命令 - - name: Login to the server and execute docker-compose command - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.SERVER_HOST }} - username: ${{ secrets.SERVER_USERNAME }} - key: ${{ secrets.SERVER_KEY }} - port: ${{ secrets.SERVER_PORT }} - script: | - docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} git.yingbo.im:333 - cd /home/${{ secrets.SERVER_USERNAME }}/docker/egg_server - sed -i "s/sha/${{ github.sha }}/g" docker-compose.yml - docker compose up -d --force-recreate --no-deps egg_server +name: Egg CI/CD +on: [push] + +jobs: + build-image: + runs-on: ubuntu-latest + container: catthehacker/ubuntu:act-latest + steps: + - name: Check out repository code + uses: actions/checkout@v3 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + registry: git.yingbo.im:333 + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Build and push + uses: docker/build-push-action@v4 + with: + push: true + tags: git.yingbo.im:333/zhaoyingbo/egg_server:${{ github.sha }} + + deploy: + needs: build-image + runs-on: ubuntu-latest + container: catthehacker/ubuntu:act-latest + steps: + # 检出代码 + - name: Check out repository code + uses: actions/checkout@v3 + # 使用scp命令将docker-compose.yml文件上传到服务器 + - name: Upload docker-compose.yml to server + uses: appleboy/scp-action@master + with: + host: ${{ secrets.SERVER_HOST }} + username: ${{ secrets.SERVER_USERNAME }} + key: ${{ secrets.SERVER_KEY }} + port: ${{ secrets.SERVER_PORT }} + source: docker-compose.yml + target: /home/${{ secrets.SERVER_USERNAME }}/docker/egg_server + # 登录服务器,执行docker-compose命令 + - name: Login to the server and execute docker-compose command + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.SERVER_HOST }} + username: ${{ secrets.SERVER_USERNAME }} + key: ${{ secrets.SERVER_KEY }} + port: ${{ secrets.SERVER_PORT }} + script: | + docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} git.yingbo.im:333 + cd /home/${{ secrets.SERVER_USERNAME }}/docker/egg_server + sed -i "s/sha/${{ github.sha }}/g" docker-compose.yml + docker compose up -d --force-recreate --no-deps egg_server diff --git a/.gitignore b/.gitignore index 10e1165..96c022e 100644 --- a/.gitignore +++ b/.gitignore @@ -49,7 +49,7 @@ profile-* .idea # vscode -.vscode +# .vscode *code-workspace # clinic diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 0000000..0a4b97d --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1 @@ +npx --no -- commitlint --edit $1 diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..af5adff --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +lint-staged \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f0025be --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "cSpell.words": [ + "bunx", + "CEINTL", + "Chakroun", + "commitlint", + "dbaeumer", + "devcontainers", + "eamodio", + "esbenp", + "Gruntfuggly", + "tseslint", + "wlpbbgiky" + ] +} diff --git a/bun.lockb b/bun.lockb index 0ffd67401dce9567755ca57dbeaee6372c73ee5a..94d0484e580d4a552136b08292996554e909e4c2 100755 GIT binary patch literal 100473 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 z%0D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 managePbError(() => pbClient.collection("api_key").getOne(id, { expand: "app", }) - ); + ) const apiKey = { getOne, -}; +} -export default apiKey; +export default apiKey diff --git a/db/appInfo/index.ts b/db/appInfo/index.ts index 3d1fe71..cf79231 100644 --- a/db/appInfo/index.ts +++ b/db/appInfo/index.ts @@ -1,6 +1,6 @@ -import pbClient from "../pbClient"; -import { managePb404 } from "../../utils/pbTools"; -import { DB } from "../../types"; +import { DB } from "../../types" +import { managePb404 } from "../../utils/pbTools" +import pbClient from "../pbClient" /** * 获取配置 @@ -10,13 +10,13 @@ import { DB } from "../../types"; const get = async (appName: string) => managePb404(() => pbClient.collection("app_info").getFirstListItem(`name='${appName}'`) - ); + ) /** * 获取所有配置 * @returns */ -const getFullList = () => pbClient.collection("app_info").getFullList(); +const getFullList = () => pbClient.collection("app_info").getFullList() /** * 获取配置的某个值 @@ -25,15 +25,15 @@ const getFullList = () => pbClient.collection("app_info").getFullList(); * @returns */ const getVal = async (appName: string, key: string) => { - const config = await get(appName); - if (!config) return ""; - return config[key] || ""; -}; + const config = await get(appName) + if (!config) return "" + return config[key] || "" +} const appInfo = { get, getVal, getFullList, -}; +} -export default appInfo; +export default appInfo diff --git a/db/index.ts b/db/index.ts index 4119367..9e4d405 100644 --- a/db/index.ts +++ b/db/index.ts @@ -1,15 +1,15 @@ -import messageGroup from "./messageGroup"; -import tenantAccessToken from "./tenantAccessToken"; -import appInfo from "./appInfo"; -import log from "./log"; -import apiKey from "./apiKey"; - -const db = { - apiKey, - appInfo, - messageGroup, - log, - tenantAccessToken, -}; - -export default db; +import apiKey from "./apiKey" +import appInfo from "./appInfo" +import log from "./log" +import messageGroup from "./messageGroup" +import tenantAccessToken from "./tenantAccessToken" + +const db = { + apiKey, + appInfo, + messageGroup, + log, + tenantAccessToken, +} + +export default db diff --git a/db/log/index.ts b/db/log/index.ts index 00279d8..635c6d5 100644 --- a/db/log/index.ts +++ b/db/log/index.ts @@ -1,12 +1,12 @@ -import { DB } from "../../types"; -import { managePbError } from "../../utils/pbTools"; -import pbClient from "../pbClient"; +import { DB } from "../../types" +import { managePbError } from "../../utils/pbTools" +import pbClient from "../pbClient" const create = (collection: DB.LogCollection, log: DB.Log) => - managePbError(() => pbClient.collection(collection).create(log)); + managePbError(() => pbClient.collection(collection).create(log)) const log = { create, -}; +} -export default log; +export default log diff --git a/db/messageGroup/index.ts b/db/messageGroup/index.ts index 56cd46c..ce6a867 100644 --- a/db/messageGroup/index.ts +++ b/db/messageGroup/index.ts @@ -1,14 +1,14 @@ -import { DB } from "../../types"; -import { managePbError } from "../../utils/pbTools"; -import pbClient from "../pbClient"; - -const getOne = (groupId: string) => - managePbError(() => - pbClient.collection("message_group").getOne(groupId) - ); - -const messageGroup = { - getOne, -}; - -export default messageGroup; +import { DB } from "../../types" +import { managePbError } from "../../utils/pbTools" +import pbClient from "../pbClient" + +const getOne = (groupId: string) => + managePbError(() => + pbClient.collection("message_group").getOne(groupId) + ) + +const messageGroup = { + getOne, +} + +export default messageGroup diff --git a/db/pbClient.ts b/db/pbClient.ts index 11f28d0..621f35d 100644 --- a/db/pbClient.ts +++ b/db/pbClient.ts @@ -1,7 +1,7 @@ -import PocketBase from "pocketbase"; - -const pbClient = new PocketBase("https://eggpb.imoaix.cn"); - -pbClient.autoCancellation(false); - -export default pbClient; +import PocketBase from "pocketbase" + +const pbClient = new PocketBase("https://eggpb.imoaix.cn") + +pbClient.autoCancellation(false) + +export default pbClient diff --git a/db/tenantAccessToken/index.ts b/db/tenantAccessToken/index.ts index 8eca4ac..6516ba7 100644 --- a/db/tenantAccessToken/index.ts +++ b/db/tenantAccessToken/index.ts @@ -1,40 +1,42 @@ -import appInfo from "../appInfo"; -import pbClient from "../pbClient"; - -const tokenCache = {} as Record; - -/** - * 更新租户的token - * @param {string} id 记录id - * @param {string} appName 租户名称 - * @param {string} value 新的token - */ -const update = async (id: string, appName: string, value: string) => { - try { - await pbClient - .collection("app_info") - .update(id, { tenant_access_token: value }); - } catch {} - - tokenCache[appName] = value; - console.log(`reset ${appName} access token success`, value); -}; - -/** - * 获取租户的token - * @param {string} appName 租户名称 - * @returns {string} 租户的token - */ -const get = async (appName: string) => { - if (tokenCache[appName]) return tokenCache[appName]; - const config = await appInfo.getVal(appName, "tenant_access_token"); - tokenCache[appName] = config; - return config; -}; - -const tenantAccessToken = { - get, - update, -}; - -export default tenantAccessToken; +import appInfo from "../appInfo" +import pbClient from "../pbClient" + +const tokenCache = {} as Record + +/** + * 更新租户的token + * @param {string} id 记录id + * @param {string} appName 租户名称 + * @param {string} value 新的token + */ +const update = async (id: string, appName: string, value: string) => { + try { + await pbClient + .collection("app_info") + .update(id, { tenant_access_token: value }) + } catch { + /* empty */ + } + + tokenCache[appName] = value + console.log(`reset ${appName} access token success`, value) +} + +/** + * 获取租户的token + * @param {string} appName 租户名称 + * @returns {string} 租户的token + */ +const get = async (appName: string) => { + if (tokenCache[appName]) return tokenCache[appName] + const config = await appInfo.getVal(appName, "tenant_access_token") + tokenCache[appName] = config + return config +} + +const tenantAccessToken = { + get, + update, +} + +export default tenantAccessToken diff --git a/docker-compose.yml b/docker-compose.yml index f802bf7..818f2de 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,9 @@ -version: "3" - -services: - egg_server: - image: git.yingbo.im:333/zhaoyingbo/egg_server:sha - container_name: egg_server - restart: always - ports: - - 3003:3000 +version: "3" + +services: + egg_server: + image: git.yingbo.im:333/zhaoyingbo/egg_server:sha + container_name: egg_server + restart: always + ports: + - 3003:3000 diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..d871960 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,22 @@ +import pluginJs from "@eslint/js" +import simpleImportSort from "eslint-plugin-simple-import-sort" +import globals from "globals" +import tseslint from "typescript-eslint" + +export default [ + { files: ["**/*.{js,mjs,cjs,ts}"] }, + { languageOptions: { globals: globals.browser } }, + pluginJs.configs.recommended, + ...tseslint.configs.recommended, + { + plugins: { + "simple-import-sort": simpleImportSort, + }, + rules: { + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-namespace": "off", + "simple-import-sort/imports": "error", + "simple-import-sort/exports": "error", + }, + }, +] diff --git a/index.ts b/index.ts index 8fa4b1c..d32e03a 100644 --- a/index.ts +++ b/index.ts @@ -1,36 +1,36 @@ -import { manageBotReq } from "./routes/bot"; -import { manageMessageReq } from "./routes/message"; -import { manageMicroAppReq } from "./routes/microApp"; -import { manageSheetReq } from "./routes/sheet"; -import { initSchedule } from "./schedule"; -import netTool from "./services/netTool"; - -initSchedule(); - -const server = Bun.serve({ - async fetch(req) { - try { - const url = new URL(req.url); - // 根路由 - if (url.pathname === "/") return netTool.ok("hello, glade to see you!"); - // 机器人 - if (url.pathname === "/bot") return await manageBotReq(req); - // 消息代理发送 - if (url.pathname === "/message") return await manageMessageReq(req); - // 表格代理操作 - if (url.pathname === "/sheet") return await manageSheetReq(req); - // 小程序 - if (url.pathname.startsWith("/micro_app")) - return await manageMicroAppReq(req); - // 其他 - return netTool.ok("hello, glade to see you!"); - } catch (error: any) { - // 错误处理 - console.error("🚀 ~ serve ~ error", error); - return netTool.serverError(error.message || "server error"); - } - }, - port: 3000, -}); - -console.log(`Listening on ${server.hostname}:${server.port}`); +import { manageBotReq } from "./routes/bot" +import { manageMessageReq } from "./routes/message" +import { manageMicroAppReq } from "./routes/microApp" +import { manageSheetReq } from "./routes/sheet" +import { initSchedule } from "./schedule" +import netTool from "./services/netTool" + +initSchedule() + +const server = Bun.serve({ + async fetch(req) { + try { + const url = new URL(req.url) + // 根路由 + if (url.pathname === "/") return netTool.ok("hello, glade to see you!") + // 机器人 + if (url.pathname === "/bot") return await manageBotReq(req) + // 消息代理发送 + if (url.pathname === "/message") return await manageMessageReq(req) + // 表格代理操作 + if (url.pathname === "/sheet") return await manageSheetReq(req) + // 小程序 + if (url.pathname.startsWith("/micro_app")) + return await manageMicroAppReq(req) + // 其他 + return netTool.ok("hello, glade to see you!") + } catch (error: any) { + // 错误处理 + console.error("🚀 ~ serve ~ error", error) + return netTool.serverError(error.message || "server error") + } + }, + port: 3000, +}) + +console.log(`Listening on ${server.hostname}:${server.port}`) diff --git a/package.json b/package.json index 922e397..5e0d93b 100644 --- a/package.json +++ b/package.json @@ -3,17 +3,36 @@ "module": "index.ts", "type": "module", "scripts": { - "start": "bun run index.ts" + "start": "bun run index.ts", + "lint": "eslint --fix .", + "prepare": "husky", + "prettier": "prettier --write ." + }, + "lint-staged": { + "*.{js,jsx,ts,tsx}": [ + "eslint --fix", + "prettier --write", + "git add" + ] }, "devDependencies": { - "bun-types": "latest" + "@commitlint/cli": "^19.3.0", + "@commitlint/config-conventional": "^19.2.2", + "@eslint/js": "^9.7.0", + "@types/node-schedule": "^2.1.7", + "bun-types": "latest", + "eslint": "^9.7.0", + "eslint-plugin-simple-import-sort": "^12.1.1", + "husky": "^9.1.1", + "lint-staged": "^15.2.7", + "prettier": "^3.3.3", + "typescript-eslint": "^7.17.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "dependencies": { - "@types/node-schedule": "^2.1.6", "node-schedule": "^2.1.1", "pocketbase": "^0.21.3" } -} \ No newline at end of file +} diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..db67959 --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,6 @@ +export default { + trailingComma: "es5", + tabWidth: 2, + semi: false, + singleQuote: false, +} diff --git a/routes/bot/actionMsg.ts b/routes/bot/actionMsg.ts index 0927a9c..745eae9 100644 --- a/routes/bot/actionMsg.ts +++ b/routes/bot/actionMsg.ts @@ -1,63 +1,63 @@ -import { sleep } from "bun"; -import { getActionType, getIsActionMsg } from "../../utils/msgTools"; - -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", - data: { - config: { - update_multi: true, - }, - template_id: "ctp_AAi3NnHb6zgK", - template_variable: { - chat_id: body.open_chat_id, - }, - }, - }); -}; - -const ACTION_MAP = { - chat_id: makeChatIdCard, -}; - -/** - * 处理按钮点击事件 - * @param {LarkAction.Data} body - */ -const manageBtnClick = async (body: LarkAction.Data) => { - const { action } = body?.action?.value as { - action: keyof typeof ACTION_MAP; - }; - if (!action) return; - const func = ACTION_MAP[action]; - if (!func) return; - const card = await func(body); - if (!card) return; - // 更新飞书的卡片 - await service.lark.message.update()(body.open_message_id, card); -}; - -/** - * 处理Action消息 - * @param {LarkAction.Data} body - * @returns {boolean} 是否在本函数中处理了消息 - */ -export const manageActionMsg = (body: LarkAction.Data) => { - // 过滤非Action消息 - if (!getIsActionMsg(body)) { - return false; - } - const actionType = getActionType(body); - if (actionType === "button") { - manageBtnClick(body); - } - return true; -}; +import { sleep } from "bun" + +import service from "../../services" +import { LarkAction } from "../../types" +import { getActionType, getIsActionMsg } from "../../utils/msgTools" + +/** + * 返回ChatId卡片 + * @param {LarkAction.Data} body + */ +const makeChatIdCard = async (body: LarkAction.Data) => { + await sleep(500) + return JSON.stringify({ + type: "template", + data: { + config: { + update_multi: true, + }, + template_id: "ctp_AAi3NnHb6zgK", + template_variable: { + chat_id: body.open_chat_id, + }, + }, + }) +} + +const ACTION_MAP = { + chat_id: makeChatIdCard, +} + +/** + * 处理按钮点击事件 + * @param {LarkAction.Data} body + */ +const manageBtnClick = async (body: LarkAction.Data) => { + const { action } = body?.action?.value as { + action: keyof typeof ACTION_MAP + } + if (!action) return + const func = ACTION_MAP[action] + if (!func) return + const card = await func(body) + if (!card) return + // 更新飞书的卡片 + await service.lark.message.update()(body.open_message_id, card) +} + +/** + * 处理Action消息 + * @param {LarkAction.Data} body + * @returns {boolean} 是否在本函数中处理了消息 + */ +export const manageActionMsg = (body: LarkAction.Data) => { + // 过滤非Action消息 + if (!getIsActionMsg(body)) { + return false + } + const actionType = getActionType(body) + if (actionType === "button") { + manageBtnClick(body) + } + return true +} diff --git a/routes/bot/eventMsg.ts b/routes/bot/eventMsg.ts index 27d7362..56e386b 100644 --- a/routes/bot/eventMsg.ts +++ b/routes/bot/eventMsg.ts @@ -1,158 +1,158 @@ -import service from "../../services"; -import { LarkEvent } from "../../types"; -import { - getChatId, - getChatType, - getIsEventMsg, - getMentions, - getMsgText, - getMsgType, -} from "../../utils/msgTools"; - -/** - * 是否为P2P或者群聊并且艾特了小煎蛋 - * @param {LarkEvent.Data} body - * @returns {boolean} 是否为P2P或者群聊并且艾特了小煎蛋 - */ -const getIsP2pOrGroupAtBot = (body: LarkEvent.Data) => { - const isP2p = getChatType(body) === "p2p"; - const isAtBot = getMentions(body)?.some?.( - (mention) => mention.name === "小煎蛋" - ); - return isP2p || isAtBot; -}; - -/** - * 过滤出非法消息,如果发表情包就直接发回去 - * @param {LarkEvent.Data} body - * @returns {boolean} 是否为非法消息 - */ -const filterIllegalMsg = (body: LarkEvent.Data) => { - // 没有chatId的消息不处理 - const chatId = getChatId(body); - if (!chatId) return true; - - // 获取msgType - const msgType = getMsgType(body); - - // 放行纯文本消息 - if (msgType === "text") { - // 过滤艾特全体成员的消息 - if (getMsgText(body).includes("@_all")) { - return true; - } - // 放行 - return false; - } - - // 发表情包就直接发回去 - if (msgType === "sticker") { - const content = body?.event?.message?.content; - service.lark.message.send()("chat_id", chatId, "sticker", content); - } - - // 非表情包只在私聊或者群聊中艾特小煎蛋时才回复 - else if (getIsP2pOrGroupAtBot(body)) { - const content = JSON.stringify({ - text: "哇!这是什么东东?我只懂普通文本啦![可爱]", - }); - service.lark.message.send()("chat_id", chatId, "text", content); - } - - // 非纯文本,全不放行 - return true; -}; - -/** - * 发送ID消息 - * @param chatId - 发送消息的chatId - */ -const manageIdMsg = async (chatId: string) => { - const content = JSON.stringify({ - type: "template", - data: { - config: { - update_multi: true, - }, - template_id: "ctp_AAi3NnHb6zgK", - template_variable: { - chat_id: chatId, - }, - }, - }); - service.lark.message.send()("chat_id", chatId, "interactive", content); -}; - -/** - * 处理命令消息 - * @param body - 消息体 - * @returns - */ -const manageCMDMsg = (body: LarkEvent.Data) => { - const text = getMsgText(body); - console.log("🚀 ~ manageCMDMsg ~ text:", text); - const chatId = getChatId(body); - if (text.trim() === "/id") { - manageIdMsg(chatId); - return true; - } - if (text.trim() === "/ci") { - service.attach.ciMonitor(chatId); - return true; - } - if (text.includes("share") && text.includes("简报")) { - service.attach.reportCollector(body); - // 这个用时比较久,先发一条提醒用户收到了请求 - const content = JSON.stringify({ - text: "正在为您收集简报,请稍等片刻~", - }); - service.lark.message.send()("chat_id", chatId, "text", content); - return true; - } - return false; -}; - -/** - * 回复引导消息 - * @param {LarkEvent.Data} body - */ -const replyGuideMsg = async (body: LarkEvent.Data) => { - const chatId = getChatId(body); - const content = JSON.stringify({ - type: "template", - data: { - config: { - enable_forward: false, - update_multi: true, - }, - template_id: "ctp_AAyVx5R39xU9", - template_variable: { - chat_id: chatId, - }, - }, - }); - await service.lark.message.send()("chat_id", chatId, "interactive", content); -}; - -/** - * 处理Event消息 - * @param {LarkUserAction} body - * @returns {boolean} 是否在本函数中处理了消息 - */ -export const manageEventMsg = (body: LarkEvent.Data) => { - // 过滤非Event消息 - if (!getIsEventMsg(body)) { - return false; - } - // 过滤非法消息 - if (filterIllegalMsg(body)) { - return true; - } - // 处理命令消息 - if (manageCMDMsg(body)) { - return true; - } - // 返回引导消息 - replyGuideMsg(body); - return true; -}; +import service from "../../services" +import { LarkEvent } from "../../types" +import { + getChatId, + getChatType, + getIsEventMsg, + getMentions, + getMsgText, + getMsgType, +} from "../../utils/msgTools" + +/** + * 是否为P2P或者群聊并且艾特了小煎蛋 + * @param {LarkEvent.Data} body + * @returns {boolean} 是否为P2P或者群聊并且艾特了小煎蛋 + */ +const getIsP2pOrGroupAtBot = (body: LarkEvent.Data) => { + const isP2p = getChatType(body) === "p2p" + const isAtBot = getMentions(body)?.some?.( + (mention) => mention.name === "小煎蛋" + ) + return isP2p || isAtBot +} + +/** + * 过滤出非法消息,如果发表情包就直接发回去 + * @param {LarkEvent.Data} body + * @returns {boolean} 是否为非法消息 + */ +const filterIllegalMsg = (body: LarkEvent.Data) => { + // 没有chatId的消息不处理 + const chatId = getChatId(body) + if (!chatId) return true + + // 获取msgType + const msgType = getMsgType(body) + + // 放行纯文本消息 + if (msgType === "text") { + // 过滤艾特全体成员的消息 + if (getMsgText(body).includes("@_all")) { + return true + } + // 放行 + return false + } + + // 发表情包就直接发回去 + if (msgType === "sticker") { + const content = body?.event?.message?.content + service.lark.message.send()("chat_id", chatId, "sticker", content) + } + + // 非表情包只在私聊或者群聊中艾特小煎蛋时才回复 + else if (getIsP2pOrGroupAtBot(body)) { + const content = JSON.stringify({ + text: "哇!这是什么东东?我只懂普通文本啦![可爱]", + }) + service.lark.message.send()("chat_id", chatId, "text", content) + } + + // 非纯文本,全不放行 + return true +} + +/** + * 发送ID消息 + * @param chatId - 发送消息的chatId + */ +const manageIdMsg = async (chatId: string) => { + const content = JSON.stringify({ + type: "template", + data: { + config: { + update_multi: true, + }, + template_id: "ctp_AAi3NnHb6zgK", + template_variable: { + chat_id: chatId, + }, + }, + }) + service.lark.message.send()("chat_id", chatId, "interactive", content) +} + +/** + * 处理命令消息 + * @param body - 消息体 + * @returns + */ +const manageCMDMsg = (body: LarkEvent.Data) => { + const text = getMsgText(body) + console.log("🚀 ~ manageCMDMsg ~ text:", text) + const chatId = getChatId(body) + if (text.trim() === "/id") { + manageIdMsg(chatId) + return true + } + if (text.trim() === "/ci") { + service.attach.ciMonitor(chatId) + return true + } + if (text.includes("share") && text.includes("简报")) { + service.attach.reportCollector(body) + // 这个用时比较久,先发一条提醒用户收到了请求 + const content = JSON.stringify({ + text: "正在为您收集简报,请稍等片刻~", + }) + service.lark.message.send()("chat_id", chatId, "text", content) + return true + } + return false +} + +/** + * 回复引导消息 + * @param {LarkEvent.Data} body + */ +const replyGuideMsg = async (body: LarkEvent.Data) => { + const chatId = getChatId(body) + const content = JSON.stringify({ + type: "template", + data: { + config: { + enable_forward: false, + update_multi: true, + }, + template_id: "ctp_AAyVx5R39xU9", + template_variable: { + chat_id: chatId, + }, + }, + }) + await service.lark.message.send()("chat_id", chatId, "interactive", content) +} + +/** + * 处理Event消息 + * @param {LarkUserAction} body + * @returns {boolean} 是否在本函数中处理了消息 + */ +export const manageEventMsg = (body: LarkEvent.Data) => { + // 过滤非Event消息 + if (!getIsEventMsg(body)) { + return false + } + // 过滤非法消息 + if (filterIllegalMsg(body)) { + return true + } + // 处理命令消息 + if (manageCMDMsg(body)) { + return true + } + // 返回引导消息 + replyGuideMsg(body) + return true +} diff --git a/routes/bot/index.ts b/routes/bot/index.ts index 3c7e919..664a861 100644 --- a/routes/bot/index.ts +++ b/routes/bot/index.ts @@ -1,18 +1,18 @@ -import netTool from "../../services/netTool"; -import { manageActionMsg } from "./actionMsg"; -import { manageEventMsg } from "./eventMsg"; - -export const manageBotReq = async (req: Request) => { - const body = (await req.json()) as any; - console.log("🚀 ~ manageBotReq ~ body:", body); - // 验证机器人 - if (body?.type === "url_verification") { - return Response.json({ challenge: body?.challenge }); - } - // 处理Event消息 - if (manageEventMsg(body)) return netTool.ok(); - // 处理Action消息 - if (manageActionMsg(body)) return netTool.ok(); - // 其他 - return netTool.ok(); -}; +import netTool from "../../services/netTool" +import { manageActionMsg } from "./actionMsg" +import { manageEventMsg } from "./eventMsg" + +export const manageBotReq = async (req: Request) => { + const body = (await req.json()) as any + console.log("🚀 ~ manageBotReq ~ body:", body) + // 验证机器人 + if (body?.type === "url_verification") { + return Response.json({ challenge: body?.challenge }) + } + // 处理Event消息 + if (manageEventMsg(body)) return netTool.ok() + // 处理Action消息 + if (manageActionMsg(body)) return netTool.ok() + // 其他 + return netTool.ok() +} diff --git a/routes/message/index.ts b/routes/message/index.ts index c2141cc..67dd045 100644 --- a/routes/message/index.ts +++ b/routes/message/index.ts @@ -1,138 +1,138 @@ -import db from "../../db"; -import service from "../../services"; -import netTool from "../../services/netTool"; -import { DB, LarkServer, MsgProxy } from "../../types"; -import { safeJsonStringify } from "../../utils/pathTools"; - -const LOG_COLLECTION = "message_log"; - -const validateMessageReq = (body: MsgProxy.Body) => { - if (!body.api_key) { - return netTool.badRequest("api_key is required"); - } - if (!body.group_id && !body.receive_id) { - return netTool.badRequest("group_id or receive_id is required"); - } - if (body.receive_id && !body.receive_id_type) { - return netTool.badRequest("receive_id_type is required"); - } - if (!body.msg_type) { - return netTool.badRequest("msg_type is required"); - } - if (!body.content) { - return netTool.badRequest("content is required"); - } - return false; -}; - -export const manageMessageReq = async (req: Request) => { - const body = (await req.json()) as MsgProxy.Body; - // 校验参数 - const validateRes = validateMessageReq(body); - if (validateRes) return validateRes; - - // 处理消息内容 - const finalContent = - typeof body.content !== "string" - ? safeJsonStringify(body.content) - : body.content; - - // 遍历所有id发送消息,保存所有对应的messageId - const sendRes = { - chat_id: {} as Record, - open_id: {} as Record, - union_id: {} as Record, - user_id: {} as Record, - email: {} as Record, - }; - - // 发送消息列表 - const sendList = [] as Promise[]; - - // 构造消息记录 - const baseLog: DB.MessageLogCreate = { - ...body, - final_content: finalContent, - }; - - // 校验api_key - const apiKeyInfo = await db.apiKey.getOne(body.api_key); - if (!apiKeyInfo) { - const error = "api key not found"; - db.log.create(LOG_COLLECTION, { ...baseLog, error }); - return netTool.notFound(error); - } - - // 获取app name - const appName = apiKeyInfo.expand?.app?.name; - if (!appName) { - const error = "app name not found"; - db.log.create(LOG_COLLECTION, { ...baseLog, error }); - return netTool.notFound(error); - } - - // 如果有group_id,则发送给所有group_id中的人 - if (body.group_id) { - // 获取所有接收者 - const group = await db.messageGroup.getOne(body.group_id!); - if (!group) { - const error = "message group not found"; - db.log.create(LOG_COLLECTION, { ...baseLog, error }); - return netTool.notFound(error); - } - - const { chat_id, open_id, union_id, user_id, email } = group; - - // 构造发送消息函数 - const makeSendFunc = (receive_id_type: LarkServer.ReceiveIDType) => { - return (receive_id: string) => { - sendList.push( - service.lark.message - .send(appName)( - receive_id_type, - receive_id, - body.msg_type, - finalContent - ) - .then((res) => { - sendRes[receive_id_type][receive_id] = res; - }) - ); - }; - }; - - // 创建消息列表 - if (chat_id) chat_id.map(makeSendFunc("chat_id")); - if (open_id) open_id.map(makeSendFunc("open_id")); - if (union_id) union_id.map(makeSendFunc("union_id")); - if (user_id) user_id.map(makeSendFunc("user_id")); - if (email) email.map(makeSendFunc("email")); - } - - if (body.receive_id && body.receive_id_type) { - sendList.push( - service.lark.message - .send(appName)( - body.receive_id_type, - body.receive_id, - body.msg_type, - finalContent - ) - .then((res) => { - sendRes[body.receive_id_type][body.receive_id] = res; - }) - ); - } - - try { - // 里边有错误处理,这里不用担心执行不完 - await Promise.all(sendList); - // 保存消息记录 - db.log.create(LOG_COLLECTION, { ...baseLog, send_result: sendRes }); - return netTool.ok(sendRes); - } catch { - const error = "send msg failed"; - db.log.create(LOG_COLLECTION, { ...baseLog, error }); - return netTool.serverError(error, sendRes); - } -}; +import db from "../../db" +import service from "../../services" +import netTool from "../../services/netTool" +import { DB, LarkServer, MsgProxy } from "../../types" +import { safeJsonStringify } from "../../utils/pathTools" + +const LOG_COLLECTION = "message_log" + +const validateMessageReq = (body: MsgProxy.Body) => { + if (!body.api_key) { + return netTool.badRequest("api_key is required") + } + if (!body.group_id && !body.receive_id) { + return netTool.badRequest("group_id or receive_id is required") + } + if (body.receive_id && !body.receive_id_type) { + return netTool.badRequest("receive_id_type is required") + } + if (!body.msg_type) { + return netTool.badRequest("msg_type is required") + } + if (!body.content) { + return netTool.badRequest("content is required") + } + return false +} + +export const manageMessageReq = async (req: Request) => { + const body = (await req.json()) as MsgProxy.Body + // 校验参数 + const validateRes = validateMessageReq(body) + if (validateRes) return validateRes + + // 处理消息内容 + const finalContent = + typeof body.content !== "string" + ? safeJsonStringify(body.content) + : body.content + + // 遍历所有id发送消息,保存所有对应的messageId + const sendRes = { + chat_id: {} as Record, + open_id: {} as Record, + union_id: {} as Record, + user_id: {} as Record, + email: {} as Record, + } + + // 发送消息列表 + const sendList = [] as Promise[] + + // 构造消息记录 + const baseLog: DB.MessageLogCreate = { + ...body, + final_content: finalContent, + } + + // 校验api_key + const apiKeyInfo = await db.apiKey.getOne(body.api_key) + if (!apiKeyInfo) { + const error = "api key not found" + db.log.create(LOG_COLLECTION, { ...baseLog, error }) + return netTool.notFound(error) + } + + // 获取app name + const appName = apiKeyInfo.expand?.app?.name + if (!appName) { + const error = "app name not found" + db.log.create(LOG_COLLECTION, { ...baseLog, error }) + return netTool.notFound(error) + } + + // 如果有group_id,则发送给所有group_id中的人 + if (body.group_id) { + // 获取所有接收者 + const group = await db.messageGroup.getOne(body.group_id!) + if (!group) { + const error = "message group not found" + db.log.create(LOG_COLLECTION, { ...baseLog, error }) + return netTool.notFound(error) + } + + const { chat_id, open_id, union_id, user_id, email } = group + + // 构造发送消息函数 + const makeSendFunc = (receive_id_type: LarkServer.ReceiveIDType) => { + return (receive_id: string) => { + sendList.push( + service.lark.message + .send(appName)( + receive_id_type, + receive_id, + body.msg_type, + finalContent + ) + .then((res) => { + sendRes[receive_id_type][receive_id] = res + }) + ) + } + } + + // 创建消息列表 + if (chat_id) chat_id.map(makeSendFunc("chat_id")) + if (open_id) open_id.map(makeSendFunc("open_id")) + if (union_id) union_id.map(makeSendFunc("union_id")) + if (user_id) user_id.map(makeSendFunc("user_id")) + if (email) email.map(makeSendFunc("email")) + } + + if (body.receive_id && body.receive_id_type) { + sendList.push( + service.lark.message + .send(appName)( + body.receive_id_type, + body.receive_id, + body.msg_type, + finalContent + ) + .then((res) => { + sendRes[body.receive_id_type][body.receive_id] = res + }) + ) + } + + try { + // 里边有错误处理,这里不用担心执行不完 + await Promise.all(sendList) + // 保存消息记录 + db.log.create(LOG_COLLECTION, { ...baseLog, send_result: sendRes }) + return netTool.ok(sendRes) + } catch { + const error = "send msg failed" + db.log.create(LOG_COLLECTION, { ...baseLog, error }) + return netTool.serverError(error, sendRes) + } +} diff --git a/routes/microApp/index.ts b/routes/microApp/index.ts index 641f15e..bc77dde 100644 --- a/routes/microApp/index.ts +++ b/routes/microApp/index.ts @@ -1,6 +1,6 @@ -import service from "../../services"; -import netTool from "../../services/netTool"; -import { trimPathPrefix } from "../../utils/pathTools"; +import service from "../../services" +import netTool from "../../services/netTool" +import { trimPathPrefix } from "../../utils/pathTools" /** * 处理登录请求 @@ -8,34 +8,34 @@ import { trimPathPrefix } from "../../utils/pathTools"; * @returns */ const manageLogin = async (req: Request) => { - const url = new URL(req.url); - const code = url.searchParams.get("code"); - const appName = url.searchParams.get("app_name") || undefined; + const url = new URL(req.url) + const code = url.searchParams.get("code") + const appName = url.searchParams.get("app_name") || undefined if (!code) { - return netTool.badRequest("code not found"); + return netTool.badRequest("code not found") } const { code: resCode, data, msg, - } = await service.lark.user.code2Login(appName)(code); + } = await service.lark.user.code2Login(appName)(code) - console.log("🚀 ~ manageLogin:", resCode, data, msg); + console.log("🚀 ~ manageLogin:", resCode, data, msg) if (resCode !== 0) { return Response.json({ code: resCode, message: msg, data: null, - }); + }) } return Response.json({ code: 0, message: "success", data, - }); -}; + }) +} /** * 处理批量获取用户信息请求 @@ -43,35 +43,35 @@ const manageLogin = async (req: Request) => { * @returns */ const manageBatchUser = async (req: Request) => { - const body = (await req.json()) as any; - console.log("🚀 ~ manageBatchUser ~ body:", body); - const { user_ids, user_id_type, app_name } = body; + const body = (await req.json()) as any + console.log("🚀 ~ manageBatchUser ~ body:", body) + const { user_ids, user_id_type, app_name } = body if (!user_ids) { - return netTool.badRequest("user_ids not found"); + return netTool.badRequest("user_ids not found") } if (!user_id_type) { - return netTool.badRequest("user_id_type not found"); + return netTool.badRequest("user_id_type not found") } const { code, data, msg } = await service.lark.user.batchGet(app_name)( user_ids, user_id_type - ); + ) - console.log("🚀 ~ manageBatchUser:", code, data, msg); + console.log("🚀 ~ manageBatchUser:", code, data, msg) if (code !== 0) { return Response.json({ code, message: msg, data: null, - }); + }) } return Response.json({ code, message: "success", data: data.items, - }); -}; + }) +} /** * 处理小程序请求 @@ -79,15 +79,15 @@ const manageBatchUser = async (req: Request) => { * @returns */ export const manageMicroAppReq = async (req: Request) => { - const url = new URL(req.url); - const withoutPrefix = trimPathPrefix(url.pathname, "/micro_app"); + const url = new URL(req.url) + const withoutPrefix = trimPathPrefix(url.pathname, "/micro_app") // 处理登录请求 if (withoutPrefix === "/login") { - return manageLogin(req); + return manageLogin(req) } // 处理批量获取用户信息请求 if (withoutPrefix === "/batch_user") { - return manageBatchUser(req); + return manageBatchUser(req) } - return netTool.ok(); -}; + return netTool.ok() +} diff --git a/routes/sheet/index.ts b/routes/sheet/index.ts index f3a13e0..fa8a45b 100644 --- a/routes/sheet/index.ts +++ b/routes/sheet/index.ts @@ -1,59 +1,59 @@ -import db from "../../db"; -import service from "../../services"; -import netTool from "../../services/netTool"; -import { SheetProxy } from "../../types/sheetProxy"; +import db from "../../db" +import service from "../../services" +import netTool from "../../services/netTool" +import { SheetProxy } from "../../types/sheetProxy" const validateSheetReq = async (body: SheetProxy.Body) => { if (!body.api_key) { - return netTool.badRequest("api_key is required"); + return netTool.badRequest("api_key is required") } if (!body.sheet_token) { - return netTool.badRequest("sheet_token is required"); + return netTool.badRequest("sheet_token is required") } if (!body.range) { - return netTool.badRequest("range is required"); + return netTool.badRequest("range is required") } if (!body.values) { - return netTool.badRequest("values is required"); + return netTool.badRequest("values is required") } if (!SheetProxy.isType(body.type)) { - return netTool.badRequest("type is invalid"); + return netTool.badRequest("type is invalid") } - return false; -}; + return false +} export const manageSheetReq = async (req: Request) => { - const body = (await req.json()) as SheetProxy.Body; + const body = (await req.json()) as SheetProxy.Body // 校验参数 - const validateRes = await validateSheetReq(body); - if (validateRes) return validateRes; + const validateRes = await validateSheetReq(body) + if (validateRes) return validateRes // 校验api_key - const apiKeyInfo = await db.apiKey.getOne(body.api_key); + const apiKeyInfo = await db.apiKey.getOne(body.api_key) if (!apiKeyInfo) { - return netTool.notFound("api key not found"); + return netTool.notFound("api key not found") } // 获取 app name - const appName = apiKeyInfo.expand?.app?.name; + const appName = apiKeyInfo.expand?.app?.name if (!appName) { - return netTool.notFound("app name not found"); + return netTool.notFound("app name not found") } if (body.type === "insert") { // 插入行 - const insertRes = await service.lark.sheet.insterRows(appName)( + const insertRes = await service.lark.sheet.insertRows(appName)( body.sheet_token, body.range, body.values - ); + ) if (insertRes?.code !== 0) { - return netTool.serverError(insertRes?.msg, insertRes?.data); + return netTool.serverError(insertRes?.msg, insertRes?.data) } // 返回 - return netTool.ok(insertRes?.data); + return netTool.ok(insertRes?.data) } - return netTool.ok(); -}; + return netTool.ok() +} diff --git a/schedule/accessToken.ts b/schedule/accessToken.ts index e2fbb1b..6ac783c 100644 --- a/schedule/accessToken.ts +++ b/schedule/accessToken.ts @@ -1,20 +1,20 @@ -import db from "../db"; -import netTool from "../services/netTool"; - -const URL = - "https://open.f.mioffice.cn/open-apis/auth/v3/tenant_access_token/internal"; - -export const resetAccessToken = async () => { - try { - const appList = await db.appInfo.getFullList(); - for (const app of appList) { - const { tenant_access_token } = await netTool.post(URL, { - app_id: app.app_id, - app_secret: app.app_secret, - }); - await db.tenantAccessToken.update(app.id, app.name, tenant_access_token); - } - } catch (error) { - console.error("🚀 ~ resetAccessToken ~ error", error); - } -}; +import db from "../db" +import netTool from "../services/netTool" + +const URL = + "https://open.f.mioffice.cn/open-apis/auth/v3/tenant_access_token/internal" + +export const resetAccessToken = async () => { + try { + const appList = await db.appInfo.getFullList() + for (const app of appList) { + const { tenant_access_token } = await netTool.post(URL, { + app_id: app.app_id, + app_secret: app.app_secret, + }) + await db.tenantAccessToken.update(app.id, app.name, tenant_access_token) + } + } catch (error) { + console.error("🚀 ~ resetAccessToken ~ error", error) + } +} diff --git a/schedule/index.ts b/schedule/index.ts index 3f75a4a..4cce843 100644 --- a/schedule/index.ts +++ b/schedule/index.ts @@ -1,9 +1,10 @@ -import { resetAccessToken } from "./accessToken"; -import schedule from "node-schedule"; - -export const initSchedule = async () => { - // 定时任务,每15分钟刷新一次token - schedule.scheduleJob("*/15 * * * *", resetAccessToken); - // 立即执行一次 - resetAccessToken(); -}; +import schedule from "node-schedule" + +import { resetAccessToken } from "./accessToken" + +export const initSchedule = async () => { + // 定时任务,每15分钟刷新一次token + schedule.scheduleJob("*/15 * * * *", resetAccessToken) + // 立即执行一次 + resetAccessToken() +} diff --git a/services/attach/index.ts b/services/attach/index.ts index 32bd0f7..f7fb15c 100644 --- a/services/attach/index.ts +++ b/services/attach/index.ts @@ -1,18 +1,18 @@ -import { LarkEvent } from "../../types"; -import netTool from "../netTool"; +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}`; + const URL = `https://ci-monitor.xiaomiwh.cn/ci?chat_id=${chat_id}` try { - const res = await netTool.get(URL); - return (res as string) || ""; + const res = await netTool.get(URL) + return (res as string) || "" } catch { - return ""; + return "" } -}; +} /** * 请求简报收集器 @@ -20,18 +20,18 @@ const ciMonitor = async (chat_id: string) => { * @returns */ const reportCollector = async (body: LarkEvent.Data) => { - const URL = "https://report.imoaix.cn/report"; + const URL = "https://report.imoaix.cn/report" try { - const res = await netTool.post(URL, body); - return (res as string) || ""; + const res = await netTool.post(URL, body) + return (res as string) || "" } catch { - return ""; + return "" } -}; +} const attach = { ciMonitor, reportCollector, -}; +} -export default attach; +export default attach diff --git a/services/index.ts b/services/index.ts index 578da2d..c6eee96 100644 --- a/services/index.ts +++ b/services/index.ts @@ -1,9 +1,9 @@ -import attach from "./attach"; -import lark from "./lark"; - -const service = { - attach, - lark, -}; - -export default service; +import attach from "./attach" +import lark from "./lark" + +const service = { + attach, + lark, +} + +export default service diff --git a/services/lark/drive.ts b/services/lark/drive.ts index ed6b74d..610c62c 100644 --- a/services/lark/drive.ts +++ b/services/lark/drive.ts @@ -1,42 +1,42 @@ -import { LarkServer } from "../../types"; -import larkNetTool from "./larkNetTool"; +import { LarkServer } from "../../types" +import larkNetTool from "./larkNetTool" const batchGetMeta = (appName?: string) => async (docTokens: string[], doc_type = "doc", user_id_type = "user_id") => { const URL = - "https://open.f.mioffice.cn/open-apis/drive/v1/metas/batch_query"; + "https://open.f.mioffice.cn/open-apis/drive/v1/metas/batch_query" // 如果docTokens长度超出150,需要分批请求 - const docTokensLen = docTokens.length; - const maxLen = 150; + const docTokensLen = docTokens.length + const maxLen = 150 const requestMap = Array.from( { length: Math.ceil(docTokensLen / maxLen) }, (_, index) => { - const start = index * maxLen; - const docTokensSlice = docTokens.slice(start, start + maxLen); + const start = index * maxLen + const docTokensSlice = docTokens.slice(start, start + maxLen) const data = { request_docs: docTokensSlice.map((id) => ({ doc_token: id, doc_type, })), - }; + } return larkNetTool.post(appName)( URL, data, { user_id_type, } - ); + ) } - ); - const responses = await Promise.all(requestMap); + ) + const responses = await Promise.all(requestMap) const metas = responses.flatMap((res) => { - return res.data?.metas || []; - }); + return res.data?.metas || [] + }) const failed_list = responses.flatMap((res) => { - return res.data?.failed_list || []; - }); + return res.data?.failed_list || [] + }) return { code: 0, @@ -45,11 +45,11 @@ const batchGetMeta = failed_list, }, msg: "success", - }; - }; + } + } const drive = { batchGetMeta, -}; +} -export default drive; +export default drive diff --git a/services/lark/index.ts b/services/lark/index.ts index 4a9703f..d04fa35 100644 --- a/services/lark/index.ts +++ b/services/lark/index.ts @@ -1,13 +1,13 @@ -import message from "./message"; -import user from "./user"; -import drive from "./drive"; -import sheet from "./sheet"; +import drive from "./drive" +import message from "./message" +import sheet from "./sheet" +import user from "./user" const lark = { message, user, drive, sheet, -}; +} -export default lark; +export default lark diff --git a/services/lark/larkNetTool.ts b/services/lark/larkNetTool.ts index 2bd6ad2..66cf818 100644 --- a/services/lark/larkNetTool.ts +++ b/services/lark/larkNetTool.ts @@ -1,6 +1,6 @@ -import db from "../../db"; -import { DB, LarkServer } from "../../types"; -import netTool from "../netTool"; +import db from "../../db" +import { LarkServer } from "../../types" +import netTool from "../netTool" /** * 发送网络请求并返回一个解析为响应数据的Promise。 @@ -21,17 +21,17 @@ const larkNetTool = async ({ additionalHeaders, appName = "egg", }: { - url: string; - method: string; - queryParams?: any; - payload?: any; - additionalHeaders?: any; - appName?: string; + url: string + method: string + queryParams?: any + payload?: any + additionalHeaders?: any + appName?: string }): Promise => { const headersWithAuth = { Authorization: `Bearer ${await db.tenantAccessToken.get(appName)}`, ...additionalHeaders, - }; + } return netTool({ url, method, @@ -39,14 +39,14 @@ const larkNetTool = async ({ payload, additionalHeaders: headersWithAuth, }).catch((error) => { - console.error("larkNetTool catch error: ", error); + console.error("larkNetTool catch error: ", error) return { code: 1, data: null, msg: error.message || "网络请求异常", - } as T; - }); -}; + } as T + }) +} /** * 发送GET请求并返回一个解析为响应数据的Promise。 @@ -67,7 +67,7 @@ larkNetTool.get = queryParams, additionalHeaders, appName, - }); + }) /** * 发送POST请求并返回一个解析为响应数据的Promise。 @@ -90,7 +90,7 @@ larkNetTool.post = queryParams, additionalHeaders, appName, - }); + }) /** * 发送DELETE请求并返回一个解析为响应数据的Promise。 @@ -105,7 +105,7 @@ larkNetTool.del = payload: any, additionalHeaders?: any ): Promise => - larkNetTool({ url, method: "delete", payload, additionalHeaders, appName }); + larkNetTool({ url, method: "delete", payload, additionalHeaders, appName }) /** * 发送PATCH请求并返回一个解析为响应数据的Promise。 @@ -120,6 +120,6 @@ larkNetTool.patch = payload: any, additionalHeaders?: any ): Promise => - larkNetTool({ url, method: "patch", payload, additionalHeaders, appName }); + larkNetTool({ url, method: "patch", payload, additionalHeaders, appName }) -export default larkNetTool; +export default larkNetTool diff --git a/services/lark/message.ts b/services/lark/message.ts index ead64f5..f9b8c52 100644 --- a/services/lark/message.ts +++ b/services/lark/message.ts @@ -1,5 +1,5 @@ -import { LarkServer } from "../../types/larkServer"; -import larkNetTool from "./larkNetTool"; +import { LarkServer } from "../../types/larkServer" +import larkNetTool from "./larkNetTool" /** * 发送卡片 @@ -16,16 +16,16 @@ const send = msg_type: LarkServer.MsgType, content: string ) => { - const URL = `https://open.f.mioffice.cn/open-apis/im/v1/messages?receive_id_type=${receive_id_type}`; + const URL = `https://open.f.mioffice.cn/open-apis/im/v1/messages?receive_id_type=${receive_id_type}` if (msg_type === "text" && !content.includes('"text"')) { - content = JSON.stringify({ text: content }); + content = JSON.stringify({ text: content }) } return larkNetTool.post(appName)(URL, { receive_id, msg_type, content, - }); - }; + }) + } /** * 更新卡片 @@ -34,13 +34,13 @@ const send = */ const update = (appName?: string) => async (message_id: string, content: string) => { - const URL = `https://open.f.mioffice.cn/open-apis/im/v1/messages/${message_id}`; - return larkNetTool.patch(appName)(URL, { content }); - }; + const URL = `https://open.f.mioffice.cn/open-apis/im/v1/messages/${message_id}` + return larkNetTool.patch(appName)(URL, { content }) + } const message = { send, update, -}; +} -export default message; +export default message diff --git a/services/lark/sheet.ts b/services/lark/sheet.ts index 1c8ae5b..8ec5eab 100644 --- a/services/lark/sheet.ts +++ b/services/lark/sheet.ts @@ -1,25 +1,25 @@ -import { LarkServer } from "../../types/larkServer"; -import larkNetTool from "./larkNetTool"; +import { LarkServer } from "../../types/larkServer" +import larkNetTool from "./larkNetTool" /** * 向电子表格中插入行。 * @param appName - 应用程序的名称(可选)。 * @returns 一个函数,该函数接受表格令牌、范围和要插入的值。 */ -const insterRows = +const insertRows = (appName?: string) => async (sheetToken: string, range: string, values: string[][]) => { - const URL = `https://open.f.mioffice.cn/open-apis/sheets/v2/spreadsheets/${sheetToken}/values_append?insertDataOption=INSERT_ROWS`; + const URL = `https://open.f.mioffice.cn/open-apis/sheets/v2/spreadsheets/${sheetToken}/values_append?insertDataOption=INSERT_ROWS` return larkNetTool.post(appName)(URL, { valueRange: { range, values, }, - }); - }; + }) + } const sheet = { - insterRows, -}; + insertRows, +} -export default sheet; +export default sheet diff --git a/services/lark/user.ts b/services/lark/user.ts index 213c868..bca43a0 100644 --- a/services/lark/user.ts +++ b/services/lark/user.ts @@ -1,5 +1,5 @@ -import { LarkServer } from "../../types/larkServer"; -import larkNetTool from "./larkNetTool"; +import { LarkServer } from "../../types/larkServer" +import larkNetTool from "./larkNetTool" /** * 登录凭证校验 @@ -7,9 +7,9 @@ import larkNetTool from "./larkNetTool"; * @returns */ const code2Login = (appName?: string) => async (code: string) => { - const URL = `https://open.f.mioffice.cn/open-apis/mina/v2/tokenLoginValidate`; - return larkNetTool.post(appName)(URL, { code }); -}; + const URL = `https://open.f.mioffice.cn/open-apis/mina/v2/tokenLoginValidate` + return larkNetTool.post(appName)(URL, { code }) +} /** * 获取用户信息 @@ -19,11 +19,11 @@ const code2Login = (appName?: string) => async (code: string) => { const get = (appName?: string) => async (user_id: string, user_id_type: "open_id" | "user_id") => { - const URL = `https://open.f.mioffice.cn/open-apis/contact/v3/users/${user_id}`; + const URL = `https://open.f.mioffice.cn/open-apis/contact/v3/users/${user_id}` return larkNetTool.get(appName)(URL, { user_id_type, - }); - }; + }) + } /** * 批量获取用户信息 @@ -33,32 +33,32 @@ const get = const batchGet = (appName?: string) => async (user_ids: string[], user_id_type: "open_id" | "user_id") => { - const URL = `https://open.f.mioffice.cn/open-apis/contact/v3/users/batch`; + const URL = `https://open.f.mioffice.cn/open-apis/contact/v3/users/batch` // 如果user_id长度超出50,需要分批请求, - const userCount = user_ids.length; - const maxLen = 50; + const userCount = user_ids.length + const maxLen = 50 const requestMap = Array.from( { length: Math.ceil(userCount / maxLen) }, (_, index) => { - const start = index * maxLen; - const user_idsSlice = user_ids.slice(start, start + maxLen); + const start = index * maxLen + const user_idsSlice = user_ids.slice(start, start + maxLen) const getParams = `${user_idsSlice .map((id) => `user_ids=${id}`) - .join("&")}&user_id_type=${user_id_type}`; + .join("&")}&user_id_type=${user_id_type}` return larkNetTool.get(appName)( URL, getParams - ); + ) } - ); + ) - const responses = await Promise.all(requestMap); + const responses = await Promise.all(requestMap) const items = responses.flatMap((res) => { - return res.data?.items || []; - }); + return res.data?.items || [] + }) return { code: 0, @@ -66,13 +66,13 @@ const batchGet = items, }, msg: "success", - }; - }; + } + } const user = { code2Login, batchGet, get, -}; +} -export default user; +export default user diff --git a/services/netTool.ts b/services/netTool.ts index cdd2d46..082a37f 100644 --- a/services/netTool.ts +++ b/services/netTool.ts @@ -1,9 +1,9 @@ interface NetRequestParams { - url: string; - method: string; - queryParams?: any; - payload?: any; - additionalHeaders?: any; + url: string + method: string + queryParams?: any + payload?: any + additionalHeaders?: any } /** @@ -32,10 +32,10 @@ const logResponse = ( responseHeaders: response.headers, requestBody, responseBody, - }; - console.log("🚀 ~ responseLog:", JSON.stringify(responseLog, null, 2)); - return responseLog; -}; + } + console.log("🚀 ~ responseLog:", JSON.stringify(responseLog, null, 2)) + return responseLog +} /** * 发送网络请求并返回一个解析为响应数据的Promise。 @@ -55,13 +55,13 @@ const netTool = async ({ additionalHeaders, }: NetRequestParams): Promise => { // 拼接完整的URL - let fullUrl = url; + let fullUrl = url if (queryParams) { if (typeof queryParams === "string") { - fullUrl = `${url}?${queryParams}`; + fullUrl = `${url}?${queryParams}` } else { - const queryString = new URLSearchParams(queryParams).toString(); - if (queryString) fullUrl = `${url}?${queryString}`; + const queryString = new URLSearchParams(queryParams).toString() + if (queryString) fullUrl = `${url}?${queryString}` } } @@ -69,40 +69,42 @@ const netTool = async ({ const headers = { "Content-Type": "application/json", ...additionalHeaders, - }; + } // 发送请求 const res = await fetch(fullUrl, { method, body: JSON.stringify(payload), headers, - }); + }) // 获取响应数据 - let resData: any = null; - let resText: string = ""; + let resData: any = null + let resText: string = "" try { - resText = await res.text(); - resData = JSON.parse(resText); - } catch {} + resText = await res.text() + resData = JSON.parse(resText) + } catch { + /* empty */ + } // 记录响应 - logResponse(res, method, headers, payload, resData || resText); + logResponse(res, method, headers, payload, resData || resText) if (!res.ok) { if (resData?.msg) { - throw new Error(resData.msg); + throw new Error(resData.msg) } if (resText) { - throw new Error(resText); + throw new Error(resText) } - throw new Error("网络响应异常"); + throw new Error("网络响应异常") } // http 错误码正常,但解析异常 if (!resData) { - throw new Error("解析响应数据异常"); + throw new Error("解析响应数据异常") } - return resData as T; -}; + return resData as T +} /** * 发送GET请求并返回一个解析为响应数据的Promise。 @@ -116,8 +118,7 @@ netTool.get = ( url: string, queryParams?: any, additionalHeaders?: any -): Promise => - netTool({ url, method: "get", queryParams, additionalHeaders }); +): Promise => netTool({ url, method: "get", queryParams, additionalHeaders }) /** * 发送POST请求并返回一个解析为响应数据的Promise。 @@ -134,7 +135,7 @@ netTool.post = ( queryParams?: any, additionalHeaders?: any ): Promise => - netTool({ url, method: "post", payload, queryParams, additionalHeaders }); + netTool({ url, method: "post", payload, queryParams, additionalHeaders }) /** * 发送PUT请求并返回一个解析为响应数据的Promise。 @@ -151,7 +152,7 @@ netTool.put = ( queryParams?: any, additionalHeaders?: any ): Promise => - netTool({ url, method: "put", payload, queryParams, additionalHeaders }); + netTool({ url, method: "put", payload, queryParams, additionalHeaders }) /** * 发送DELETE请求并返回一个解析为响应数据的Promise。 @@ -168,7 +169,7 @@ netTool.del = ( queryParams?: any, additionalHeaders?: any ): Promise => - netTool({ url, method: "delete", payload, queryParams, additionalHeaders }); + netTool({ url, method: "delete", payload, queryParams, additionalHeaders }) /** * 发送PATCH请求并返回一个解析为响应数据的Promise。 @@ -185,7 +186,7 @@ netTool.patch = ( queryParams?: any, additionalHeaders?: any ): Promise => - netTool({ url, method: "patch", payload, queryParams, additionalHeaders }); + netTool({ url, method: "patch", payload, queryParams, additionalHeaders }) /** * 创建一个表示400 Bad Request的响应对象。 @@ -195,7 +196,7 @@ netTool.patch = ( * @returns 一个表示400 Bad Request的响应对象。 */ netTool.badRequest = (msg: string, requestId?: string) => - Response.json({ code: 400, msg, requestId }, { status: 400 }); + Response.json({ code: 400, msg, requestId }, { status: 400 }) /** * 创建一个表示404 Not Found的响应对象。 @@ -205,7 +206,7 @@ netTool.badRequest = (msg: string, requestId?: string) => * @returns 一个表示404 Not Found的响应对象。 */ netTool.notFound = (msg: string, requestId?: string) => - Response.json({ code: 404, msg, requestId }, { status: 404 }); + Response.json({ code: 404, msg, requestId }, { status: 404 }) /** * 创建一个表示500 Internal Server Error的响应对象。 @@ -216,7 +217,7 @@ netTool.notFound = (msg: string, requestId?: string) => * @returns 一个表示500 Internal Server Error的响应对象。 */ netTool.serverError = (msg: string, data?: any, requestId?: string) => - Response.json({ code: 500, msg, data, requestId }, { status: 500 }); + Response.json({ code: 500, msg, data, requestId }, { status: 500 }) /** * 创建一个表示200 OK的响应对象。 @@ -226,6 +227,6 @@ netTool.serverError = (msg: string, data?: any, requestId?: string) => * @returns 一个表示200 OK的响应对象。 */ netTool.ok = (data?: any, requestId?: string) => - Response.json({ code: 0, msg: "success", data, requestId }); + Response.json({ code: 0, msg: "success", data, requestId }) -export default netTool; +export default netTool diff --git a/test/batchUser.ts b/test/batchUser.ts index 01bba2c..7ed2a9f 100644 --- a/test/batchUser.ts +++ b/test/batchUser.ts @@ -1,5 +1,5 @@ -const localUrl = "http://localhost:3000/micro_app/batch_user"; -const prodUrl = "https://egg.imoaix.cn/micro_app/batch_user"; +const localUrl = "http://localhost:3000/micro_app/batch_user" +// const prodUrl = "https://egg.imoaix.cn/micro_app/batch_user"; const res = await fetch(localUrl, { method: "POST", @@ -10,6 +10,6 @@ const res = await fetch(localUrl, { user_ids: ["zhaoyingbo"], user_id_type: "user_id", }), -}); +}) -console.log(JSON.stringify(await res.json())); +console.log(JSON.stringify(await res.json())) diff --git a/test/getApiKey.ts b/test/getApiKey.ts index b98b57b..c10ded4 100644 --- a/test/getApiKey.ts +++ b/test/getApiKey.ts @@ -1,5 +1,5 @@ -import db from "../db"; +import db from "../db" -const res = await db.apiKey.getOne("uwnpzb9hvoft28h"); +const res = await db.apiKey.getOne("uwnpzb9hvoft28h") -console.log("🚀 ~ res", res); +console.log("🚀 ~ res", res) diff --git a/test/insertSheet.ts b/test/insertSheet.ts index efe6403..16c5d74 100644 --- a/test/insertSheet.ts +++ b/test/insertSheet.ts @@ -1,5 +1,5 @@ // const URL = "https://egg.imoaix.cn/sheet"; -const URL = "http://localhost:3000/sheet"; +const URL = "http://localhost:3000/sheet" const res = await fetch(URL, { method: "POST", @@ -10,6 +10,6 @@ const res = await fetch(URL, { range: "a2YJxa", values: [["hello", "world"]], }), -}); +}) -console.log(JSON.stringify(await res.json(), null, 2)); +console.log(JSON.stringify(await res.json(), null, 2)) diff --git a/test/sendMsg.ts b/test/sendMsg.ts index 1f93ee7..0b91a17 100644 --- a/test/sendMsg.ts +++ b/test/sendMsg.ts @@ -1,5 +1,5 @@ // const URL = "https://egg.imoaix.cn/message"; -const URL = "http://localhost:3000/message"; +const URL = "http://localhost:3000/message" const res = await fetch(URL, { method: "POST", @@ -10,6 +10,6 @@ const res = await fetch(URL, { msg_type: "text", content: "hello, world!", }), -}); +}) -console.log(JSON.stringify(await res.text())); +console.log(JSON.stringify(await res.text())) diff --git a/thunder-tests/thunderActivity.json b/thunder-tests/thunderActivity.json index f647a3a..17e3b24 100644 --- a/thunder-tests/thunderActivity.json +++ b/thunder-tests/thunderActivity.json @@ -1,21 +1,21 @@ -[ - { - "_id": "a5cb7b58-0c22-41fb-bc00-af460ef90c1b", - "colId": "history", - "containerId": "", - "name": "https://self.imoaix.cn/bot", - "url": "https://self.imoaix.cn/bot", - "method": "POST", - "sortNum": 0, - "created": "2023-08-15T07:47:05.439Z", - "modified": "2023-08-15T07:47:34.510Z", - "headers": [], - "params": [], - "body": { - "type": "json", - "raw": "{\r\n \"challenge\": \"c14206ad-5b5f-4a77-82b6-b39b7365392b\",\r\n \"token\": \"tV9djUKSjzVnekV7xTg2Od06NFTcsBnj\",\r\n \"type\": \"url_verification\"\r\n}", - "form": [] - }, - "tests": [] - } -] \ No newline at end of file +[ + { + "_id": "a5cb7b58-0c22-41fb-bc00-af460ef90c1b", + "colId": "history", + "containerId": "", + "name": "https://self.imoaix.cn/bot", + "url": "https://self.imoaix.cn/bot", + "method": "POST", + "sortNum": 0, + "created": "2023-08-15T07:47:05.439Z", + "modified": "2023-08-15T07:47:34.510Z", + "headers": [], + "params": [], + "body": { + "type": "json", + "raw": "{\r\n \"challenge\": \"c14206ad-5b5f-4a77-82b6-b39b7365392b\",\r\n \"token\": \"tV9djUKSjzVnekV7xTg2Od06NFTcsBnj\",\r\n \"type\": \"url_verification\"\r\n}", + "form": [] + }, + "tests": [] + } +] diff --git a/tsconfig.json b/tsconfig.json index 888802f..7556e1d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,22 +1,22 @@ -{ - "compilerOptions": { - "lib": ["ESNext"], - "module": "esnext", - "target": "esnext", - "moduleResolution": "bundler", - "moduleDetection": "force", - "allowImportingTsExtensions": true, - "noEmit": true, - "composite": true, - "strict": true, - "downlevelIteration": true, - "skipLibCheck": true, - "jsx": "react-jsx", - "allowSyntheticDefaultImports": true, - "forceConsistentCasingInFileNames": true, - "allowJs": true, - "types": [ - "bun-types" // add Bun global - ] - } -} +{ + "compilerOptions": { + "lib": ["ESNext"], + "module": "esnext", + "target": "esnext", + "moduleResolution": "bundler", + "moduleDetection": "force", + "allowImportingTsExtensions": true, + "noEmit": true, + "composite": true, + "strict": true, + "downlevelIteration": true, + "skipLibCheck": true, + "jsx": "react-jsx", + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "allowJs": true, + "types": [ + "bun-types" // add Bun global + ] + } +} diff --git a/types/db.ts b/types/db.ts index 242fc91..79e047c 100644 --- a/types/db.ts +++ b/types/db.ts @@ -1,44 +1,44 @@ -import { RecordModel } from "pocketbase"; +import { RecordModel } from "pocketbase" export namespace DB { export interface AppInfo extends RecordModel { - name: string; - app_id: string; - app_secret: string; - app_name: string; - avatar_url: string; - description: string; - tenant_access_token: string; + name: string + app_id: string + app_secret: string + app_name: string + avatar_url: string + description: string + tenant_access_token: string } export interface ApiKey extends RecordModel { - name: string; - user: string; - app: string; - apply_reason: string; + name: string + user: string + app: string + apply_reason: string } export interface MessageGroup extends RecordModel { - desc: string; - id: string; - name: string; - email?: string[]; - chat_id?: string[]; - open_id?: string[]; - union_id?: string[]; - user_id?: string[]; + desc: string + id: string + name: string + email?: string[] + chat_id?: string[] + open_id?: string[] + union_id?: string[] + user_id?: string[] } export interface MessageLog extends RecordModel { - api_key: string; - group_id?: string; - receive_id?: string; - receive_id_type?: string; - msg_type: string; - content: string; - final_content?: string; - send_result?: any; - error?: string; + api_key: string + group_id?: string + receive_id?: string + receive_id_type?: string + msg_type: string + content: string + final_content?: string + send_result?: any + error?: string } export type MessageLogCreate = Pick< @@ -52,8 +52,8 @@ export namespace DB { | "final_content" | "send_result" | "error" - >; + > - export type Log = MessageLogCreate; - export type LogCollection = "message_log"; + export type Log = MessageLogCreate + export type LogCollection = "message_log" } diff --git a/types/index.ts b/types/index.ts index 15ec450..277c1b6 100644 --- a/types/index.ts +++ b/types/index.ts @@ -1,7 +1,7 @@ -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"; +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 { DB, LarkAction, LarkEvent, LarkServer, MsgProxy }; +export { DB, LarkAction, LarkEvent, LarkServer, MsgProxy } diff --git a/types/larkAction.ts b/types/larkAction.ts index 050c68f..22e57c6 100644 --- a/types/larkAction.ts +++ b/types/larkAction.ts @@ -3,32 +3,32 @@ export namespace LarkAction { /** * open_id */ - open_id: string; + open_id: string /** * 用户名 * @example zhaoyingbo */ - user_id: string; + user_id: string /** * 当前消息的ID * @example om_038fc0eceed6224a1abc1cdaa4266405 */ - open_message_id: string; + open_message_id: string /** * 对话流ID * @example oc_433b1cb7a9dbb7ebe70a4e1a59cb8bb1 */ - open_chat_id: string; + open_chat_id: string /** * 应用ID * @example 2ee61fe50f4f1657 */ - tenant_key: string; + tenant_key: string /** * token * @example tV9djUKSjzVnekV7xTg2Od06NFTcsBnj */ - token: string; + token: string /** * 事件结果 */ @@ -36,21 +36,21 @@ export namespace LarkAction { /** * 传的参数 */ - value: any; + value: any /** * 标签名 * @example picker_datetime */ - tag: string; + tag: string /** * 选择的事件 * @example 2023-09-03 10:35 +0800 */ - option: string; + option: string /** * 时区 */ - timezone: string; - }; + timezone: string + } } } diff --git a/types/larkEvent.ts b/types/larkEvent.ts index 7830649..aad0ea4 100644 --- a/types/larkEvent.ts +++ b/types/larkEvent.ts @@ -7,32 +7,32 @@ export namespace LarkEvent { * 事件ID * @example 0f8ab23b60993cf8dd15c8cde4d7b0f5 */ - event_id: string; + event_id: string /** * token * @example tV9djUKSjzVnekV7xTg2Od06NFTcsBnj */ - token: string; + token: string /** * 创建时间戳 * @example 1693565712117 */ - create_time: string; + create_time: string /** * 事件类型 * @example im.message.receive_v1 */ - event_type: string; + event_type: string /** * tenant_key * @example 2ee61fe50f4f1657 */ - tenant_key: string; + tenant_key: string /** * app_id * @example cli_a1eff35b43b89063 */ - app_id: string; + app_id: string } /** * 被AT的人的信息 @@ -41,22 +41,22 @@ export namespace LarkEvent { /** * 被艾特的人的ID信息 */ - id: UserIdInfo; + id: UserIdInfo /** * 对应到文本内的内容 * @example "@_user_1" */ - key: string; + key: string /** * 用户名 * @example 小煎蛋 */ - name: string; + name: string /** * 应用ID * @example 2ee61fe50f4f1657 */ - tenant_key: string; + tenant_key: string } /** * 消息内容信息 @@ -66,36 +66,36 @@ export namespace LarkEvent { * 对话流ID * @example oc_433b1cb7a9dbb7ebe70a4e1a59cb8bb1 */ - chat_id: string; + chat_id: string /** * 消息类型 * @example group | p2p */ - chat_type: "group" | "p2p"; + chat_type: "group" | "p2p" /** * JSON字符串文本内容 * @example "{\"text\":\"@_user_1 测试\"}" */ - content: string; + content: string /** * 消息发送时间戳 * @example 1693565711996 */ - create_time: string; + create_time: string /** * 被艾特的人信息 */ - mentions?: Mention[]; + mentions?: Mention[] /** * 当前消息的ID * @example om_038fc0eceed6224a1abc1cdaa4266405 */ - message_id: string; + message_id: string /** * 消息类型 * @example text、post、image、file、audio、media、sticker、interactive、share_chat、share_user */ - message_type: string; + message_type: string } /** @@ -106,17 +106,17 @@ export namespace LarkEvent { * 用户标记 * @example ou_032f507d08f9a7f28b042fcd086daef5 */ - open_id: string; + open_id: string /** * 用户标记 * @example on_7111660fddd8302ce47bf1999147c011 */ - union_id: string; + union_id: string /** * 用户名 * @example zhaoyingbo */ - user_id: string; + user_id: string } /** * 消息发送者信息 @@ -125,24 +125,24 @@ export namespace LarkEvent { /** * id 相关信息 */ - sender_id: UserIdInfo; + sender_id: UserIdInfo /** * 发送者类型 * @example user */ - sender_type: string; + sender_type: string /** * 应用ID * @example 2ee61fe50f4f1657 */ - tenant_key: string; + tenant_key: string } /** * 事件详情 */ export interface Event { - message: Message; - sender: Sender; + message: Message + sender: Sender } export interface Data { @@ -150,14 +150,14 @@ export namespace LarkEvent { * 协议版本 * @example 2.0 */ - schema: string; + schema: string /** * 事件头 */ - header: Header; + header: Header /** * 事件详情 */ - event: Event; + event: Event } } diff --git a/types/larkServer.ts b/types/larkServer.ts index 199432b..5fa5a96 100644 --- a/types/larkServer.ts +++ b/types/larkServer.ts @@ -3,88 +3,88 @@ export namespace LarkServer { /** * 访问令牌 */ - access_token: string; + access_token: string /** * 员工ID */ - employee_id: string; + employee_id: string /** * 令牌过期时间 */ - expires_in: number; + expires_in: number /** * 开放ID */ - open_id: string; + open_id: string /** * 刷新令牌 */ - refresh_token: string; + refresh_token: string /** * 会话密钥 */ - session_key: string; + session_key: string /** * 租户密钥 */ - tenant_key: string; + tenant_key: string /** * 联盟ID */ - union_id: string; + union_id: string } export interface SuccessDocMeta { - doc_token: string; - doc_type: string; - title: string; - owner_id: string; - create_time: string; - latest_modify_user: string; - latest_modify_time: string; - url: string; - sec_label_name: string; + doc_token: string + doc_type: string + title: string + owner_id: string + create_time: string + latest_modify_user: string + latest_modify_time: string + url: string + sec_label_name: string } export interface FailedDocMeta { - token: string; - code: number; + token: string + code: number } export interface BaseRes { - code: number; - data: any; - msg: string; + code: number + data: any + msg: string } export interface UserSessionRes extends BaseRes { - data: UserSession; + data: UserSession } export interface UserInfoRes extends BaseRes { data: { - user: any; - }; + user: any + } } export interface BatchUserInfoRes extends BaseRes { data: { - items: any[]; - }; + items: any[] + } } export interface BatchDocMetaRes extends BaseRes { data: { - metas: SuccessDocMeta[]; - failed_list: FailedDocMeta[]; - }; + metas: SuccessDocMeta[] + failed_list: FailedDocMeta[] + } } export type ReceiveIDType = @@ -92,7 +92,7 @@ export namespace LarkServer { | "user_id" | "union_id" | "email" - | "chat_id"; + | "chat_id" export type MsgType = | "text" @@ -104,5 +104,5 @@ export namespace LarkServer { | "sticker" | "interactive" | "share_chat" - | "share_user"; + | "share_user" } diff --git a/types/msgProxy.ts b/types/msgProxy.ts index 249c908..b9dc2c2 100644 --- a/types/msgProxy.ts +++ b/types/msgProxy.ts @@ -1,17 +1,17 @@ -import { LarkServer } from "./larkServer"; +import { LarkServer } from "./larkServer" export namespace MsgProxy { export interface BaseBody { - msg_type: LarkServer.MsgType; - content: string; - api_key: string; + msg_type: LarkServer.MsgType + content: string + api_key: string } export interface GroupBody extends BaseBody { - group_id: string; + group_id: string } export interface NormalBody extends BaseBody { - receive_id: string; - receive_id_type: LarkServer.ReceiveIDType; + receive_id: string + receive_id_type: LarkServer.ReceiveIDType } - export type Body = GroupBody & NormalBody; + export type Body = GroupBody & NormalBody } diff --git a/types/remind.ts b/types/remind.ts index 0c7299f..5d68478 100644 --- a/types/remind.ts +++ b/types/remind.ts @@ -5,20 +5,20 @@ export interface User { /** * id */ - id: string; + id: string /** * 用户名 * @example zhaoyingbo */ - userId: string; + userId: string /** * open_id */ - openId: string; + openId: string /** * 提醒列表 */ - remindList: string[]; + remindList: string[] } /** @@ -28,31 +28,31 @@ export interface Remind { /** * id */ - id: string; + id: string /** * 所有者信息,绑定用户表的id */ - owner: string; + owner: string /** * 消息Id */ - messageId: string; + messageId: string /** * 接收者类型 */ - subscriberType: "open_id" | "user_id" | "union_id" | "email" | "chat_id"; + subscriberType: "open_id" | "user_id" | "union_id" | "email" | "chat_id" /** * 接收者Id */ - subscriberId: string; + subscriberId: string /** * 是否需要回复,不需要回复的也不会重复提醒 */ - needReply: boolean; + needReply: boolean /** * 延迟时间 */ - delayTime: number; + delayTime: number /** * 卡片信息,用于绘制初始卡片、确认卡片、取消卡片、延迟卡片 */ @@ -60,28 +60,28 @@ export interface Remind { /** * 提醒标题,必须要有 */ - title: string; + title: string /** * 插图key */ - imageKey?: string; + imageKey?: string /** * 提醒内容,为空不显示 */ - content?: string; + content?: string /** * 确认文本,为空不显示,为需要回复卡片时,如果为空则默认为“完成” */ - confirmText?: string; + confirmText?: string /** * 取消文本,为空不显示 */ - cancelText?: string; + cancelText?: string /** * 延迟文本,为空不显示 */ - delayText?: string; - } | null; + delayText?: string + } | null /** * 卡片模板信息 */ @@ -91,7 +91,7 @@ export interface Remind { * ${owner} 所有者 * ${remindTime} 提醒时间 */ - pendingTemplateId: string; + pendingTemplateId: string /** * 交互之后的卡片模板ID,如果有这个就不会用下边三个但是都会注入变量 * ${owner} 所有者 @@ -99,38 +99,38 @@ export interface Remind { * ${result} 交互结果,会读卡片按钮绑定的变量text,如果没有则是绑定的result对应的 已确认、已取消、已延迟 * ${interactTime} 交互时间 */ - interactedTemplateId: string; + interactedTemplateId: string /** * 确认之后的卡片模板ID */ - confirmedTemplateId: string; + confirmedTemplateId: string /** * 取消之后的卡片模板ID */ - cancelededTemplateId: string; + cancelededTemplateId: string /** * 延迟之后的卡片模板ID */ - delayedTemplateId: string; - } | null; + delayedTemplateId: string + } | null /** * 提醒时间 */ - remindTimes: RemindTime[]; + remindTimes: RemindTime[] /** * 是否启用 */ - enabled: boolean; + enabled: boolean /** * 下次提醒的时间,格式为yyyy-MM-dd HH:mm */ - nextRemindTime: string; + nextRemindTime: string /** * 下次提醒时间的中文 */ - nextRemindTimeCHS: string; + nextRemindTimeCHS: string } /** @@ -155,23 +155,23 @@ export interface RemindTime { | "monthly" | "yearly" | "workday" - | "holiday"; + | "holiday" /** * 提醒时间,格式为HH:mm, single类型时仅作展示用,类型为yyyy-MM-dd HH:mm */ - time: string; + time: string /** * 星期几[1-7],当frequency为weekly时有效 */ - daysOfWeek: number[]; + daysOfWeek: number[] /** * 每月的几号[1-31],当frequency为monthly时有效 */ - daysOfMonth: number[]; + daysOfMonth: number[] /** * 每年的哪天提醒,当frequency为 yearly 时有效,格式为MM-dd */ - dayOfYear: string; + dayOfYear: string } /** @@ -182,15 +182,15 @@ export interface RemindRecord { /** * 记录Id */ - id: string; + id: string /** * 关联的提醒Id */ - remindId: string; + remindId: string /** * 发送的卡片Id */ - messageId: string; + messageId: string /** * 提醒状态 * pending: 待确认 @@ -198,17 +198,17 @@ export interface RemindRecord { * confirmed: 已确认 * canceled: 已取消 */ - status: "pending" | "delayed" | "confirmed" | "canceled"; + status: "pending" | "delayed" | "confirmed" | "canceled" /** * 本次提醒时间,格式为yyyy-MM-dd HH:mm */ - remindTime: string; + remindTime: string /** * 用户交互的时间,格式为yyyy-MM-dd HH:mm */ - interactTime: string; + interactTime: string /** * 用户回答的结果,类似每天 07:00 */ - result: object; + result: object } diff --git a/types/sheetProxy.ts b/types/sheetProxy.ts index c50c02b..a783827 100644 --- a/types/sheetProxy.ts +++ b/types/sheetProxy.ts @@ -4,15 +4,15 @@ export namespace SheetProxy { } export interface Body { - api_key: string; - sheet_token: string; - type: "insert"; - range: string; - values: string[][]; // 二维数组 + api_key: string + sheet_token: string + type: "insert" + range: string + values: string[][] // 二维数组 } // 判断一个值是否是枚举中的值 export const isType = (value: any): value is Type => { - return Object.values(Type).includes(value); - }; + return Object.values(Type).includes(value) + } } diff --git a/utils/msgTools.ts b/utils/msgTools.ts index 51c3655..196fafb 100644 --- a/utils/msgTools.ts +++ b/utils/msgTools.ts @@ -1,91 +1,89 @@ -import { LarkAction, LarkEvent } from "../types"; - -/** - * 是否为事件消息 - * @param {LarkEvent.Data} body - */ -export const getIsEventMsg = (body: LarkEvent.Data) => { - return body?.header?.event_type === "im.message.receive_v1"; -}; - -/** - * 获取事件文本类型 - * @param {LarkEvent.Data} body - * @returns - */ -export const getMsgType = (body: LarkEvent.Data) => { - return body?.event?.message?.message_type; -}; - -/** - * 获取对话流Id - * @param {LarkEvent.Data} body - * @returns - */ -export const getChatId = (body: LarkEvent.Data) => { - return body?.event?.message?.chat_id; -}; - -/** - * 获取用户Id - * @param {LarkEvent.Data} body - * @returns - */ -export const getUserId = (body: LarkEvent.Data) => { - return body?.event?.sender?.sender_id?.user_id; -}; - -/** - * 是否为Action消息 - * @param {LarkAction.Data} body - */ -export const getIsActionMsg = (body: LarkAction.Data) => { - return body?.action; -}; - -/** - * 获取Action类型 - * @param {LarkAction.Data} body - * @returns {string} Action类型 - */ -export const getActionType = (body: LarkAction.Data) => { - return body?.action?.tag; -}; - -/** - * 获取文本内容并剔除艾特信息 - * @param {LarkEvent.Data} body - * @returns {string} 文本内容 - */ -export const getMsgText = (body: LarkEvent.Data) => { - try { - const { text }: { text: string } = JSON.parse( - body?.event?.message?.content - ); - // 去掉@_user_1相关的内容,例如 '@_user_1 测试' -> '测试' - const textWithoutAt = text.replace(/@_user_\d+/g, ""); - // // 去除空格和换行 - // const textWithoutSpace = textWithoutAt.replace(/[\s\n]/g, ""); - return textWithoutAt; - } catch (e) { - return ""; - } -}; - -/** - * 获取聊天类型 - * @param {LarkEvent.Data} body - * @returns {string} 聊天类型 - */ -export const getChatType = (body: LarkEvent.Data) => { - return body?.event?.message?.chat_type; -}; - -/** - * 获取艾特信息 - * @param {LarkEvent.Data} body - * @returns {Array} 艾特信息 - */ -export const getMentions = (body: LarkEvent.Data) => { - return body?.event?.message?.mentions; -}; +import { LarkAction, LarkEvent } from "../types" + +/** + * 是否为事件消息 + * @param {LarkEvent.Data} body + */ +export const getIsEventMsg = (body: LarkEvent.Data) => { + return body?.header?.event_type === "im.message.receive_v1" +} + +/** + * 获取事件文本类型 + * @param {LarkEvent.Data} body + * @returns + */ +export const getMsgType = (body: LarkEvent.Data) => { + return body?.event?.message?.message_type +} + +/** + * 获取对话流Id + * @param {LarkEvent.Data} body + * @returns + */ +export const getChatId = (body: LarkEvent.Data) => { + return body?.event?.message?.chat_id +} + +/** + * 获取用户Id + * @param {LarkEvent.Data} body + * @returns + */ +export const getUserId = (body: LarkEvent.Data) => { + return body?.event?.sender?.sender_id?.user_id +} + +/** + * 是否为Action消息 + * @param {LarkAction.Data} body + */ +export const getIsActionMsg = (body: LarkAction.Data) => { + return body?.action +} + +/** + * 获取Action类型 + * @param {LarkAction.Data} body + * @returns {string} Action类型 + */ +export const getActionType = (body: LarkAction.Data) => { + return body?.action?.tag +} + +/** + * 获取文本内容并剔除艾特信息 + * @param {LarkEvent.Data} body + * @returns {string} 文本内容 + */ +export const getMsgText = (body: LarkEvent.Data) => { + try { + const { text }: { text: string } = JSON.parse(body?.event?.message?.content) + // 去掉@_user_1相关的内容,例如 '@_user_1 测试' -> '测试' + const textWithoutAt = text.replace(/@_user_\d+/g, "") + // // 去除空格和换行 + // const textWithoutSpace = textWithoutAt.replace(/[\s\n]/g, ""); + return textWithoutAt + } catch (e) { + return "" + } +} + +/** + * 获取聊天类型 + * @param {LarkEvent.Data} body + * @returns {string} 聊天类型 + */ +export const getChatType = (body: LarkEvent.Data) => { + return body?.event?.message?.chat_type +} + +/** + * 获取艾特信息 + * @param {LarkEvent.Data} body + * @returns {Array} 艾特信息 + */ +export const getMentions = (body: LarkEvent.Data) => { + return body?.event?.message?.mentions +} diff --git a/utils/pathTools.ts b/utils/pathTools.ts index ce5c824..2204b52 100644 --- a/utils/pathTools.ts +++ b/utils/pathTools.ts @@ -1,12 +1,12 @@ // 裁剪指定prefix路径 export function trimPathPrefix(path: string, prefix: string): string { - return path.startsWith(prefix) ? path.slice(prefix.length) : path; + return path.startsWith(prefix) ? path.slice(prefix.length) : path } export const safeJsonStringify = (obj: any) => { try { - return JSON.stringify(obj); + return JSON.stringify(obj) } catch (e) { - return String(obj); + return String(obj) } -}; +} diff --git a/utils/pbTools.ts b/utils/pbTools.ts index f5ae9a3..58fd310 100644 --- a/utils/pbTools.ts +++ b/utils/pbTools.ts @@ -1,18 +1,22 @@ -export const managePb404 = async (dbFunc: Function): Promise => { - try { - return await dbFunc(); - } catch (err: any) { - if (err?.message === "The requested resource wasn't found.") { - return null; - } else throw err; - } -}; - -export const managePbError = async (dbFunc: Function): Promise => { - try { - return await dbFunc(); - } catch (err: any) { - console.log("🚀 ~ managePbError ~ err:", err); - return null; - } -}; +export const managePb404 = async ( + dbFunc: () => Promise +): Promise => { + try { + return await dbFunc() + } catch (err: any) { + if (err?.message === "The requested resource wasn't found.") { + return null + } else throw err + } +} + +export const managePbError = async ( + dbFunc: () => Promise +): Promise => { + try { + return await dbFunc() + } catch (err: any) { + console.log("🚀 ~ managePbError ~ err:", err) + return null + } +}