feat: 接入lint 和 husky
All checks were successful
Egg CI/CD / build-image (push) Successful in 32s
Egg CI/CD / deploy (push) Successful in 37s

This commit is contained in:
zhaoyingbo 2024-07-25 01:48:22 +00:00
parent 61919e0155
commit 6e65581bbf
56 changed files with 1260 additions and 1179 deletions

View File

@ -1,33 +1,27 @@
{ {
"name": "egg_server", "name": "ci_monitor",
"image": "micr.cloud.mioffice.cn/zhaoyingbo/dev:bun", "image": "mcr.microsoft.com/devcontainers/typescript-node:20",
"remoteUser": "bun", "customizations": {
"containerUser": "bun", "vscode": {
"forwardPorts": [3000], "settings": {
"customizations": { "files.autoSave": "afterDelay",
"vscode": { "editor.guides.bracketPairs": true,
"settings": { "editor.defaultFormatter": "esbenp.prettier-vscode",
"files.autoSave": "afterDelay", "editor.formatOnSave": true,
"editor.guides.bracketPairs": true, "editor.codeActionsOnSave": {
"editor.defaultFormatter": "esbenp.prettier-vscode", "source.fixAll.eslint": "always"
"editor.formatOnSave": true, }
"github.copilot.chat.localeOverride": "zh-CN" },
}, "extensions": [
"extensions": [ "eamodio.gitlens",
"dbaeumer.vscode-eslint", "Gruntfuggly.todo-tree",
"esbenp.prettier-vscode", "dbaeumer.vscode-eslint",
"eamodio.gitlens", "esbenp.prettier-vscode",
"unifiedjs.vscode-mdx", "ChakrounAnas.turbo-console-log",
"litiany4.umijs-plugin-model", "streetsidesoftware.code-spell-checker",
"oderwat.indent-rainbow", "MS-CEINTL.vscode-language-pack-zh-hans"
"jock.svg", ]
"ChakrounAnas.turbo-console-log", }
"Gruntfuggly.todo-tree", },
"MS-CEINTL.vscode-language-pack-zh-hans", "onCreateCommand": "curl -fsSL https://bun.sh/install | bash"
"GitHub.copilot", }
"GitHub.copilot-chat"
]
}
},
"postCreateCommand": "bash -i /workspaces/egg_server/.devcontainer/initial.bash"
}

View File

@ -1 +0,0 @@
echo "alias dev=\"cd /workspaces/egg_server && bun run dev\"" >> /home/bun/.bashrc

17
.devcontainer/readme.md Normal file
View File

@ -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)

View File

@ -1,57 +1,57 @@
name: Egg CI/CD name: Egg CI/CD
on: [push] on: [push]
jobs: jobs:
build-image: build-image:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: catthehacker/ubuntu:act-latest container: catthehacker/ubuntu:act-latest
steps: steps:
- name: Check out repository code - name: Check out repository code
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
registry: git.yingbo.im:333 registry: git.yingbo.im:333
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push - name: Build and push
uses: docker/build-push-action@v4 uses: docker/build-push-action@v4
with: with:
push: true push: true
tags: git.yingbo.im:333/zhaoyingbo/egg_server:${{ github.sha }} tags: git.yingbo.im:333/zhaoyingbo/egg_server:${{ github.sha }}
deploy: deploy:
needs: build-image needs: build-image
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: catthehacker/ubuntu:act-latest container: catthehacker/ubuntu:act-latest
steps: steps:
# 检出代码 # 检出代码
- name: Check out repository code - name: Check out repository code
uses: actions/checkout@v3 uses: actions/checkout@v3
# 使用scp命令将docker-compose.yml文件上传到服务器 # 使用scp命令将docker-compose.yml文件上传到服务器
- name: Upload docker-compose.yml to server - name: Upload docker-compose.yml to server
uses: appleboy/scp-action@master uses: appleboy/scp-action@master
with: with:
host: ${{ secrets.SERVER_HOST }} host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USERNAME }} username: ${{ secrets.SERVER_USERNAME }}
key: ${{ secrets.SERVER_KEY }} key: ${{ secrets.SERVER_KEY }}
port: ${{ secrets.SERVER_PORT }} port: ${{ secrets.SERVER_PORT }}
source: docker-compose.yml source: docker-compose.yml
target: /home/${{ secrets.SERVER_USERNAME }}/docker/egg_server target: /home/${{ secrets.SERVER_USERNAME }}/docker/egg_server
# 登录服务器执行docker-compose命令 # 登录服务器执行docker-compose命令
- name: Login to the server and execute docker-compose command - name: Login to the server and execute docker-compose command
uses: appleboy/ssh-action@master uses: appleboy/ssh-action@master
with: with:
host: ${{ secrets.SERVER_HOST }} host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USERNAME }} username: ${{ secrets.SERVER_USERNAME }}
key: ${{ secrets.SERVER_KEY }} key: ${{ secrets.SERVER_KEY }}
port: ${{ secrets.SERVER_PORT }} port: ${{ secrets.SERVER_PORT }}
script: | script: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} git.yingbo.im:333 docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} git.yingbo.im:333
cd /home/${{ secrets.SERVER_USERNAME }}/docker/egg_server cd /home/${{ secrets.SERVER_USERNAME }}/docker/egg_server
sed -i "s/sha/${{ github.sha }}/g" docker-compose.yml sed -i "s/sha/${{ github.sha }}/g" docker-compose.yml
docker compose up -d --force-recreate --no-deps egg_server docker compose up -d --force-recreate --no-deps egg_server

2
.gitignore vendored
View File

@ -49,7 +49,7 @@ profile-*
.idea .idea
# vscode # vscode
.vscode # .vscode
*code-workspace *code-workspace
# clinic # clinic

1
.husky/commit-msg Normal file
View File

@ -0,0 +1 @@
npx --no -- commitlint --edit $1

1
.husky/pre-commit Normal file
View File

@ -0,0 +1 @@
lint-staged

15
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,15 @@
{
"cSpell.words": [
"bunx",
"CEINTL",
"Chakroun",
"commitlint",
"dbaeumer",
"devcontainers",
"eamodio",
"esbenp",
"Gruntfuggly",
"tseslint",
"wlpbbgiky"
]
}

BIN
bun.lockb

Binary file not shown.

1
commitlint.config.js Normal file
View File

@ -0,0 +1 @@
export default { extends: ["@commitlint/config-conventional"] }

View File

@ -1,16 +1,16 @@
import { DB } from "../../types"; import { DB } from "../../types"
import { managePbError } from "../../utils/pbTools"; import { managePbError } from "../../utils/pbTools"
import pbClient from "../pbClient"; import pbClient from "../pbClient"
const getOne = (id: string) => const getOne = (id: string) =>
managePbError<DB.MessageGroup>(() => managePbError<DB.MessageGroup>(() =>
pbClient.collection("api_key").getOne(id, { pbClient.collection("api_key").getOne(id, {
expand: "app", expand: "app",
}) })
); )
const apiKey = { const apiKey = {
getOne, getOne,
}; }
export default apiKey; export default apiKey

View File

@ -1,6 +1,6 @@
import pbClient from "../pbClient"; import { DB } from "../../types"
import { managePb404 } from "../../utils/pbTools"; import { managePb404 } from "../../utils/pbTools"
import { DB } from "../../types"; import pbClient from "../pbClient"
/** /**
* *
@ -10,13 +10,13 @@ import { DB } from "../../types";
const get = async (appName: string) => const get = async (appName: string) =>
managePb404<DB.AppInfo>(() => managePb404<DB.AppInfo>(() =>
pbClient.collection("app_info").getFirstListItem(`name='${appName}'`) pbClient.collection("app_info").getFirstListItem(`name='${appName}'`)
); )
/** /**
* *
* @returns * @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 * @returns
*/ */
const getVal = async (appName: string, key: string) => { const getVal = async (appName: string, key: string) => {
const config = await get(appName); const config = await get(appName)
if (!config) return ""; if (!config) return ""
return config[key] || ""; return config[key] || ""
}; }
const appInfo = { const appInfo = {
get, get,
getVal, getVal,
getFullList, getFullList,
}; }
export default appInfo; export default appInfo

View File

@ -1,15 +1,15 @@
import messageGroup from "./messageGroup"; import apiKey from "./apiKey"
import tenantAccessToken from "./tenantAccessToken"; import appInfo from "./appInfo"
import appInfo from "./appInfo"; import log from "./log"
import log from "./log"; import messageGroup from "./messageGroup"
import apiKey from "./apiKey"; import tenantAccessToken from "./tenantAccessToken"
const db = { const db = {
apiKey, apiKey,
appInfo, appInfo,
messageGroup, messageGroup,
log, log,
tenantAccessToken, tenantAccessToken,
}; }
export default db; export default db

View File

@ -1,12 +1,12 @@
import { DB } from "../../types"; import { DB } from "../../types"
import { managePbError } from "../../utils/pbTools"; import { managePbError } from "../../utils/pbTools"
import pbClient from "../pbClient"; import pbClient from "../pbClient"
const create = (collection: DB.LogCollection, log: DB.Log) => const create = (collection: DB.LogCollection, log: DB.Log) =>
managePbError(() => pbClient.collection(collection).create(log)); managePbError(() => pbClient.collection(collection).create(log))
const log = { const log = {
create, create,
}; }
export default log; export default log

View File

@ -1,14 +1,14 @@
import { DB } from "../../types"; import { DB } from "../../types"
import { managePbError } from "../../utils/pbTools"; import { managePbError } from "../../utils/pbTools"
import pbClient from "../pbClient"; import pbClient from "../pbClient"
const getOne = (groupId: string) => const getOne = (groupId: string) =>
managePbError<DB.MessageGroup>(() => managePbError<DB.MessageGroup>(() =>
pbClient.collection("message_group").getOne(groupId) pbClient.collection("message_group").getOne(groupId)
); )
const messageGroup = { const messageGroup = {
getOne, getOne,
}; }
export default messageGroup; export default messageGroup

View File

@ -1,7 +1,7 @@
import PocketBase from "pocketbase"; import PocketBase from "pocketbase"
const pbClient = new PocketBase("https://eggpb.imoaix.cn"); const pbClient = new PocketBase("https://eggpb.imoaix.cn")
pbClient.autoCancellation(false); pbClient.autoCancellation(false)
export default pbClient; export default pbClient

View File

