feat: 完成了消息的处理

This commit is contained in:
zhaoyingbo 2023-09-02 20:56:40 +08:00
parent f597594bd8
commit 686b3efb34
8 changed files with 463 additions and 53 deletions

View File

@ -20,6 +20,7 @@
"oderwat.indent-rainbow",
"jock.svg",
"GitHub.copilot",
// "aminer.codegeex",
"ChakrounAnas.turbo-console-log",
"Gruntfuggly.todo-tree",
"MS-CEINTL.vscode-language-pack-zh-hans"

View File

@ -3,6 +3,8 @@
## 里程碑
[ ] 建立日志系统
[ ] Dockerfile & Gitea Action
[ ] webhook消息通知
@ -15,6 +17,8 @@
[ ] 提醒统计
[ ] 备忘录功能
## 零碎TODO
[ ] 用药提醒的卡片模板,确认、取消、延迟
@ -23,6 +27,8 @@
[ ] 通过getInfo指令在对话里返回请求的数据
[ ] 支持单独艾特某人,应该是创建一句话提醒的时候,提醒里边如果有艾特的非小煎蛋人物就发送给他
## 待定TODO
[ ] 支持消息加急
@ -54,18 +60,13 @@ interface Remind {
*/
owner: string;
/**
* 接收消息相关信息
* 接收者类型
*/
subscriber: {
/**
* 接收者类型
*/
type: "open_id" | "user_id" | "union_id" | "email" | "chat_id";
/**
* 接收者Id
*/
id: string;
};
subscriberType: "open_id" | "user_id" | "union_id" | "email" | "chat_id";
/**
* 接收者Id
*/
subscriberId: string;
/**
* 是否需要回复,不需要回复的也不会重复提醒
*/
@ -222,4 +223,32 @@ interface RemindRecord {
result: string;
}
```
### 用户列表
存储用户信息便于拿取提醒列表浏览器应用接入米盾之后可以用user_id对应username拿到对应信息
后续可能有关键词提醒
```typescript
interface User {
/**
* id
*/
id: string;
/**
* 用户名
* @example zhaoyingbo
*/
user_id: string;
/**
* open_id
*/
open_id: string;
/**
* 提醒列表
*/
remindList: string[];
}
```

View File

@ -1,39 +1,49 @@
'use strict'
const { genSetRemindCard } = require("../../utils/genCard")
const { filterIllegalMsg, filterGetInfoCommand, getMsgText, getChatId } = require("../../utils/msgTools")
const { upsertUser } = require("../../utils/pb")
const { sendMsg } = require("../../utils/sendMsg")
/**
*
* @param {LarkMessageEvent} body
* @returns
*/
// const getSenderInfo = async (body) => {
// if (body.event.sender.sender_type === 'user') {
// // 是人发的给注册下
// return await upsertUser(body.event.sender.sender_id)
// }
// return false
// }
const isTextMessage = (body) => {
return body?.event?.message?.message_type === 'text'
}
const getChatId = (body) => {
return body?.event?.message?.chat_id
}
const sendIllegalBackInfo = (body) => {
const chatId = getChatId(body)
if (!chatId) return
const textList = [
'仅支持普通文本内容[黑脸]',
'唔...我只能处理普通文本哦[泣不成声]',
'噢!这似乎是个非普通文本[看]',
'哇!这是什么东东?我只懂普通文本啦![可爱]',
'只能处理普通文本内容哦[捂脸]',
]
sendMsg('chat_id', chatId, 'text', JSON.stringify({ text: textList[Math.floor(Math.random() * textList.length)] }))
/**
* 发送设置提醒卡片
* @param {LarkMessageEvent} body
*/
const sendSetRemindCard = async (body) => {
const text = getMsgText(body)
const setRemindCard = genSetRemindCard(text)
await sendMsg('chat_id', getChatId(body), 'interactive', setRemindCard)
}
module.exports = async function (fastify, opts) {
// 机器人验证及分发
fastify.post('/', async function (request, reply) {
console.log(JSON.stringify(request.body))
// 验证机器人
if (request.body.type === 'url_verification') {
console.log('url_verification')
return { challenge: request.body.challenge }
}
if (!isTextMessage(request.body)) {
sendIllegalBackInfo(request.body)
}
// 过滤非法消息
if (filterIllegalMsg(request.body)) return 'OK'
// 过滤获取用户信息的指令
if (filterGetInfoCommand(request.body)) return 'OK'
// const userInfo = await getSenderInfo(request.body)
// if (!userInfo) return 'OK'
// console.log(userInfo)
// 发送设置提醒卡片
sendSetRemindCard(request.body)
return 'OK'
})
}

View File

@ -1,4 +1,4 @@
const { genCard } = require("../utils/genCard")
const { genRemindCard } = require("../utils/genCard")
const { getDelayedRemindTime, getNextRemindTime } = require("../utils/nextRemindTime")
const { getCurrRemind, updateNextRemindTime, getPendingRemindRecord, updateRemindRecord, createRemindRecord } = require("../utils/pb")
const { sendMsg, updateCard } = require("../utils/sendMsg")
@ -12,7 +12,7 @@ const updateLastPendingCardToDelayed = async (remind) => {
// 当前时间即为回复时间
const interactTime = getNowStr()
// 创建delayed状态的卡片
const card = genCard(remind, 'delayed', null, interactTime)
const card = genRemindCard(remind, 'delayed', null, interactTime)
// 更新飞书的卡片
await updateCard(record.messageId, card)
// 更新remindRecord
@ -33,9 +33,9 @@ const manageRemind = async (remind) => {
// 更新上一个pending状态的卡片至delayed
await updateLastPendingCardToDelayed(remind)
// 生成卡片
const card = genCard(remind, 'pending', null, null)
const card = genRemindCard(remind, 'pending', null, null)
// 发送卡片
const messageId = await sendMsg(remind.subscriber.type, remind.subscriber.id, 'interactive', card)
const messageId = await sendMsg(remind.subscriberType, remind.subscriberId, 'interactive', card)
// 创建remindRecord
await createRemindRecord(remind.id, messageId)
// 获取下一次提醒时间不需要回复的直接时下一次提醒时间需要回复的则是当前时间延后10min

209
typings.d.ts vendored
View File

@ -1,4 +1,25 @@
/**
*
*/
interface User {
/**
* id
*/
id: string;
/**
*
* @example zhaoyingbo
*/
user_id: string;
/**
* open_id
*/
open_id: string;
/**
*
*/
remindList: string[];
}
/**
*
@ -13,18 +34,13 @@ interface Remind {
*/
owner: string;
/**
*
*
*/
subscriber: {
/**
*
*/
type: "open_id" | "user_id" | "union_id" | "email" | "chat_id";
/**
* Id
*/
id: string;
};
subscriberType: "open_id" | "user_id" | "union_id" | "email" | "chat_id";
/**
* Id
*/
subscriberId: string;
/**
*
*/
@ -174,3 +190,172 @@ interface RemindRecord {
*/
result: string;
}
/**
*
*/
interface Header {
/**
* ID
* @example 0f8ab23b60993cf8dd15c8cde4d7b0f5
*/
event_id: string;
/**
* token
* @example tV9djUKSjzVnekV7xTg2Od06NFTcsBnj
*/
token: string;
/**
*
* @example 1693565712117
*/
create_time: string;
/**
*
* @example im.message.receive_v1
*/
event_type: string;
/**
* tenant_key
* @example 2ee61fe50f4f1657
*/
tenant_key: string;
/**
* app_id
* @example cli_a1eff35b43b89063
*/
app_id: string;
}
/**
* ID信息
*/
interface UserIdInfo {
/**
*
* @example ou_032f507d08f9a7f28b042fcd086daef5
*/
open_id: string;
/**
*
* @example on_7111660fddd8302ce47bf1999147c011
*/
union_id: string;
/**
*
* @example zhaoyingbo
*/
user_id: string;
}
/**
* AT的人的信息
*/
interface Mention {
/**
* ID信息
*/
id: UserIdInfo;
/**
*
* @example "@_user_1"
*/
key: string;
/**
*
* @example
*/
name: string;
/**
* ID
* @example 2ee61fe50f4f1657
*/
tenant_key: string;
}
/**
*
*/
interface Message {
/**
* ID
* @example oc_433b1cb7a9dbb7ebe70a4e1a59cb8bb1
*/
chat_id: string;
/**
*
* @example group | p2p
*/
chat_type: string;
/**
* JSON字符串文本内容
* @example "{\"text\":\"@_user_1 测试\"}"
*/
content: string;
/**
*
* @example 1693565711996
*/
create_time: string;
/**
*
*/
mentions? : Mention[];
/**
* ID
* @example om_038fc0eceed6224a1abc1cdaa4266405
*/
message_id: string;
/**
*
* @example textpostimagefileaudiomediastickerinteractiveshare_chatshare_user
*/
message_type: string;
}
/**
*
*/
interface Sender {
/**
* id
*/
sender_id: UserIdInfo;
/**
*
* @example user
*/
sender_type: string;
/**
* ID
* @example 2ee61fe50f4f1657
*/
tenant_key: string;
}
/**
*
*/
interface Event {
message: Message;
sender: Sender;
}
/**
*
*/
interface LarkMessageEvent {
/**
*
* @example 2.0
*/
schema: string;
/**
*
*/
header: Header;
/**
*
*/
event: Event;
}

View File

@ -1,3 +1,5 @@
const { getNowStr } = require("./time");
/**
* 生成提醒时间文本
*/
@ -79,7 +81,7 @@ const genActions = (cardInfo, cardType, needReply) => {
// 如果是交互卡片的完成交互状态,不显示按钮
if (cardType !== "pending") return null;
const { confirmText = '完成', cancelText, delayText } = cardInfo;
const { confirmText = "完成", cancelText, delayText } = cardInfo;
const actions = [];
const genBtn = (text, type) => ({
tag: "button",
@ -160,7 +162,7 @@ const genRemindInfo = (
* @param {*} interactInfo 交互信息
* @param {*} interactTime 交互时间在上层已经转成 yyyy-MM-dd HH:mm 格式了
*/
module.exports.genCard = (
module.exports.genRemindCard = (
remindInfo,
cardType,
interactInfo,
@ -177,7 +179,7 @@ module.exports.genCard = (
dayOfYear,
} = remindInfo;
// TODO: Onwer信息获取先暂定自己
const owner = "赵英博";
const owner = "zhaoyingbo";
// 生成提醒时间文本
const remindTime = genRemindTimeText(
frequency,
@ -282,3 +284,41 @@ module.exports.genCard = (
header,
});
};
module.exports.genSetRemindCard = (text) => {
return JSON.stringify({
elements: [
{
tag: "div",
text: {
content: `📝 ${text}`,
tag: "lark_md",
},
},
{
tag: "action",
actions: [
{
tag: "picker_datetime",
placeholder: {
tag: "plain_text",
content: "请选择提醒时间",
},
value: {
content: text,
},
initial_datetime: getNowStr(),
},
],
layout: "bisected",
},
],
header: {
template: "turquoise",
title: {
content: "✨小煎蛋提醒创建工具",
tag: "plain_text",
},
},
});
};

90
utils/msgTools.js Normal file
View File

@ -0,0 +1,90 @@
const { sendMsg } = require("./sendMsg")
/**
* 获取事件文本类型
* @param {LarkMessageEvent} body
* @returns
*/
const getMsgType = (body) => {
return body?.event?.message?.message_type
}
/**
* 获取对话流Id
* @param {LarkMessageEvent} body
* @returns
*/
const getChatId = (body) => {
return body?.event?.message?.chat_id
}
/**
* 获取文本内容并剔除艾特信息
* @param {LarkMessageEvent} body
* @returns {string} 文本内容
*/
const getMsgText = (body) => {
// TODO: 如果之后想支持单独提醒,这里需要做模板解析
try {
const { text } = 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 textWithoutSpace
}
catch (e) {
return ''
}
}
/**
* 过滤出非法消息如果发表情包就直接发回去
* @param {LarkMessageEvent} body
* @returns {boolean} 是否为非法消息
*/
const filterIllegalMsg = (body) => {
const chatId = getChatId(body)
if (!chatId) return true
const msgType = getMsgType(body)
if (msgType === 'sticker') {
const content = body?.event?.message?.content
sendMsg('chat_id', chatId, 'sticker', content)
return true
}
if (msgType !== 'text') {
const textList = [
'仅支持普通文本内容[黑脸]',
'唔...我只能处理普通文本哦[泣不成声]',
'噢!这似乎是个非普通文本[看]',
'哇!这是什么东东?我只懂普通文本啦![可爱]',
'只能处理普通文本内容哦[捂脸]',
]
const content = JSON.stringify({ text: textList[Math.floor(Math.random() * textList.length)] })
sendMsg('chat_id', chatId, 'text', content)
return true
}
return false
}
/**
* 过滤出info指令
* @param {LarkMessageEvent} body
* @returns {boolean} 是否为info指令
*/
const filterGetInfoCommand = (body) => {
const chatId = getChatId(body)
const text = getMsgText(body)
if (text !== 'info') return false
const content = JSON.stringify({ text: JSON.stringify(body)})
sendMsg('chat_id', chatId, 'text', content)
return true
}
module.exports = {
getMsgType,
getChatId,
getMsgText,
filterIllegalMsg,
filterGetInfoCommand,
}

View File

@ -78,3 +78,58 @@ module.exports.createRemindRecord = async (remindId, messageId) => {
module.exports.updateRemindRecord = async (id, record) => {
await pb.collection('remindRecord').update(id, record)
}
/**
* 获取指定用户的信息
* @param {string} userId 用户id
* @returns {User} 用户信息
*/
const getUser = async (userId) => {
return await pb.collection("user").getFirstListItem(`userId="${userId}"`)
}
/**
* 创建用户
* @param {string} userId 用户id
* @param {string} openId 用户openId
* @param {[]} remindList 用户提醒列表
* @returns {User} 用户信息
*/
const createUser = async (userId, openId, remindList) => {
return await pb.collection("user").create({ userId, openId, remindList })
}
/**
* 更新用户信息
* @param {string} id 用户id
* @param {*} data 用户信息
* @returns {User} 用户信息
*/
const updateUser = async (id, data) => {
return await pb.collection("user").update(id, data)
}
/**
* 更新用户信息如果用户不存在则创建
* @param {User} userInfo
* @returns {User} 用户信息
*/
module.exports.upsertUser = async (userInfo) => {
try {
const user = await getUser(userInfo.user_id);
// 如果用户信息没变化,直接返回
if (user.openId === userInfo.open_id) {
return user;
}
// 如果用户信息有变化,更新
return await updateUser(user.id, {
openId: userInfo.open_id,
});
} catch (err) {
// 没有这个用户上传个新的
if (err.message === "The requested resource wasn't found.") {
const user = await createUser(userInfo.user_id, userInfo.open_id, []);
return user;
} else throw err;
}
}