码桶
发现社区成员的开源项目
push.js6.2 KB
/**
* Webhook 推送插件路由
* 前端管理接口(PushConfig 组件调用)+ 外部 Webhook 接收接口。
* 实际数据操作由 plugins/push/index.js 导出的方法完成。
*/
const express = require('express');
const router = express.Router();
const db = require('../lib/db');
const Auth = require('../lib/auth');
const Bot = require('../lib/bot');
const push = require('../plugins/push');
/** 当前用户 + 校验 bot 归属 */
async function owns(req, res) {
const u = await Auth.currentUser(req);
if (!u) { res.status(401).json({ ok: false, msg: '未登录' }); return null; }
const botId = parseInt(req.body.bot_id || req.query.bot_id || 0, 10);
if (!botId) { res.status(400).json({ ok: false, msg: '缺少 bot_id' }); return null; }
const bot = await Bot.owned(botId, u.id);
if (!bot) { res.status(403).json({ ok: false, msg: '机器人不存在或无权访问' }); return null; }
return { user: u, bot };
}
// 列表:通道 + 定时推送 + 默认会话
router.post('/channels', async (req, res) => {
const ctx = await owns(req, res); if (!ctx) return;
try {
const data = await push.listForBot(ctx.bot.id);
res.json({ ok: true, ...data });
} catch (e) {
res.json({ ok: false, msg: e.message });
}
});
// 绑定 / 重新绑定
router.post('/bind', async (req, res) => {
const ctx = await owns(req, res); if (!ctx) return;
try {
const peerId = (req.body.default_peer || '').toString().trim();
const ch = req.body.rebind
? await push.rebindChannel(ctx.bot.id, peerId)
: await push.bindChannel(ctx.bot.id, peerId, '');
res.json({ ok: true, channel: { ...ch, url: push.webhookUrl(ch.token) } });
} catch (e) {
res.json({ ok: false, msg: e.message });
}
});
// 解绑(删除通道)
router.post('/unbind', async (req, res) => {
const ctx = await owns(req, res); if (!ctx) return;
try {
const id = parseInt(req.body.id, 10);
if (!id) throw new Error('缺少 id');
await push.deleteChannel(id, ctx.bot.id);
res.json({ ok: true });
} catch (e) {
res.json({ ok: false, msg: e.message });
}
});
// 更新通道(AI 开关 / AI 指令)
router.post('/channel_update', async (req, res) => {
const ctx = await owns(req, res); if (!ctx) return;
try {
const id = parseInt(req.body.id, 10);
if (!id) throw new Error('缺少 id');
const patch = {};
if (req.body.ai_enabled !== undefined) patch.ai_enabled = req.body.ai_enabled ? 1 : 0;
if (req.body.ai_prompt !== undefined) patch.ai_prompt = String(req.body.ai_prompt || '');
await push.updateChannel(id, ctx.bot.id, patch);
res.json({ ok: true });
} catch (e) {
res.json({ ok: false, msg: e.message });
}
});
// 添加定时推送
router.post('/schedule_add', async (req, res) => {
const ctx = await owns(req, res); if (!ctx) return;
try {
const { content, time_desc, peer_id } = req.body;
await push.addSchedule(ctx.bot.id, (peer_id || '').toString().trim(), {
content,
ai_enabled: !!req.body.ai_enabled,
ai_prompt: '',
time_desc,
});
res.json({ ok: true });
} catch (e) {
res.json({ ok: false, msg: e.message });
}
});
// 取消定时推送
router.post('/schedule_del', async (req, res) => {
const ctx = await owns(req, res); if (!ctx) return;
try {
const id = parseInt(req.body.id, 10);
if (!id) throw new Error('缺少 id');
await push.deleteSchedule(id, ctx.bot.id);
res.json({ ok: true });
} catch (e) {
res.json({ ok: false, msg: e.message });
}
});
// ==================== 勿扰 / 推送设置(DND)====================
const dnd = require('../lib/dnd');
// 列出该 bot 近期会话(供选择要设置勿扰的用户)+ 已有勿扰配置
router.post('/dnd_list', async (req, res) => {
const ctx = await owns(req, res); if (!ctx) return;
try {
await dnd.ensure();
// 近期有往来的 peer(最多 100 个)
const peers = await db.rows(
`SELECT peer_id, MAX(created_at) AS last_at, COUNT(*) AS cnt
FROM messages WHERE bot_id=? AND peer_id IS NOT NULL AND peer_id<>''
GROUP BY peer_id ORDER BY last_at DESC LIMIT 100`,
[ctx.bot.id]
);
const dndRows = await db.rows('SELECT * FROM user_dnd WHERE bot_id=?', [ctx.bot.id]);
const dndMap = {};
dndRows.forEach(r => { dndMap[r.peer_id] = { enabled: r.enabled === 1, start_min: r.start_min, end_min: r.end_min, mask: r.mask }; });
res.json({ ok: true, peers, dnd: dndMap, cats: dnd.CAT, defaultMask: dnd.DEFAULT_MASK });
} catch (e) {
res.json({ ok: false, msg: e.message });
}
});
// 获取某个 peer 的勿扰配置
router.post('/dnd_get', async (req, res) => {
const ctx = await owns(req, res); if (!ctx) return;
try {
const peerId = (req.body.peer_id || '').toString().trim();
if (!peerId) throw new Error('缺少 peer_id');
const cfg = await dnd.getDnd(ctx.bot.id, peerId);
res.json({ ok: true, dnd: cfg, cats: dnd.CAT, defaultMask: dnd.DEFAULT_MASK });
} catch (e) {
res.json({ ok: false, msg: e.message });
}
});
// 保存某个 peer 的勿扰配置
router.post('/dnd_save', async (req, res) => {
const ctx = await owns(req, res); if (!ctx) return;
try {
const peerId = (req.body.peer_id || '').toString().trim();
if (!peerId) throw new Error('缺少 peer_id');
await dnd.setDnd(ctx.bot.id, peerId, {
enabled: !!req.body.enabled,
start_min: parseInt(req.body.start_min, 10) || 0,
end_min: parseInt(req.body.end_min, 10) || 0,
mask: req.body.mask == null ? dnd.DEFAULT_MASK : (parseInt(req.body.mask, 10) | 0),
});
res.json({ ok: true });
} catch (e) {
res.json({ ok: false, msg: e.message });
}
});
// 外部 Webhook 接收(无需登录):POST /api/push/webhook/<token>
router.post('/webhook/:token', async (req, res) => {
try {
const token = req.params.token;
const body = req.body && Object.keys(req.body).length ? req.body : req.query;
const result = await push.handleWebhook(token, body);
res.json({ ok: true, ...result });
} catch (e) {
const status = e.status || 500;
res.status(status).json({ ok: false, msg: e.message });
}
});
module.exports = router;