@ -1,40 +1,42 @@
import appInfo from "../appInfo"; import appInfo from "../appInfo"
import pbClient from "../pbClient"; import pbClient from "../pbClient"
const tokenCache = {} as Record<string, string>; const tokenCache = {} as Record<string, string>
/** /**
* token * token
* @param {string} id id * @param {string} id id
* @param {string} appName * @param {string} appName
* @param {string} value token * @param {string} value token
*/ */
const update = async (id: string, appName: string, value: string) => { const update = async (id: string, appName: string, value: string) => {
try { try {
await pbClient await pbClient
.collection("app_info") .collection("app_info")
.update(id, { tenant_access_token: value }); .update(id, { tenant_access_token: value })
} catch {} } catch {
/* empty */
tokenCache[appName] = value; }
console.log(`reset ${appName} access token success`, value);
}; tokenCache[appName] = value
console.log(`reset ${appName} access token success`, value)
/** }
* token
* @param {string} appName /**
* @returns {string} token * token
*/ * @param {string} appName
const get = async (appName: string) => { * @returns {string} token
if (tokenCache[appName]) return tokenCache[appName]; */
const config = await appInfo.getVal(appName, "tenant_access_token"); const get = async (appName: string) => {
tokenCache[appName] = config; if (tokenCache[appName]) return tokenCache[appName]
return config; const config = await appInfo.getVal(appName, "tenant_access_token")
}; tokenCache[appName] = config
return config
const tenantAccessToken = { }
get,
update, const tenantAccessToken = {
}; get,
update,
export default tenantAccessToken; }
export default tenantAccessToken

View File

@ -1,9 +1,9 @@
version: "3" version: "3"
services: services:
egg_server: egg_server:
image: git.yingbo.im:333/zhaoyingbo/egg_server:sha image: git.yingbo.im:333/zhaoyingbo/egg_server:sha
container_name: egg_server container_name: egg_server
restart: always restart: always
ports: ports:
- 3003:3000 - 3003:3000

22
eslint.config.js Normal file
View File

@ -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",
},
},
]

View File

@ -1,36 +1,36 @@
import { manageBotReq } from "./routes/bot"; import { manageBotReq } from "./routes/bot"
import { manageMessageReq } from "./routes/message"; import { manageMessageReq } from "./routes/message"
import { manageMicroAppReq } from "./routes/microApp"; import { manageMicroAppReq } from "./routes/microApp"
import { manageSheetReq } from "./routes/sheet"; import { manageSheetReq } from "./routes/sheet"
import { initSchedule } from "./schedule"; import { initSchedule } from "./schedule"
import netTool from "./services/netTool"; import netTool from "./services/netTool"
initSchedule(); initSchedule()
const server = Bun.serve({ const server = Bun.serve({
async fetch(req) { async fetch(req) {
try { try {
const url = new URL(req.url); const url = new URL(req.url)
// 根路由 // 根路由
if (url.pathname === "/") return netTool.ok("hello, glade to see you!"); if (url.pathname === "/") return netTool.ok("hello, glade to see you!")
// 机器人 // 机器人
if (url.pathname === "/bot") return await manageBotReq(req); if (url.pathname === "/bot") return await manageBotReq(req)
// 消息代理发送 // 消息代理发送
if (url.pathname === "/message") return await manageMessageReq(req); if (url.pathname === "/message") return await manageMessageReq(req)
// 表格代理操作 // 表格代理操作
if (url.pathname === "/sheet") return await manageSheetReq(req); if (url.pathname === "/sheet") return await manageSheetReq(req)
// 小程序 // 小程序
if (url.pathname.startsWith("/micro_app")) if (url.pathname.startsWith("/micro_app"))
return await manageMicroAppReq(req); return await manageMicroAppReq(req)
// 其他 // 其他
return netTool.ok("hello, glade to see you!"); return netTool.ok("hello, glade to see you!")
} catch (error: any) { } catch (error: any) {
// 错误处理 // 错误处理
console.error("🚀 ~ serve ~ error", error); console.error("🚀 ~ serve ~ error", error)
return netTool.serverError(error.message || "server error"); return netTool.serverError(error.message || "server error")
} }
}, },
port: 3000, port: 3000,
}); })
console.log(`Listening on ${server.hostname}:${server.port}`); console.log(`Listening on ${server.hostname}:${server.port}`)

View File

@ -3,17 +3,36 @@
"module": "index.ts", "module": "index.ts",
"type": "module", "type": "module",
"scripts": { "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": { "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": { "peerDependencies": {
"typescript": "^5.0.0" "typescript": "^5.0.0"
}, },
"dependencies": { "dependencies": {
"@types/node-schedule": "^2.1.6",
"node-schedule": "^2.1.1", "node-schedule": "^2.1.1",
"pocketbase": "^0.21.3" "pocketbase": "^0.21.3"
} }
} }

6
prettier.config.js Normal file
View File

@ -0,0 +1,6 @@
export default {
trailingComma: "es5",
tabWidth: 2,
semi: false,
singleQuote: false,
}

View File

@ -1,63 +1,63 @@
import { sleep } from "bun"; import { sleep } from "bun"
import { getActionType, getIsActionMsg } from "../../utils/msgTools";
import service from "../../services"
import service from "../../services"; import { LarkAction } from "../../types"
import { LarkAction } from "../../types"; import { getActionType, getIsActionMsg } from "../../utils/msgTools"
/** /**
* ChatId卡片 * ChatId卡片
* @param {LarkAction.Data} body * @param {LarkAction.Data} body
*/ */
const makeChatIdCard = async (body: LarkAction.Data) => { const makeChatIdCard = async (body: LarkAction.Data) => {
await sleep(500); await sleep(500)
return JSON.stringify({ return JSON.stringify({
type: "template", type: "template",
data: { data: {
config: { config: {
update_multi: true, update_multi: true,
}, },
template_id: "ctp_AAi3NnHb6zgK", template_id: "ctp_AAi3NnHb6zgK",
template_variable: { template_variable: {
chat_id: body.open_chat_id, chat_id: body.open_chat_id,
}, },
}, },
}); })
}; }
const ACTION_MAP = { const ACTION_MAP = {
chat_id: makeChatIdCard, chat_id: makeChatIdCard,
}; }
/** /**
* *
* @param {LarkAction.Data} body * @param {LarkAction.Data} body
*/ */
const manageBtnClick = async (body: LarkAction.Data) => { const manageBtnClick = async (body: LarkAction.Data) => {
const { action } = body?.action?.value as { const { action } = body?.action?.value as {
action: keyof typeof ACTION_MAP; action: keyof typeof ACTION_MAP
}; }
if (!action) return; if (!action) return
const func = ACTION_MAP[action]; const func = ACTION_MAP[action]
if (!func) return; if (!func) return
const card = await func(body); const card = await func(body)
if (!card) return; if (!card) return
// 更新飞书的卡片 // 更新飞书的卡片
await service.lark.message.update()(body.open_message_id, card); await service.lark.message.update()(body.open_message_id, card)
}; }
/** /**
* Action消息 * Action消息
* @param {LarkAction.Data} body * @param {LarkAction.Data} body
* @returns {boolean} * @returns {boolean}
*/ */
export const manageActionMsg = (body: LarkAction.Data) => { export const manageActionMsg = (body: LarkAction.Data) => {
// 过滤非Action消息 // 过滤非Action消息
if (!getIsActionMsg(body)) { if (!getIsActionMsg(body)) {
return false; return false
} }
const actionType = getActionType(body); const actionType = getActionType(body)
if (actionType === "button") { if (actionType === "button") {
manageBtnClick(body); manageBtnClick(body)
} }
return true; return true
}; }

View File

