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

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

2
.gitignore vendored
View File

@ -49,7 +49,7 @@ profile-*
.idea
# vscode
.vscode
# .vscode
*code-workspace
# 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 { managePbError } from "../../utils/pbTools";
import pbClient from "../pbClient";
import { DB } from "../../types"
import { managePbError } from "../../utils/pbTools"
import pbClient from "../pbClient"
const getOne = (id: string) =>
managePbError<DB.MessageGroup>(() =>
pbClient.collection("api_key").getOne(id, {
expand: "app",
})
);
)
const apiKey = {
getOne,
};
}
export default apiKey;
export default apiKey

View File

@ -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<DB.AppInfo>(() =>
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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,40 +1,42 @@
import appInfo from "../appInfo";
import pbClient from "../pbClient";
const tokenCache = {} as Record<string, string>;
/**
* 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<string, string>
/**
* 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

View File

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

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 { 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}`)

View File

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

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 { 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
}

View File

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

View File

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

View File

@ -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<string, any>,
open_id: {} as Record<string, any>,
union_id: {} as Record<string, any>,
user_id: {} as Record<string, any>,
email: {} as Record<string, any>,
};
// 发送消息列表
const sendList = [] as Promise<any>[];
// 构造消息记录
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<string, any>,
open_id: {} as Record<string, any>,
union_id: {} as Record<string, any>,
user_id: {} as Record<string, any>,
email: {} as Record<string, any>,
}
// 发送消息列表
const sendList = [] as Promise<any>[]
// 构造消息记录
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)
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)<LarkServer.BatchDocMetaRes>(
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

View File

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

View File

@ -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 <T = LarkServer.BaseRes>({
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<T> => {
const headersWithAuth = {
Authorization: `Bearer ${await db.tenantAccessToken.get(appName)}`,
...additionalHeaders,
};
}
return netTool<T>({
url,
method,
@ -39,14 +39,14 @@ const larkNetTool = async <T = LarkServer.BaseRes>({
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<T> =>
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<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 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)<LarkServer.BaseRes>(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)<LarkServer.BaseRes>(URL, { content });
};
const URL = `https://open.f.mioffice.cn/open-apis/im/v1/messages/${message_id}`
return larkNetTool.patch(appName)<LarkServer.BaseRes>(URL, { content })
}
const message = {
send,
update,
};
}
export default message;
export default message

View File

@ -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)<LarkServer.BaseRes>(URL, {
valueRange: {
range,
values,
},
});
};
})
}
const sheet = {
insterRows,
};
insertRows,
}
export default sheet;
export default sheet

View File

@ -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)<LarkServer.UserSessionRes>(URL, { code });
};
const URL = `https://open.f.mioffice.cn/open-apis/mina/v2/tokenLoginValidate`
return larkNetTool.post(appName)<LarkServer.UserSessionRes>(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)<LarkServer.UserInfoRes>(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)<LarkServer.BatchUserInfoRes>(
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

View File

@ -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 <T = any>({
additionalHeaders,
}: NetRequestParams): Promise<T> => {
// 拼接完整的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 <T = any>({
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 = <T = any>(
url: string,
queryParams?: any,
additionalHeaders?: any
): Promise<T> =>
netTool({ url, method: "get", queryParams, additionalHeaders });
): Promise<T> => netTool({ url, method: "get", queryParams, additionalHeaders })
/**
* POST请求并返回一个解析为响应数据的Promise
@ -134,7 +135,7 @@ netTool.post = <T = any>(
queryParams?: any,
additionalHeaders?: any
): Promise<T> =>
netTool({ url, method: "post", payload, queryParams, additionalHeaders });
netTool({ url, method: "post", payload, queryParams, additionalHeaders })
/**
* PUT请求并返回一个解析为响应数据的Promise
@ -151,7 +152,7 @@ netTool.put = <T = any>(
queryParams?: any,
additionalHeaders?: any
): Promise<T> =>
netTool({ url, method: "put", payload, queryParams, additionalHeaders });
netTool({ url, method: "put", payload, queryParams, additionalHeaders })
/**
* DELETE请求并返回一个解析为响应数据的Promise
@ -168,7 +169,7 @@ netTool.del = <T = any>(
queryParams?: any,
additionalHeaders?: any
): Promise<T> =>
netTool({ url, method: "delete", payload, queryParams, additionalHeaders });
netTool({ url, method: "delete", payload, queryParams, additionalHeaders })
/**
* PATCH请求并返回一个解析为响应数据的Promise
@ -185,7 +186,7 @@ netTool.patch = <T = any>(
queryParams?: any,
additionalHeaders?: any
): Promise<T> =>
netTool({ url, method: "patch", payload, queryParams, additionalHeaders });
netTool({ url, method: "patch", payload, queryParams, additionalHeaders })
/**
* 400 Bad Request的响应对象
@ -195,7 +196,7 @@ netTool.patch = <T = any>(
* @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

View File

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

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 = "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))

View File

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

View File

@ -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": []
}
]
[
{
"_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": []
}
]

View File

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

View File

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

View File

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

View File

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

View File

@ -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 textpostimagefileaudiomediastickerinteractiveshare_chatshare_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
}
}

View File

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

View File

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

View File

@ -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} textresult对应的
* ${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
}

View File

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

View File

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

View File

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

View File

@ -1,18 +1,22 @@
export const managePb404 = async <T>(dbFunc: Function): Promise<T | null> => {
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 <T>(dbFunc: Function): Promise<T | null> => {
try {
return await dbFunc();
} catch (err: any) {
console.log("🚀 ~ managePbError ~ err:", err);
return null;
}
};
export const managePb404 = async <T>(
dbFunc: () => Promise<T>
): Promise<T | null> => {
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 <T>(
dbFunc: () => Promise<T>
): Promise<T | null> => {
try {
return await dbFunc()
} catch (err: any) {
console.log("🚀 ~ managePbError ~ err:", err)
return null
}
}