feat: 完成了消息的处理
This commit is contained in:
parent
f597594bd8
commit
686b3efb34
@ -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"
|
||||
|
51
README.md
51
README.md
@ -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[];
|
||||
}
|
||||
```
|
@ -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'
|
||||
})
|
||||
}
|
@ -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
209
typings.d.ts
vendored
@ -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 text、post、image、file、audio、media、sticker、interactive、share_chat、share_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;
|
||||
}
|
@ -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
90
utils/msgTools.js
Normal 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,
|
||||
}
|
55
utils/pb.js
55
utils/pb.js
@ -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;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user