@ -1,158 +1,158 @@
import service from "../../services"; import service from "../../services"
import { LarkEvent } from "../../types"; import { LarkEvent } from "../../types"
import { import {
getChatId, getChatId,
getChatType, getChatType,
getIsEventMsg, getIsEventMsg,
getMentions, getMentions,
getMsgText, getMsgText,
getMsgType, getMsgType,
} from "../../utils/msgTools"; } from "../../utils/msgTools"
/** /**
* P2P或者群聊并且艾特了小煎蛋 * P2P或者群聊并且艾特了小煎蛋
* @param {LarkEvent.Data} body * @param {LarkEvent.Data} body
* @returns {boolean} P2P或者群聊并且艾特了小煎蛋 * @returns {boolean} P2P或者群聊并且艾特了小煎蛋
*/ */
const getIsP2pOrGroupAtBot = (body: LarkEvent.Data) => { const getIsP2pOrGroupAtBot = (body: LarkEvent.Data) => {
const isP2p = getChatType(body) === "p2p"; const isP2p = getChatType(body) === "p2p"
const isAtBot = getMentions(body)?.some?.( const isAtBot = getMentions(body)?.some?.(
(mention) => mention.name === "小煎蛋" (mention) => mention.name === "小煎蛋"
); )
return isP2p || isAtBot; return isP2p || isAtBot
}; }
/** /**
* *
* @param {LarkEvent.Data} body * @param {LarkEvent.Data} body
* @returns {boolean} * @returns {boolean}
*/ */
const filterIllegalMsg = (body: LarkEvent.Data) => { const filterIllegalMsg = (body: LarkEvent.Data) => {
// 没有chatId的消息不处理 // 没有chatId的消息不处理
const chatId = getChatId(body); const chatId = getChatId(body)
if (!chatId) return true; if (!chatId) return true
// 获取msgType // 获取msgType
const msgType = getMsgType(body); const msgType = getMsgType(body)
// 放行纯文本消息 // 放行纯文本消息
if (msgType === "text") { if (msgType === "text") {
// 过滤艾特全体成员的消息 // 过滤艾特全体成员的消息
if (getMsgText(body).includes("@_all")) { if (getMsgText(body).includes("@_all")) {
return true; return true
} }
// 放行 // 放行
return false; return false
} }
// 发表情包就直接发回去 // 发表情包就直接发回去
if (msgType === "sticker") { if (msgType === "sticker") {
const content = body?.event?.message?.content; const content = body?.event?.message?.content
service.lark.message.send()("chat_id", chatId, "sticker", content); service.lark.message.send()("chat_id", chatId, "sticker", content)
} }
// 非表情包只在私聊或者群聊中艾特小煎蛋时才回复 // 非表情包只在私聊或者群聊中艾特小煎蛋时才回复
else if (getIsP2pOrGroupAtBot(body)) { else if (getIsP2pOrGroupAtBot(body)) {
const content = JSON.stringify({ const content = JSON.stringify({
text: "哇!这是什么东东?我只懂普通文本啦![可爱]", text: "哇!这是什么东东?我只懂普通文本啦![可爱]",
}); })
service.lark.message.send()("chat_id", chatId, "text", content); service.lark.message.send()("chat_id", chatId, "text", content)
} }
// 非纯文本,全不放行 // 非纯文本,全不放行
return true; return true
}; }
/** /**
* ID消息 * ID消息
* @param chatId - chatId * @param chatId - chatId
*/ */
const manageIdMsg = async (chatId: string) => { const manageIdMsg = async (chatId: string) => {
const content = JSON.stringify({ const content = JSON.stringify({
type: "template", type: "template",
data: { data: {
config: { config: {
update_multi: true, update_multi: true,
}, },
template_id: "ctp_AAi3NnHb6zgK", template_id: "ctp_AAi3NnHb6zgK",
template_variable: { template_variable: {
chat_id: chatId, chat_id: chatId,
}, },
}, },
}); })
service.lark.message.send()("chat_id", chatId, "interactive", content); service.lark.message.send()("chat_id", chatId, "interactive", content)
}; }
/** /**
* *
* @param body - * @param body -
* @returns * @returns
*/ */
const manageCMDMsg = (body: LarkEvent.Data) => { const manageCMDMsg = (body: LarkEvent.Data) => {
const text = getMsgText(body); const text = getMsgText(body)
console.log("🚀 ~ manageCMDMsg ~ text:", text); console.log("🚀 ~ manageCMDMsg ~ text:", text)
const chatId = getChatId(body); const chatId = getChatId(body)
if (text.trim() === "/id") { if (text.trim() === "/id") {
manageIdMsg(chatId); manageIdMsg(chatId)
return true; return true
} }
if (text.trim() === "/ci") { if (text.trim() === "/ci") {
service.attach.ciMonitor(chatId); service.attach.ciMonitor(chatId)
return true; return true
} }
if (text.includes("share") && text.includes("简报")) { if (text.includes("share") && text.includes("简报")) {
service.attach.reportCollector(body); service.attach.reportCollector(body)
// 这个用时比较久,先发一条提醒用户收到了请求 // 这个用时比较久,先发一条提醒用户收到了请求
const content = JSON.stringify({ const content = JSON.stringify({
text: "正在为您收集简报,请稍等片刻~", text: "正在为您收集简报,请稍等片刻~",
}); })
service.lark.message.send()("chat_id", chatId, "text", content); service.lark.message.send()("chat_id", chatId, "text", content)
return true; return true
} }
return false; return false
}; }
/** /**
* *
* @param {LarkEvent.Data} body * @param {LarkEvent.Data} body
*/ */
const replyGuideMsg = async (body: LarkEvent.Data) => { const replyGuideMsg = async (body: LarkEvent.Data) => {
const chatId = getChatId(body); const chatId = getChatId(body)
const content = JSON.stringify({ const content = JSON.stringify({
type: "template", type: "template",
data: { data: {
config: { config: {
enable_forward: false, enable_forward: false,
update_multi: true, update_multi: true,
}, },
template_id: "ctp_AAyVx5R39xU9", template_id: "ctp_AAyVx5R39xU9",
template_variable: { template_variable: {
chat_id: chatId, chat_id: chatId,
}, },
}, },
}); })
await service.lark.message.send()("chat_id", chatId, "interactive", content); await service.lark.message.send()("chat_id", chatId, "interactive", content)
}; }
/** /**
* Event消息 * Event消息
* @param {LarkUserAction} body * @param {LarkUserAction} body
* @returns {boolean} * @returns {boolean}
*/ */
export const manageEventMsg = (body: LarkEvent.Data) => { export const manageEventMsg = (body: LarkEvent.Data) => {
// 过滤非Event消息 // 过滤非Event消息
if (!getIsEventMsg(body)) { if (!getIsEventMsg(body)) {
return false; return false
} }
// 过滤非法消息 // 过滤非法消息
if (filterIllegalMsg(body)) { if (filterIllegalMsg(body)) {
return true; return true
} }
// 处理命令消息 // 处理命令消息
if (manageCMDMsg(body)) { if (manageCMDMsg(body)) {
return true; return true
} }
// 返回引导消息 // 返回引导消息
replyGuideMsg(body); replyGuideMsg(body)
return true; return true
}; }

View File

@ -1,18 +1,18 @@
import netTool from "../../services/netTool"; import netTool from "../../services/netTool"
import { manageActionMsg } from "./actionMsg"; import { manageActionMsg } from "./actionMsg"
import { manageEventMsg } from "./eventMsg"; import { manageEventMsg } from "./eventMsg"
export const manageBotReq = async (req: Request) => { export const manageBotReq = async (req: Request) => {
const body = (await req.json()) as any; const body = (await req.json()) as any
console.log("🚀 ~ manageBotReq ~ body:", body); console.log("🚀 ~ manageBotReq ~ body:", body)
// 验证机器人 // 验证机器人
if (body?.type === "url_verification") { if (body?.type === "url_verification") {
return Response.json({ challenge: body?.challenge }); return Response.json({ challenge: body?.challenge })
} }
// 处理Event消息 // 处理Event消息
if (manageEventMsg(body)) return netTool.ok(); if (manageEventMsg(body)) return netTool.ok()
// 处理Action消息 // 处理Action消息
if (manageActionMsg(body)) return netTool.ok(); if (manageActionMsg(body)) return netTool.ok()
// 其他 // 其他
return netTool.ok(); return netTool.ok()
}; }

View File

@ -1,138 +1,138 @@
import db from "../../db"; import db from "../../db"
import service from "../../services"; import service from "../../services"
import netTool from "../../services/netTool"; import netTool from "../../services/netTool"
import { DB, LarkServer, MsgProxy } from "../../types"; import { DB, LarkServer, MsgProxy } from "../../types"
import { safeJsonStringify } from "../../utils/pathTools"; import { safeJsonStringify } from "../../utils/pathTools"
const LOG_COLLECTION = "message_log"; const LOG_COLLECTION = "message_log"
const validateMessageReq = (body: MsgProxy.Body) => { const validateMessageReq = (body: MsgProxy.Body) => {
if (!body.api_key) { if (!body.api_key) {
return netTool.badRequest("api_key is required"); return netTool.badRequest("api_key is required")
} }
if (!body.group_id && !body.receive_id) { if (!body.group_id && !body.receive_id) {
return netTool.badRequest("group_id or receive_id is required"); return netTool.badRequest("group_id or receive_id is required")
} }
if (body.receive_id && !body.receive_id_type) { if (body.receive_id && !body.receive_id_type) {
return netTool.badRequest("receive_id_type is required"); return netTool.badRequest("receive_id_type is required")
} }
if (!body.msg_type) { if (!body.msg_type) {
return netTool.badRequest("msg_type is required"); return netTool.badRequest("msg_type is required")
} }
if (!body.content) { if (!body.content) {
return netTool.badRequest("content is required"); return netTool.badRequest("content is required")
} }
return false; return false
}; }
export const manageMessageReq = async (req: Request) => { export const manageMessageReq = async (req: Request) => {
const body = (await req.json()) as MsgProxy.Body; const body = (await req.json()) as MsgProxy.Body
// 校验参数 // 校验参数
const validateRes = validateMessageReq(body); const validateRes = validateMessageReq(body)
if (validateRes) return validateRes; if (validateRes) return validateRes
// 处理消息内容 // 处理消息内容
const finalContent = const finalContent =
typeof body.content !== "string" typeof body.content !== "string"
? safeJsonStringify(body.content) ? safeJsonStringify(body.content)
: body.content; : body.content
// 遍历所有id发送消息保存所有对应的messageId // 遍历所有id发送消息保存所有对应的messageId
const sendRes = { const sendRes = {
chat_id: {} as Record<string, any>, chat_id: {} as Record<string, any>,
open_id: {} as Record<string, any>, open_id: {} as Record<string, any>,
union_id: {} as Record<string, any>, union_id: {} as Record<string, any>,
user_id: {} as Record<string, any>, user_id: {} as Record<string, any>,
email: {} as Record<string, any>, email: {} as Record<string, any>,
}; }
// 发送消息列表 // 发送消息列表
const sendList = [] as Promise<any>[]; const sendList = [] as Promise<any>[]
// 构造消息记录 // 构造消息记录
const baseLog: DB.MessageLogCreate = { const baseLog: DB.MessageLogCreate = {
...body, ...body,
final_content: finalContent, final_content: finalContent,
}; }
// 校验api_key // 校验api_key
const apiKeyInfo = await db.apiKey.getOne(body.api_key); const apiKeyInfo = await db.apiKey.getOne(body.api_key)
if (!apiKeyInfo) { if (!apiKeyInfo) {
const error = "api key not found"; const error = "api key not found"
db.log.create(LOG_COLLECTION, { ...baseLog, error }); db.log.create(LOG_COLLECTION, { ...baseLog, error })
return netTool.notFound(error); return netTool.notFound(error)
} }
// 获取app name // 获取app name
const appName = apiKeyInfo.expand?.app?.name; const appName = apiKeyInfo.expand?.app?.name
if (!appName) { if (!appName) {
const error = "app name not found"; const error = "app name not found"
db.log.create(LOG_COLLECTION, { ...baseLog, error }); db.log.create(LOG_COLLECTION, { ...baseLog, error })
return netTool.notFound(error); return netTool.notFound(error)
} }
// 如果有group_id则发送给所有group_id中的人 // 如果有group_id则发送给所有group_id中的人
if (body.group_id) { if (body.group_id) {
// 获取所有接收者 // 获取所有接收者
const group = await db.messageGroup.getOne(body.group_id!); const group = await db.messageGroup.getOne(body.group_id!)
if (!group) { if (!group) {
const error = "message group not found"; const error = "message group not found"
db.log.create(LOG_COLLECTION, { ...baseLog, error }); db.log.create(LOG_COLLECTION, { ...baseLog, error })
return netTool.notFound(error); return netTool.notFound(error)
} }
const { chat_id, open_id, union_id, user_id, email } = group; const { chat_id, open_id, union_id, user_id, email } = group
// 构造发送消息函数 // 构造发送消息函数
const makeSendFunc = (receive_id_type: LarkServer.ReceiveIDType) => { const makeSendFunc = (receive_id_type: LarkServer.ReceiveIDType) => {
return (receive_id: string) => { return (receive_id: string) => {
sendList.push( sendList.push(
service.lark.message service.lark.message
.send(appName)( .send(appName)(
receive_id_type, receive_id_type,
receive_id, receive_id,
body.msg_type, body.msg_type,
finalContent finalContent
) )
.then((res) => { .then((res) => {
sendRes[receive_id_type][receive_id] = res; sendRes[receive_id_type][receive_id] = res
}) })
); )
}; }
}; }
// 创建消息列表 // 创建消息列表
if (chat_id) chat_id.map(makeSendFunc("chat_id")); if (chat_id) chat_id.map(makeSendFunc("chat_id"))
if (open_id) open_id.map(makeSendFunc("open_id")); if (open_id) open_id.map(makeSendFunc("open_id"))
if (union_id) union_id.map(makeSendFunc("union_id")); if (union_id) union_id.map(makeSendFunc("union_id"))
if (user_id) user_id.map(makeSendFunc("user_id")); if (user_id) user_id.map(makeSendFunc("user_id"))
if (email) email.map(makeSendFunc("email")); if (email) email.map(makeSendFunc("email"))
} }
if (body.receive_id && body.receive_id_type) { if (body.receive_id && body.receive_id_type) {
sendList.push( sendList.push(
service.lark.message service.lark.message
.send(appName)( .send(appName)(
body.receive_id_type, body.receive_id_type,
body.receive_id, body.receive_id,
body.msg_type, body.msg_type,
finalContent finalContent
) )
.then((res) => { .then((res) => {
sendRes[body.receive_id_type][body.receive_id] = res; sendRes[body.receive_id_type][body.receive_id] = res
}) })
); )
} }
try { try {
// 里边有错误处理,这里不用担心执行不完 // 里边有错误处理,这里不用担心执行不完
await Promise.all(sendList); await Promise.all(sendList)
// 保存消息记录 // 保存消息记录
db.log.create(LOG_COLLECTION, { ...baseLog, send_result: sendRes }); db.log.create(LOG_COLLECTION, { ...baseLog, send_result: sendRes })
return netTool.ok(sendRes); return netTool.ok(sendRes)
} catch { } catch {
const error = "send msg failed"; const error = "send msg failed"
db.log.create(LOG_COLLECTION, { ...baseLog, error }); db.log.create(LOG_COLLECTION, { ...baseLog, error })
return netTool.serverError(error, sendRes); return netTool.serverError(error, sendRes)
} }
}; }

View File

@ -1,6 +1,6 @@
import service from "../../services"; import service from "../../services"
import netTool from "../../services/netTool"; import netTool from "../../services/netTool"
import { trimPathPrefix } from "../../utils/pathTools"; import { trimPathPrefix } from "../../utils/pathTools"
/** /**
* *
@ -8,34 +8,34 @@ import { trimPathPrefix } from "../../utils/pathTools";
* @returns * @returns
*/ */
const manageLogin = async (req: Request) => { const manageLogin = async (req: Request) => {
const url = new URL(req.url); const url = new URL(req.url)
const code = url.searchParams.get("code"); const code = url.searchParams.get("code")
const appName = url.searchParams.get("app_name") || undefined; const appName = url.searchParams.get("app_name") || undefined
if (!code) { if (!code) {
return netTool.badRequest("code not found"); return netTool.badRequest("code not found")
} }
const { const {
code: resCode, code: resCode,
data, data,
msg, 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) { if (resCode !== 0) {
return Response.json({ return Response.json({
code: resCode, code: resCode,
message: msg, message: msg,
data: null, data: null,
}); })
} }
return Response.json({ return Response.json({
code: 0, code: 0,
message: "success", message: "success",
data, data,
}); })
}; }
/** /**
* *
@ -43,35 +43,35 @@ const manageLogin = async (req: Request) => {
* @returns * @returns
*/ */
const manageBatchUser = async (req: Request) => { const manageBatchUser = async (req: Request) => {
const body = (await req.json()) as any; const body = (await req.json()) as any
console.log("🚀 ~ manageBatchUser ~ body:", body); console.log("🚀 ~ manageBatchUser ~ body:", body)
const { user_ids, user_id_type, app_name } = body; const { user_ids, user_id_type, app_name } = body
if (!user_ids) { if (!user_ids) {
return netTool.badRequest("user_ids not found"); return netTool.badRequest("user_ids not found")
} }
if (!user_id_type) { 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)( const { code, data, msg } = await service.lark.user.batchGet(app_name)(
user_ids, user_ids,
user_id_type user_id_type
); )
console.log("🚀 ~ manageBatchUser:", code, data, msg); console.log("🚀 ~ manageBatchUser:", code, data, msg)
if (code !== 0) { if (code !== 0) {
return Response.json({ return Response.json({
code, code,
message: msg, message: msg,
data: null, data: null,
}); })
} }
return Response.json({ return Response.json({
code, code,
message: "success", message: "success",
data: data.items, data: data.items,
}); })
}; }
/** /**
* *
@ -79,15 +79,15 @@ const manageBatchUser = async (req: Request) => {
* @returns * @returns
*/ */
export const manageMicroAppReq = async (req: Request) => { export const manageMicroAppReq = async (req: Request) => {
const url = new URL(req.url); const url = new URL(req.url)
const withoutPrefix = trimPathPrefix(url.pathname, "/micro_app"); const withoutPrefix = trimPathPrefix(url.pathname, "/micro_app")
// 处理登录请求 // 处理登录请求
if (withoutPrefix === "/login") { if (withoutPrefix === "/login") {
return manageLogin(req); return manageLogin(req)
} }
// 处理批量获取用户信息请求 // 处理批量获取用户信息请求
if (withoutPrefix === "/batch_user") { if (withoutPrefix === "/batch_user") {
return manageBatchUser(req); return manageBatchUser(req)
} }
return netTool.ok(); return netTool.ok()
}; }

View File

@ -1,59 +1,59 @@
import db from "../../db"; import db from "../../db"
import service from "../../services"; import service from "../../services"
import netTool from "../../services/netTool"; import netTool from "../../services/netTool"
import { SheetProxy } from "../../types/sheetProxy"; import { SheetProxy } from "../../types/sheetProxy"
const validateSheetReq = async (body: SheetProxy.Body) => { const validateSheetReq = async (body: SheetProxy.Body) => {
if (!body.api_key) { if (!body.api_key) {
return netTool.badRequest("api_key is required"); return netTool.badRequest("api_key is required")
} }
if (!body.sheet_token) { if (!body.sheet_token) {
return netTool.badRequest("sheet_token is required"); return netTool.badRequest("sheet_token is required")
} }
if (!body.range) { if (!body.range) {
return netTool.badRequest("range is required"); return netTool.badRequest("range is required")
} }
if (!body.values) { if (!body.values) {
return netTool.badRequest("values is required"); return netTool.badRequest("values is required")
} }
if (!SheetProxy.isType(body.type)) { 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) => { 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); const validateRes = await validateSheetReq(body)
if (validateRes) return validateRes; if (validateRes) return validateRes
// 校验api_key // 校验api_key
const apiKeyInfo = await db.apiKey.getOne(body.api_key); const apiKeyInfo = await db.apiKey.getOne(body.api_key)
if (!apiKeyInfo) { if (!apiKeyInfo) {
return netTool.notFound("api key not found"); return netTool.notFound("api key not found")
} }
// 获取 app name // 获取 app name
const appName = apiKeyInfo.expand?.app?.name; const appName = apiKeyInfo.expand?.app?.name
if (!appName) { if (!appName) {
return netTool.notFound("app name not found"); return netTool.notFound("app name not found")
} }
if (body.type === "insert") { if (body.type === "insert") {
// 插入行 // 插入行
const insertRes = await service.lark.sheet.insterRows(appName)( const insertRes = await service.lark.sheet.insertRows(appName)(
body.sheet_token, body.sheet_token,
body.range, body.range,
body.values body.values
); )
if (insertRes?.code !== 0) { 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()
}; }

View File

@ -1,20 +1,20 @@
import db from "../db"; import db from "../db"
import netTool from "../services/netTool"; import netTool from "../services/netTool"
const URL = const URL =
"https://open.f.mioffice.cn/open-apis/auth/v3/tenant_access_token/internal"; "https://open.f.mioffice.cn/open-apis/auth/v3/tenant_access_token/internal"
export const resetAccessToken = async () => { export const resetAccessToken = async () => {
try { try {
const appList = await db.appInfo.getFullList(); const appList = await db.appInfo.getFullList()
for (const app of appList) { for (const app of appList) {
const { tenant_access_token } = await netTool.post(URL, { const { tenant_access_token } = await netTool.post(URL, {
app_id: app.app_id, app_id: app.app_id,
app_secret: app.app_secret, app_secret: app.app_secret,
}); })
await db.tenantAccessToken.update(app.id, app.name, tenant_access_token); await db.tenantAccessToken.update(app.id, app.name, tenant_access_token)
} }
} catch (error) { } catch (error) {
console.error("🚀 ~ resetAccessToken ~ error", error); console.error("🚀 ~ resetAccessToken ~ error", error)
} }
}; }

View File

@ -1,9 +1,10 @@
import { resetAccessToken } from "./accessToken"; import schedule from "node-schedule"
import schedule from "node-schedule";
import { resetAccessToken } from "./accessToken"
export const initSchedule = async () => {
// 定时任务每15分钟刷新一次token export const initSchedule = async () => {
schedule.scheduleJob("*/15 * * * *", resetAccessToken); // 定时任务每15分钟刷新一次token
// 立即执行一次 schedule.scheduleJob("*/15 * * * *", resetAccessToken)
resetAccessToken(); // 立即执行一次
}; resetAccessToken()
}

View File

@ -1,18 +1,18 @@
import { LarkEvent } from "../../types"; import { LarkEvent } from "../../types"
import netTool from "../netTool"; import netTool from "../netTool"
/** /**
* CI * CI
*/ */
const ciMonitor = async (chat_id: string) => { 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 { try {
const res = await netTool.get(URL); const res = await netTool.get(URL)
return (res as string) || ""; return (res as string) || ""
} catch { } catch {
return ""; return ""
} }
}; }
/** /**
* *
@ -20,18 +20,18 @@ const ciMonitor = async (chat_id: string) => {
* @returns * @returns
*/ */
const reportCollector = async (body: LarkEvent.Data) => { const reportCollector = async (body: LarkEvent.Data) => {
const URL = "https://report.imoaix.cn/report"; const URL = "https://report.imoaix.cn/report"
try { try {
const res = await netTool.post(URL, body); const res = await netTool.post(URL, body)
return (res as string) || ""; return (res as string) || ""
} catch { } catch {
return ""; return ""
} }
}; }
const attach = { const attach = {
ciMonitor, ciMonitor,
reportCollector, reportCollector,
}; }
export default attach; export default attach

View File

@ -1,9 +1,9 @@
import attach from "./attach"; import attach from "./attach"
import lark from "./lark"; import lark from "./lark"
const service = { const service = {
attach, attach,
lark, lark,
}; }
export default service; export default service

View File

@ -1,42 +1,42 @@
import { LarkServer } from "../../types"; import { LarkServer } from "../../types"
import larkNetTool from "./larkNetTool"; import larkNetTool from "./larkNetTool"
const batchGetMeta = const batchGetMeta =
(appName?: string) => (appName?: string) =>
async (docTokens: string[], doc_type = "doc", user_id_type = "user_id") => { async (docTokens: string[], doc_type = "doc", user_id_type = "user_id") => {
const URL = 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需要分批请求 // 如果docTokens长度超出150需要分批请求
const docTokensLen = docTokens.length; const docTokensLen = docTokens.length
const maxLen = 150; const maxLen = 150
const requestMap = Array.from( const requestMap = Array.from(
{ length: Math.ceil(docTokensLen / maxLen) }, { length: Math.ceil(docTokensLen / maxLen) },
(_, index) => { (_, index) => {
const start = index * maxLen; const start = index * maxLen
const docTokensSlice = docTokens.slice(start, start + maxLen); const docTokensSlice = docTokens.slice(start, start + maxLen)
const data = { const data = {
request_docs: docTokensSlice.map((id) => ({ request_docs: docTokensSlice.map((id) => ({
doc_token: id, doc_token: id,
doc_type, doc_type,
})), })),
}; }
return larkNetTool.post(appName)<LarkServer.BatchDocMetaRes>( return larkNetTool.post(appName)<LarkServer.BatchDocMetaRes>(
URL, URL,
data, data,
{ {
user_id_type, user_id_type,
} }
); )
} }
); )
const responses = await Promise.all(requestMap); const responses = await Promise.all(requestMap)
const metas = responses.flatMap((res) => { const metas = responses.flatMap((res) => {
return res.data?.metas || []; return res.data?.metas || []
}); })
const failed_list = responses.flatMap((res) => { const failed_list = responses.flatMap((res) => {
return res.data?.failed_list || []; return res.data?.failed_list || []
}); })
return { return {
code: 0, code: 0,
@ -45,11 +45,11 @@ const batchGetMeta =
failed_list, failed_list,
}, },
msg: "success", msg: "success",
}; }
}; }
const drive = { const drive = {
batchGetMeta, batchGetMeta,
}; }
export default drive; export default drive

View File

@ -1,13 +1,13 @@
import message from "./message"; import drive from "./drive"
import user from "./user"; import message from "./message"
import drive from "./drive"; import sheet from "./sheet"
import sheet from "./sheet"; import user from "./user"
const lark = { const lark = {
message, message,
user, user,
drive, drive,
sheet, sheet,
}; }
export default lark; export default lark

View File

@ -1,6 +1,6 @@
import db from "../../db"; import db from "../../db"
import { DB, LarkServer } from "../../types"; import { LarkServer } from "../../types"
import netTool from "../netTool"; import netTool from "../netTool"
/** /**
* Promise * Promise
@ -21,17 +21,17 @@ const larkNetTool = async <T = LarkServer.BaseRes>({
additionalHeaders, additionalHeaders,
appName = "egg", appName = "egg",
}: { }: {
url: string; url: string
method: string; method: string
queryParams?: any; queryParams?: any
payload?: any; payload?: any
additionalHeaders?: any; additionalHeaders?: any
appName?: string; appName?: string
}): Promise<T> => { }): Promise<T> => {
const headersWithAuth = { const headersWithAuth = {
Authorization: `Bearer ${await db.tenantAccessToken.get(appName)}`, Authorization: `Bearer ${await db.tenantAccessToken.get(appName)}`,
...additionalHeaders, ...additionalHeaders,
}; }
return netTool<T>({ return netTool<T>({
url, url,
method, method,
@ -39,14 +39,14 @@ const larkNetTool = async <T = LarkServer.BaseRes>({
payload, payload,
additionalHeaders: headersWithAuth, additionalHeaders: headersWithAuth,
}).catch((error) => { }).catch((error) => {
console.error("larkNetTool catch error: ", error); console.error("larkNetTool catch error: ", error)
return { return {
code: 1, code: 1,
data: null, data: null,
msg: error.message || "网络请求异常", msg: error.message || "网络请求异常",
} as T; } as T
}); })
}; }
/** /**
* GET请求并返回一个解析为响应数据的Promise * GET请求并返回一个解析为响应数据的Promise
@ -67,7 +67,7 @@ larkNetTool.get =
queryParams, queryParams,
additionalHeaders, additionalHeaders,
appName, appName,
}); })
/** /**
* POST请求并返回一个解析为响应数据的Promise * POST请求并返回一个解析为响应数据的Promise
@ -90,7 +90,7 @@ larkNetTool.post =
queryParams, queryParams,
additionalHeaders, additionalHeaders,
appName, appName,
}); })
/** /**
* DELETE请求并返回一个解析为响应数据的Promise * DELETE请求并返回一个解析为响应数据的Promise
@ -105,7 +105,7 @@ larkNetTool.del =
payload: any, payload: any,
additionalHeaders?: any additionalHeaders?: any
): Promise<T> => ): Promise<T> =>
larkNetTool({ url, method: "delete", payload, additionalHeaders, appName }); larkNetTool({ url, method: "delete", payload, additionalHeaders, appName })
/** /**
* PATCH请求并返回一个解析为响应数据的Promise * PATCH请求并返回一个解析为响应数据的Promise
@ -120,6 +120,6 @@ larkNetTool.patch =
payload: any, payload: any,
additionalHeaders?: any additionalHeaders?: any
): Promise<T> => ): Promise<T> =>
larkNetTool({ url, method: "patch", payload, additionalHeaders, appName }); larkNetTool({ url, method: "patch", payload, additionalHeaders, appName })
export default larkNetTool; export default larkNetTool

View File

@ -1,5 +1,5 @@
import { LarkServer } from "../../types/larkServer"; import { LarkServer } from "../../types/larkServer"
import larkNetTool from "./larkNetTool"; import larkNetTool from "./larkNetTool"
/** /**
* *
@ -16,16 +16,16 @@ const send =
msg_type: LarkServer.MsgType, msg_type: LarkServer.MsgType,
content: string 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"')) { if (msg_type === "text" && !content.includes('"text"')) {
content = JSON.stringify({ text: content }); content = JSON.stringify({ text: content })
} }
return larkNetTool.post(appName)<LarkServer.BaseRes>(URL, { return larkNetTool.post(appName)<LarkServer.BaseRes>(URL, {
receive_id, receive_id,
msg_type, msg_type,
content, content,
}); })
}; }
/** /**
* *
@ -34,13 +34,13 @@ const send =
*/ */
const update = const update =
(appName?: string) => async (message_id: string, content: string) => { (appName?: string) => async (message_id: string, content: string) => {
const URL = `https://open.f.mioffice.cn/open-apis/im/v1/messages/${message_id}`; const URL = `https://open.f.mioffice.cn/open-apis/im/v1/messages/${message_id}`
return larkNetTool.patch(appName)<LarkServer.BaseRes>(URL, { content }); return larkNetTool.patch(appName)<LarkServer.BaseRes>(URL, { content })
}; }
const message = { const message = {
send, send,
update, update,
}; }
export default message; export default message

View File

@ -1,25 +1,25 @@
import { LarkServer } from "../../types/larkServer"; import { LarkServer } from "../../types/larkServer"
import larkNetTool from "./larkNetTool"; import larkNetTool from "./larkNetTool"
/** /**
* *
* @param appName - * @param appName -
* @returns * @returns
*/ */
const insterRows = const insertRows =
(appName?: string) => (appName?: string) =>
async (sheetToken: string, range: string, values: 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)<LarkServer.BaseRes>(URL, { return larkNetTool.post(appName)<LarkServer.BaseRes>(URL, {
valueRange: { valueRange: {
range, range,
values, values,
}, },
}); })
}; }
const sheet = { const sheet = {
insterRows, insertRows,
}; }
export default sheet; export default sheet

View File

@ -1,5 +1,5 @@
import { LarkServer } from "../../types/larkServer"; import { LarkServer } from "../../types/larkServer"
import larkNetTool from "./larkNetTool"; import larkNetTool from "./larkNetTool"
/** /**
* *
@ -7,9 +7,9 @@ import larkNetTool from "./larkNetTool";
* @returns * @returns
*/ */
const code2Login = (appName?: string) => async (code: string) => { const code2Login = (appName?: string) => async (code: string) => {
const URL = `https://open.f.mioffice.cn/open-apis/mina/v2/tokenLoginValidate`; const URL = `https://open.f.mioffice.cn/open-apis/mina/v2/tokenLoginValidate`
return larkNetTool.post(appName)<LarkServer.UserSessionRes>(URL, { code }); return larkNetTool.post(appName)<LarkServer.UserSessionRes>(URL, { code })
}; }
/** /**
* *
@ -19,11 +19,11 @@ const code2Login = (appName?: string) => async (code: string) => {
const get = const get =
(appName?: string) => (appName?: string) =>
async (user_id: string, user_id_type: "open_id" | "user_id") => { 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)<LarkServer.UserInfoRes>(URL, { return larkNetTool.get(appName)<LarkServer.UserInfoRes>(URL, {
user_id_type, user_id_type,
}); })
}; }
/** /**
* *
@ -33,32 +33,32 @@ const get =
const batchGet = const batchGet =
(appName?: string) => (appName?: string) =>
async (user_ids: string[], user_id_type: "open_id" | "user_id") => { 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需要分批请求, // 如果user_id长度超出50需要分批请求,
const userCount = user_ids.length; const userCount = user_ids.length
const maxLen = 50; const maxLen = 50
const requestMap = Array.from( const requestMap = Array.from(
{ length: Math.ceil(userCount / maxLen) }, { length: Math.ceil(userCount / maxLen) },
(_, index) => { (_, index) => {
const start = index * maxLen; const start = index * maxLen
const user_idsSlice = user_ids.slice(start, start + maxLen); const user_idsSlice = user_ids.slice(start, start + maxLen)
const getParams = `${user_idsSlice const getParams = `${user_idsSlice
.map((id) => `user_ids=${id}`) .map((id) => `user_ids=${id}`)
.join("&")}&user_id_type=${user_id_type}`; .join("&")}&user_id_type=${user_id_type}`
return larkNetTool.get(appName)<LarkServer.BatchUserInfoRes>( return larkNetTool.get(appName)<LarkServer.BatchUserInfoRes>(
URL, URL,
getParams getParams
); )
} }
); )
const responses = await Promise.all(requestMap); const responses = await Promise.all(requestMap)
const items = responses.flatMap((res) => { const items = responses.flatMap((res) => {
return res.data?.items || []; return res.data?.items || []
}); })
return { return {
code: 0, code: 0,
@ -66,13 +66,13 @@ const batchGet =
items, items,
}, },
msg: "success", msg: "success",
}; }
}; }
const user = { const user = {
code2Login, code2Login,
batchGet, batchGet,
get, get,
}; }
export default user; export default user

View File

@ -1,9 +1,9 @@
interface NetRequestParams { interface NetRequestParams {
url: string; url: string
method: string; method: string
queryParams?: any; queryParams?: any
payload?: any; payload?: any
additionalHeaders?: any; additionalHeaders?: any
} }
/** /**
@ -32,10 +32,10 @@ const logResponse = (
responseHeaders: response.headers, responseHeaders: response.headers,
requestBody, requestBody,
responseBody, responseBody,
}; }
console.log("🚀 ~ responseLog:", JSON.stringify(responseLog, null, 2)); console.log("🚀 ~ responseLog:", JSON.stringify(responseLog, null, 2))
return responseLog; return responseLog
}; }
/** /**
* Promise * Promise
@ -55,13 +55,13 @@ const netTool = async <T = any>({
additionalHeaders, additionalHeaders,
}: NetRequestParams): Promise<T> => { }: NetRequestParams): Promise<T> => {
// 拼接完整的URL // 拼接完整的URL
let fullUrl = url; let fullUrl = url
if (queryParams) { if (queryParams) {
if (typeof queryParams === "string") { if (typeof queryParams === "string") {
fullUrl = `${url}?${queryParams}`; fullUrl = `${url}?${queryParams}`
} else { } else {
const queryString = new URLSearchParams(queryParams).toString(); const queryString = new URLSearchParams(queryParams).toString()
if (queryString) fullUrl = `${url}?${queryString}`; if (queryString) fullUrl = `${url}?${queryString}`
} }
} }
@ -69,40 +69,42 @@ const netTool = async <T = any>({
const headers = { const headers = {
"Content-Type": "application/json", "Content-Type": "application/json",
...additionalHeaders, ...additionalHeaders,
}; }
// 发送请求 // 发送请求
const res = await fetch(fullUrl, { const res = await fetch(fullUrl, {
method, method,
body: JSON.stringify(payload), body: JSON.stringify(payload),
headers, headers,
}); })
// 获取响应数据 // 获取响应数据
let resData: any = null; let resData: any = null
let resText: string = ""; let resText: string = ""
try { try {
resText = await res.text(); resText = await res.text()
resData = JSON.parse(resText); resData = JSON.parse(resText)
} catch {} } catch {
/* empty */
}
// 记录响应 // 记录响应
logResponse(res, method, headers, payload, resData || resText); logResponse(res, method, headers, payload, resData || resText)
if (!res.ok) { if (!res.ok) {
if (resData?.msg) { if (resData?.msg) {
throw new Error(resData.msg); throw new Error(resData.msg)
} }
if (resText) { if (resText) {
throw new Error(resText); throw new Error(resText)
} }
throw new Error("网络响应异常"); throw new Error("网络响应异常")
} }
// http 错误码正常,但解析异常 // http 错误码正常,但解析异常
if (!resData) { if (!resData) {
throw new Error("解析响应数据异常"); throw new Error("解析响应数据异常")
} }
return resData as T; return resData as T
}; }
/** /**
* GET请求并返回一个解析为响应数据的Promise * GET请求并返回一个解析为响应数据的Promise
@ -116,8 +118,7 @@ netTool.get = <T = any>(
url: string, url: string,
queryParams?: any, queryParams?: any,
additionalHeaders?: any additionalHeaders?: any
): Promise<T> => ): Promise<T> => netTool({ url, method: "get", queryParams, additionalHeaders })
netTool({ url, method: "get", queryParams, additionalHeaders });
/** /**
* POST请求并返回一个解析为响应数据的Promise * POST请求并返回一个解析为响应数据的Promise
@ -134,7 +135,7 @@ netTool.post = <T = any>(
queryParams?: any, queryParams?: any,
additionalHeaders?: any additionalHeaders?: any
): Promise<T> => ): Promise<T> =>
netTool({ url, method: "post", payload, queryParams, additionalHeaders }); netTool({ url, method: "post", payload, queryParams, additionalHeaders })
/** /**
* PUT请求并返回一个解析为响应数据的Promise * PUT请求并返回一个解析为响应数据的Promise
@ -151,7 +152,7 @@ netTool.put = <T = any>(
queryParams?: any, queryParams?: any,
additionalHeaders?: any additionalHeaders?: any
): Promise<T> => ): Promise<T> =>
netTool({ url, method: "put", payload, queryParams, additionalHeaders }); netTool({ url, method: "put", payload, queryParams, additionalHeaders })
/** /**
* DELETE请求并返回一个解析为响应数据的Promise * DELETE请求并返回一个解析为响应数据的Promise
@ -168,7 +169,7 @@ netTool.del = <T = any>(
queryParams?: any, queryParams?: any,
additionalHeaders?: any additionalHeaders?: any
): Promise<T> => ): Promise<T> =>
netTool({ url, method: "delete", payload, queryParams, additionalHeaders }); netTool({ url, method: "delete", payload, queryParams, additionalHeaders })
/** /**
* PATCH请求并返回一个解析为响应数据的Promise * PATCH请求并返回一个解析为响应数据的Promise
@ -185,7 +186,7 @@ netTool.patch = <T = any>(
queryParams?: any, queryParams?: any,
additionalHeaders?: any additionalHeaders?: any
): Promise<T> => ): Promise<T> =>
netTool({ url, method: "patch", payload, queryParams, additionalHeaders }); netTool({ url, method: "patch", payload, queryParams, additionalHeaders })
/** /**
* 400 Bad Request的响应对象 * 400 Bad Request的响应对象
@ -195,7 +196,7 @@ netTool.patch = <T = any>(
* @returns 400 Bad Request的响应对象 * @returns 400 Bad Request的响应对象
*/ */
netTool.badRequest = (msg: string, requestId?: string) => 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的响应对象 * 404 Not Found的响应对象
@ -205,7 +206,7 @@ netTool.badRequest = (msg: string, requestId?: string) =>
* @returns 404 Not Found的响应对象 * @returns 404 Not Found的响应对象
*/ */
netTool.notFound = (msg: string, requestId?: string) => 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的响应对象 * 500 Internal Server Error的响应对象
@ -216,7 +217,7 @@ netTool.notFound = (msg: string, requestId?: string) =>
* @returns 500 Internal Server Error的响应对象 * @returns 500 Internal Server Error的响应对象
*/ */
netTool.serverError = (msg: string, data?: any, requestId?: string) => 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的响应对象 * 200 OK的响应对象
@ -226,6 +227,6 @@ netTool.serverError = (msg: string, data?: any, requestId?: string) =>
* @returns 200 OK的响应对象 * @returns 200 OK的响应对象
*/ */
netTool.ok = (data?: any, requestId?: string) => 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

View File

@ -1,5 +1,5 @@
const localUrl = "http://localhost:3000/micro_app/batch_user"; const localUrl = "http://localhost:3000/micro_app/batch_user"
const prodUrl = "https://egg.imoaix.cn/micro_app/batch_user"; // const prodUrl = "https://egg.imoaix.cn/micro_app/batch_user";
const res = await fetch(localUrl, { const res = await fetch(localUrl, {
method: "POST", method: "POST",
@ -10,6 +10,6 @@ const res = await fetch(localUrl, {
user_ids: ["zhaoyingbo"], user_ids: ["zhaoyingbo"],
user_id_type: "user_id", user_id_type: "user_id",
}), }),
}); })
console.log(JSON.stringify(await res.json())); console.log(JSON.stringify(await res.json()))

View File

@ -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)

View File

@ -1,5 +1,5 @@
// const URL = "https://egg.imoaix.cn/sheet"; // const URL = "https://egg.imoaix.cn/sheet";
const URL = "http://localhost:3000/sheet"; const URL = "http://localhost:3000/sheet"
const res = await fetch(URL, { const res = await fetch(URL, {
method: "POST", method: "POST",
@ -10,6 +10,6 @@ const res = await fetch(URL, {
range: "a2YJxa", range: "a2YJxa",
values: [["hello", "world"]], values: [["hello", "world"]],
}), }),
}); })
console.log(JSON.stringify(await res.json(), null, 2)); console.log(JSON.stringify(await res.json(), null, 2))

View File

@ -1,5 +1,5 @@
// const URL = "https://egg.imoaix.cn/message"; // const URL = "https://egg.imoaix.cn/message";
const URL = "http://localhost:3000/message"; const URL = "http://localhost:3000/message"
const res = await fetch(URL, { const res = await fetch(URL, {
method: "POST", method: "POST",
@ -10,6 +10,6 @@ const res = await fetch(URL, {
msg_type: "text", msg_type: "text",
content: "hello, world!", content: "hello, world!",
}), }),
}); })
console.log(JSON.stringify(await res.text())); console.log(JSON.stringify(await res.text()))

View File

@ -1,21 +1,21 @@
[ [
{ {
"_id": "a5cb7b58-0c22-41fb-bc00-af460ef90c1b", "_id": "a5cb7b58-0c22-41fb-bc00-af460ef90c1b",
"colId": "history", "colId": "history",
"containerId": "", "containerId": "",
"name": "https://self.imoaix.cn/bot", "name": "https://self.imoaix.cn/bot",
"url": "https://self.imoaix.cn/bot", "url": "https://self.imoaix.cn/bot",
"method": "POST", "method": "POST",
"sortNum": 0, "sortNum": 0,
"created": "2023-08-15T07:47:05.439Z", "created": "2023-08-15T07:47:05.439Z",
"modified": "2023-08-15T07:47:34.510Z", "modified": "2023-08-15T07:47:34.510Z",
"headers": [], "headers": [],
"params": [], "params": [],
"body": { "body": {
"type": "json", "type": "json",
"raw": "{\r\n \"challenge\": \"c14206ad-5b5f-4a77-82b6-b39b7365392b\",\r\n \"token\": \"tV9djUKSjzVnekV7xTg2Od06NFTcsBnj\",\r\n \"type\": \"url_verification\"\r\n}", "raw": "{\r\n \"challenge\": \"c14206ad-5b5f-4a77-82b6-b39b7365392b\",\r\n \"token\": \"tV9djUKSjzVnekV7xTg2Od06NFTcsBnj\",\r\n \"type\": \"url_verification\"\r\n}",
"form": [] "form": []
}, },
"tests": [] "tests": []
} }
] ]

View File

@ -1,22 +1,22 @@
{ {
"compilerOptions": { "compilerOptions": {
"lib": ["ESNext"], "lib": ["ESNext"],
"module": "esnext", "module": "esnext",
"target": "esnext", "target": "esnext",
"moduleResolution": "bundler", "moduleResolution": "bundler",
"moduleDetection": "force", "moduleDetection": "force",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
"noEmit": true, "noEmit": true,
"composite": true, "composite": true,
"strict": true, "strict": true,
"downlevelIteration": true, "downlevelIteration": true,
"skipLibCheck": true, "skipLibCheck": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"allowJs": true, "allowJs": true,
"types": [ "types": [
"bun-types" // add Bun global "bun-types" // add Bun global
] ]
} }
} }

View File

@ -1,44 +1,44 @@
import { RecordModel } from "pocketbase"; import { RecordModel } from "pocketbase"
export namespace DB { export namespace DB {
export interface AppInfo extends RecordModel { export interface AppInfo extends RecordModel {
name: string; name: string
app_id: string; app_id: string
app_secret: string; app_secret: string
app_name: string; app_name: string
avatar_url: string; avatar_url: string
description: string; description: string
tenant_access_token: string; tenant_access_token: string
} }
export interface ApiKey extends RecordModel { export interface ApiKey extends RecordModel {
name: string; name: string
user: string; user: string
app: string; app: string
apply_reason: string; apply_reason: string
} }
export interface MessageGroup extends RecordModel { export interface MessageGroup extends RecordModel {
desc: string; desc: string
id: string; id: string
name: string; name: string
email?: string[]; email?: string[]
chat_id?: string[]; chat_id?: string[]
open_id?: string[]; open_id?: string[]
union_id?: string[]; union_id?: string[]
user_id?: string[]; user_id?: string[]
} }
export interface MessageLog extends RecordModel { export interface MessageLog extends RecordModel {
api_key: string; api_key: string
group_id?: string; group_id?: string
receive_id?: string; receive_id?: string
receive_id_type?: string; receive_id_type?: string
msg_type: string; msg_type: string
content: string; content: string
final_content?: string; final_content?: string
send_result?: any; send_result?: any
error?: string; error?: string
} }
export type MessageLogCreate = Pick< export type MessageLogCreate = Pick<
@ -52,8 +52,8 @@ export namespace DB {
| "final_content" | "final_content"
| "send_result" | "send_result"
| "error" | "error"
>; >
export type Log = MessageLogCreate; export type Log = MessageLogCreate
export type LogCollection = "message_log"; export type LogCollection = "message_log"
} }

View File

@ -1,7 +1,7 @@
import type { DB } from "./db"; import type { DB } from "./db"
import type { LarkAction } from "./larkAction"; import type { LarkAction } from "./larkAction"
import type { LarkEvent } from "./larkEvent"; import type { LarkEvent } from "./larkEvent"
import type { LarkServer } from "./larkServer"; import type { LarkServer } from "./larkServer"
import type { MsgProxy } from "./msgProxy"; import type { MsgProxy } from "./msgProxy"
export { DB, LarkAction, LarkEvent, LarkServer, MsgProxy }; export { DB, LarkAction, LarkEvent, LarkServer, MsgProxy }

View File

@ -3,32 +3,32 @@ export namespace LarkAction {
/** /**
* open_id * open_id
*/ */
open_id: string; open_id: string
/** /**
* *
* @example zhaoyingbo * @example zhaoyingbo
*/ */
user_id: string; user_id: string
/** /**
* ID * ID
* @example om_038fc0eceed6224a1abc1cdaa4266405 * @example om_038fc0eceed6224a1abc1cdaa4266405
*/ */
open_message_id: string; open_message_id: string
/** /**
* ID * ID
* @example oc_433b1cb7a9dbb7ebe70a4e1a59cb8bb1 * @example oc_433b1cb7a9dbb7ebe70a4e1a59cb8bb1
*/ */
open_chat_id: string; open_chat_id: string
/** /**
* ID * ID
* @example 2ee61fe50f4f1657 * @example 2ee61fe50f4f1657
*/ */
tenant_key: string; tenant_key: string
/** /**
* token * token
* @example tV9djUKSjzVnekV7xTg2Od06NFTcsBnj * @example tV9djUKSjzVnekV7xTg2Od06NFTcsBnj
*/ */
token: string; token: string
/** /**
* *
*/ */
@ -36,21 +36,21 @@ export namespace LarkAction {
/** /**
* *
*/ */
value: any; value: any
/** /**
* *
* @example picker_datetime * @example picker_datetime
*/ */
tag: string; tag: string
/** /**
* *
* @example 2023-09-03 10:35 +0800 * @example 2023-09-03 10:35 +0800
*/ */
option: string; option: string
/** /**
* *
*/ */
timezone: string; timezone: string
}; }
} }
} }

View File

@ -7,32 +7,32 @@ export namespace LarkEvent {
* ID * ID
* @example 0f8ab23b60993cf8dd15c8cde4d7b0f5 * @example 0f8ab23b60993cf8dd15c8cde4d7b0f5
*/ */
event_id: string; event_id: string
/** /**
* token * token
* @example tV9djUKSjzVnekV7xTg2Od06NFTcsBnj * @example tV9djUKSjzVnekV7xTg2Od06NFTcsBnj
*/ */
token: string; token: string
/** /**
* *
* @example 1693565712117 * @example 1693565712117
*/ */
create_time: string; create_time: string
/** /**
* *
* @example im.message.receive_v1 * @example im.message.receive_v1
*/ */
event_type: string; event_type: string
/** /**
* tenant_key * tenant_key
* @example 2ee61fe50f4f1657 * @example 2ee61fe50f4f1657
*/ */
tenant_key: string; tenant_key: string
/** /**
* app_id * app_id
* @example cli_a1eff35b43b89063 * @example cli_a1eff35b43b89063
*/ */
app_id: string; app_id: string
} }
/** /**
* AT的人的信息 * AT的人的信息
@ -41,22 +41,22 @@ export namespace LarkEvent {
/** /**
* ID信息 * ID信息
*/ */
id: UserIdInfo; id: UserIdInfo
/** /**
* *
* @example "@_user_1" * @example "@_user_1"
*/ */
key: string; key: string
/** /**
* *
* @example * @example
*/ */
name: string; name: string
/** /**
* ID * ID
* @example 2ee61fe50f4f1657 * @example 2ee61fe50f4f1657
*/ */
tenant_key: string; tenant_key: string
} }
/** /**
* *
@ -66,36 +66,36 @@ export namespace LarkEvent {
* ID * ID
* @example oc_433b1cb7a9dbb7ebe70a4e1a59cb8bb1 * @example oc_433b1cb7a9dbb7ebe70a4e1a59cb8bb1
*/ */
chat_id: string; chat_id: string
/** /**
* *
* @example group | p2p * @example group | p2p
*/ */
chat_type: "group" | "p2p"; chat_type: "group" | "p2p"
/** /**
* JSON字符串文本内容 * JSON字符串文本内容
* @example "{\"text\":\"@_user_1 测试\"}" * @example "{\"text\":\"@_user_1 测试\"}"
*/ */
content: string; content: string
/** /**
* *
* @example 1693565711996 * @example 1693565711996
*/ */
create_time: string; create_time: string
/** /**
* *
*/ */
mentions?: Mention[]; mentions?: Mention[]
/** /**
* ID * ID
* @example om_038fc0eceed6224a1abc1cdaa4266405 * @example om_038fc0eceed6224a1abc1cdaa4266405
*/ */
message_id: string; message_id: string
/** /**
* *
* @example textpostimagefileaudiomediastickerinteractiveshare_chatshare_user * @example textpostimagefileaudiomediastickerinteractiveshare_chatshare_user
*/ */
message_type: string; message_type: string
} }
/** /**
@ -106,17 +106,17 @@ export namespace LarkEvent {
* *
* @example ou_032f507d08f9a7f28b042fcd086daef5 * @example ou_032f507d08f9a7f28b042fcd086daef5
*/ */
open_id: string; open_id: string
/** /**
* *
* @example on_7111660fddd8302ce47bf1999147c011 * @example on_7111660fddd8302ce47bf1999147c011
*/ */
union_id: string; union_id: string
/** /**
* *
* @example zhaoyingbo * @example zhaoyingbo
*/ */
user_id: string; user_id: string
} }
/** /**
* *
@ -125,24 +125,24 @@ export namespace LarkEvent {
/** /**
* id * id
*/ */
sender_id: UserIdInfo; sender_id: UserIdInfo
/** /**
* *
* @example user * @example user
*/ */
sender_type: string; sender_type: string
/** /**
* ID * ID
* @example 2ee61fe50f4f1657 * @example 2ee61fe50f4f1657
*/ */
tenant_key: string; tenant_key: string
} }
/** /**
* *
*/ */
export interface Event { export interface Event {
message: Message; message: Message
sender: Sender; sender: Sender
} }
export interface Data { export interface Data {
@ -150,14 +150,14 @@ export namespace LarkEvent {
* *
* @example 2.0 * @example 2.0
*/ */
schema: string; schema: string
/** /**
* *
*/ */
header: Header; header: Header
/** /**
* *
*/ */
event: Event; event: Event
} }
} }

View File

@ -3,88 +3,88 @@ export namespace LarkServer {
/** /**
* 访 * 访
*/ */
access_token: string; access_token: string
/** /**
* ID * ID
*/ */
employee_id: string; employee_id: string
/** /**
* *
*/ */
expires_in: number; expires_in: number
/** /**
* ID * 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 * ID
*/ */
union_id: string; union_id: string
} }
export interface SuccessDocMeta { export interface SuccessDocMeta {
doc_token: string; doc_token: string
doc_type: string; doc_type: string
title: string; title: string
owner_id: string; owner_id: string
create_time: string; create_time: string
latest_modify_user: string; latest_modify_user: string
latest_modify_time: string; latest_modify_time: string
url: string; url: string
sec_label_name: string; sec_label_name: string
} }
export interface FailedDocMeta { export interface FailedDocMeta {
token: string; token: string
code: number; code: number
} }
export interface BaseRes { export interface BaseRes {
code: number; code: number
data: any; data: any
msg: string; msg: string
} }
export interface UserSessionRes extends BaseRes { export interface UserSessionRes extends BaseRes {
data: UserSession; data: UserSession
} }
export interface UserInfoRes extends BaseRes { export interface UserInfoRes extends BaseRes {
data: { data: {
user: any; user: any
}; }
} }
export interface BatchUserInfoRes extends BaseRes { export interface BatchUserInfoRes extends BaseRes {
data: { data: {
items: any[]; items: any[]
}; }
} }
export interface BatchDocMetaRes extends BaseRes { export interface BatchDocMetaRes extends BaseRes {
data: { data: {
metas: SuccessDocMeta[]; metas: SuccessDocMeta[]
failed_list: FailedDocMeta[]; failed_list: FailedDocMeta[]
}; }
} }
export type ReceiveIDType = export type ReceiveIDType =
@ -92,7 +92,7 @@ export namespace LarkServer {
| "user_id" | "user_id"
| "union_id" | "union_id"
| "email" | "email"
| "chat_id"; | "chat_id"
export type MsgType = export type MsgType =
| "text" | "text"
@ -104,5 +104,5 @@ export namespace LarkServer {
| "sticker" | "sticker"
| "interactive" | "interactive"
| "share_chat" | "share_chat"
| "share_user"; | "share_user"
} }

View File

@ -1,17 +1,17 @@
import { LarkServer } from "./larkServer"; import { LarkServer } from "./larkServer"
export namespace MsgProxy { export namespace MsgProxy {
export interface BaseBody { export interface BaseBody {
msg_type: LarkServer.MsgType; msg_type: LarkServer.MsgType
content: string; content: string
api_key: string; api_key: string
} }
export interface GroupBody extends BaseBody { export interface GroupBody extends BaseBody {
group_id: string; group_id: string
} }
export interface NormalBody extends BaseBody { export interface NormalBody extends BaseBody {
receive_id: string; receive_id: string
receive_id_type: LarkServer.ReceiveIDType; receive_id_type: LarkServer.ReceiveIDType
} }
export type Body = GroupBody & NormalBody; export type Body = GroupBody & NormalBody
} }

View File

@ -5,20 +5,20 @@ export interface User {
/** /**
* id * id
*/ */
id: string; id: string
/** /**
* *
* @example zhaoyingbo * @example zhaoyingbo
*/ */
userId: string; userId: string
/** /**
* open_id * open_id
*/ */
openId: string; openId: string
/** /**
* *
*/ */
remindList: string[]; remindList: string[]
} }
/** /**
@ -28,31 +28,31 @@ export interface Remind {
/** /**
* id * id
*/ */
id: string; id: string
/** /**
* id * id
*/ */
owner: string; owner: string
/** /**
* Id * 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 * 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 * key
*/ */
imageKey?: string; imageKey?: string
/** /**
* *
*/ */
content?: string; content?: string
/** /**
* *
*/ */
confirmText?: string; confirmText?: string
/** /**
* *
*/ */
cancelText?: string; cancelText?: string
/** /**
* *
*/ */
delayText?: string; delayText?: string
} | null; } | null
/** /**
* *
*/ */
@ -91,7 +91,7 @@ export interface Remind {
* ${owner} * ${owner}
* ${remindTime} * ${remindTime}
*/ */
pendingTemplateId: string; pendingTemplateId: string
/** /**
* ID * ID
* ${owner} * ${owner}
@ -99,38 +99,38 @@ export interface Remind {
* ${result} textresult对应的 * ${result} textresult对应的
* ${interactTime} * ${interactTime}
*/ */
interactedTemplateId: string; interactedTemplateId: string
/** /**
* ID * ID
*/ */
confirmedTemplateId: string; confirmedTemplateId: string
/** /**
* ID * ID
*/ */
cancelededTemplateId: string; cancelededTemplateId: string
/** /**
* ID * ID
*/ */
delayedTemplateId: string; delayedTemplateId: string
} | null; } | null
/** /**
* *
*/ */
remindTimes: RemindTime[]; remindTimes: RemindTime[]
/** /**
* *
*/ */
enabled: boolean; enabled: boolean
/** /**
* yyyy-MM-dd HH:mm * yyyy-MM-dd HH:mm
*/ */
nextRemindTime: string; nextRemindTime: string
/** /**
* *
*/ */
nextRemindTimeCHS: string; nextRemindTimeCHS: string
} }
/** /**
@ -155,23 +155,23 @@ export interface RemindTime {
| "monthly" | "monthly"
| "yearly" | "yearly"
| "workday" | "workday"
| "holiday"; | "holiday"
/** /**
* 格式为HH:mm single类型时仅作展示用yyyy-MM-dd HH:mm * 格式为HH:mm single类型时仅作展示用yyyy-MM-dd HH:mm
*/ */
time: string; time: string
/** /**
* [1-7]frequency为weekly时有效 * [1-7]frequency为weekly时有效
*/ */
daysOfWeek: number[]; daysOfWeek: number[]
/** /**
* [1-31]frequency为monthly时有效 * [1-31]frequency为monthly时有效
*/ */
daysOfMonth: number[]; daysOfMonth: number[]
/** /**
* frequency为 yearly MM-dd * frequency为 yearly MM-dd
*/ */
dayOfYear: string; dayOfYear: string
} }
/** /**
@ -182,15 +182,15 @@ export interface RemindRecord {
/** /**
* Id * Id
*/ */
id: string; id: string
/** /**
* Id * Id
*/ */
remindId: string; remindId: string
/** /**
* Id * Id
*/ */
messageId: string; messageId: string
/** /**
* *
* pending: 待确认 * pending: 待确认
@ -198,17 +198,17 @@ export interface RemindRecord {
* confirmed: 已确认 * confirmed: 已确认
* canceled: 已取消 * canceled: 已取消
*/ */
status: "pending" | "delayed" | "confirmed" | "canceled"; status: "pending" | "delayed" | "confirmed" | "canceled"
/** /**
* yyyy-MM-dd HH:mm * yyyy-MM-dd HH:mm
*/ */
remindTime: string; remindTime: string
/** /**
* yyyy-MM-dd HH:mm * yyyy-MM-dd HH:mm
*/ */
interactTime: string; interactTime: string
/** /**
* 07:00 * 07:00
*/ */
result: object; result: object
} }

View File

@ -4,15 +4,15 @@ export namespace SheetProxy {
} }
export interface Body { export interface Body {
api_key: string; api_key: string
sheet_token: string; sheet_token: string
type: "insert"; type: "insert"
range: string; range: string
values: string[][]; // 二维数组 values: string[][] // 二维数组
} }
// 判断一个值是否是枚举中的值 // 判断一个值是否是枚举中的值
export const isType = (value: any): value is Type => { export const isType = (value: any): value is Type => {
return Object.values(Type).includes(value); return Object.values(Type).includes(value)
}; }
} }

View File

@ -1,91 +1,89 @@
import { LarkAction, LarkEvent } from "../types"; import { LarkAction, LarkEvent } from "../types"
/** /**
* *
* @param {LarkEvent.Data} body * @param {LarkEvent.Data} body
*/ */
export const getIsEventMsg = (body: LarkEvent.Data) => { export const getIsEventMsg = (body: LarkEvent.Data) => {
return body?.header?.event_type === "im.message.receive_v1"; return body?.header?.event_type === "im.message.receive_v1"
}; }
/** /**
* *
* @param {LarkEvent.Data} body * @param {LarkEvent.Data} body
* @returns * @returns
*/ */
export const getMsgType = (body: LarkEvent.Data) => { export const getMsgType = (body: LarkEvent.Data) => {
return body?.event?.message?.message_type; return body?.event?.message?.message_type
}; }
/** /**
* Id * Id
* @param {LarkEvent.Data} body * @param {LarkEvent.Data} body
* @returns * @returns
*/ */
export const getChatId = (body: LarkEvent.Data) => { export const getChatId = (body: LarkEvent.Data) => {
return body?.event?.message?.chat_id; return body?.event?.message?.chat_id
}; }
/** /**
* Id * Id
* @param {LarkEvent.Data} body * @param {LarkEvent.Data} body
* @returns * @returns
*/ */
export const getUserId = (body: LarkEvent.Data) => { export const getUserId = (body: LarkEvent.Data) => {
return body?.event?.sender?.sender_id?.user_id; return body?.event?.sender?.sender_id?.user_id
}; }
/** /**
* Action消息 * Action消息
* @param {LarkAction.Data} body * @param {LarkAction.Data} body
*/ */
export const getIsActionMsg = (body: LarkAction.Data) => { export const getIsActionMsg = (body: LarkAction.Data) => {
return body?.action; return body?.action
}; }
/** /**
* Action类型 * Action类型
* @param {LarkAction.Data} body * @param {LarkAction.Data} body
* @returns {string} Action类型 * @returns {string} Action类型
*/ */
export const getActionType = (body: LarkAction.Data) => { export const getActionType = (body: LarkAction.Data) => {
return body?.action?.tag; return body?.action?.tag
}; }
/** /**
* *
* @param {LarkEvent.Data} body * @param {LarkEvent.Data} body
* @returns {string} * @returns {string}
*/ */
export const getMsgText = (body: LarkEvent.Data) => { export const getMsgText = (body: LarkEvent.Data) => {
try { try {
const { text }: { text: string } = JSON.parse( const { text }: { text: string } = JSON.parse(body?.event?.message?.content)
body?.event?.message?.content // 去掉@_user_1相关的内容例如 '@_user_1 测试' -> '测试'
); const textWithoutAt = text.replace(/@_user_\d+/g, "")
// 去掉@_user_1相关的内容例如 '@_user_1 测试' -> '测试' // // 去除空格和换行
const textWithoutAt = text.replace(/@_user_\d+/g, ""); // const textWithoutSpace = textWithoutAt.replace(/[\s\n]/g, "");
// // 去除空格和换行 return textWithoutAt
// const textWithoutSpace = textWithoutAt.replace(/[\s\n]/g, ""); } catch (e) {
return textWithoutAt; return ""
} catch (e) { }
return ""; }
}
}; /**
*
/** * @param {LarkEvent.Data} body
* * @returns {string}
* @param {LarkEvent.Data} body */
* @returns {string} export const getChatType = (body: LarkEvent.Data) => {
*/ return body?.event?.message?.chat_type
export const getChatType = (body: LarkEvent.Data) => { }
return body?.event?.message?.chat_type;
}; /**
*
/** * @param {LarkEvent.Data} body
* * @returns {Array}
* @param {LarkEvent.Data} body */
* @returns {Array} export const getMentions = (body: LarkEvent.Data) => {
*/ return body?.event?.message?.mentions
export const getMentions = (body: LarkEvent.Data) => { }
return body?.event?.message?.mentions;
};

View File

@ -1,12 +1,12 @@
// 裁剪指定prefix路径 // 裁剪指定prefix路径
export function trimPathPrefix(path: string, prefix: string): string { 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) => { export const safeJsonStringify = (obj: any) => {
try { try {
return JSON.stringify(obj); return JSON.stringify(obj)
} catch (e) { } catch (e) {
return String(obj); return String(obj)
} }
}; }

View File

@ -1,18 +1,22 @@
export const managePb404 = async <T>(dbFunc: Function): Promise<T | null> => { export const managePb404 = async <T>(
try { dbFunc: () => Promise<T>
return await dbFunc(); ): Promise<T | null> => {
} catch (err: any) { try {
if (err?.message === "The requested resource wasn't found.") { return await dbFunc()
return null; } catch (err: any) {
} else throw err; if (err?.message === "The requested resource wasn't found.") {
} return null
}; } else throw err
}
export const managePbError = async <T>(dbFunc: Function): Promise<T | null> => { }
try {
return await dbFunc(); export const managePbError = async <T>(
} catch (err: any) { dbFunc: () => Promise<T>
console.log("🚀 ~ managePbError ~ err:", err); ): Promise<T | null> => {
return null; try {
} return await dbFunc()
}; } catch (err: any) {
console.log("🚀 ~ managePbError ~ err:", err)
return null
}
}