mirror of
https://github.com/Jerryplusy/crystelf-plugin.git
synced 2025-12-05 15:41:56 +00:00
Merge pull request #8 from devil233-ui/main
fix(rss):修复rss推送,新增查看订阅列表功能
This commit is contained in:
commit
84c6990dc7
167
apps/rssPush.js
167
apps/rssPush.js
@ -6,7 +6,6 @@ import fs from 'fs';
|
|||||||
import rssCache from '../lib/rss/rssCache.js';
|
import rssCache from '../lib/rss/rssCache.js';
|
||||||
import schedule from 'node-schedule';
|
import schedule from 'node-schedule';
|
||||||
import tools from '../components/tool.js';
|
import tools from '../components/tool.js';
|
||||||
import ConfigControl from '../lib/config/configControl.js';
|
|
||||||
|
|
||||||
export default class RssPlugin extends plugin {
|
export default class RssPlugin extends plugin {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -31,6 +30,11 @@ export default class RssPlugin extends plugin {
|
|||||||
permission: 'master',
|
permission: 'master',
|
||||||
priority: 100,
|
priority: 100,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
reg: '^#rss列表$',
|
||||||
|
fnc: 'listFeeds',
|
||||||
|
permission: 'master',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
reg: /(https?:\/\/\S+(?:\.atom|\/feed))/i,
|
reg: /(https?:\/\/\S+(?:\.atom|\/feed))/i,
|
||||||
fnc: 'autoAddFeed',
|
fnc: 'autoAddFeed',
|
||||||
@ -40,20 +44,20 @@ export default class RssPlugin extends plugin {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
if (!global.__rss_job_scheduled) {
|
if (!global.__rss_job_scheduled) {
|
||||||
if (ConfigControl.get()?.rss) {
|
// 默认每10分钟执行一次
|
||||||
schedule.scheduleJob('*/10 * * * *', () => this.pushFeeds());
|
schedule.scheduleJob('*/10 * * * *', () => this.pushFeeds());
|
||||||
global.__rss_job_scheduled = true;
|
global.__rss_job_scheduled = true;
|
||||||
}
|
logger.mark('[crystelf-rss] 定时检测任务已启动');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加rss
|
* 添加rss
|
||||||
* @param e
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
*/
|
||||||
async addFeed(e) {
|
async addFeed(e) {
|
||||||
const url = e.msg.replace(/^#rss添加/, '').trim();
|
const url = e.msg.replace(/^#rss添加/, '').trim();
|
||||||
|
if (!url) return e.reply('请输入有效的RSS链接', true);
|
||||||
|
|
||||||
const feeds = configControl.get('feeds') || [];
|
const feeds = configControl.get('feeds') || [];
|
||||||
const groupId = e.group_id;
|
const groupId = e.group_id;
|
||||||
|
|
||||||
@ -74,12 +78,9 @@ export default class RssPlugin extends plugin {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 自动添加
|
* 自动添加
|
||||||
* @param e
|
|
||||||
* @returns {Promise<*|boolean>}
|
|
||||||
*/
|
*/
|
||||||
async autoAddFeed(e) {
|
async autoAddFeed(e) {
|
||||||
//if (/^#rss/i.test(e.msg.trim())) return false;
|
if (!configControl.get()?.config?.rss) {
|
||||||
if (!ConfigControl.get()?.config?.rss) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const url = e.msg.match(/(https?:\/\/\S+(?:\.atom|\/feed))/i)?.[1];
|
const url = e.msg.match(/(https?:\/\/\S+(?:\.atom|\/feed))/i)?.[1];
|
||||||
@ -89,87 +90,151 @@ export default class RssPlugin extends plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 移除rss
|
* 查看当前群组订阅列表
|
||||||
* @param e
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
*/
|
||||||
async removeFeed(e) {
|
async listFeeds(e) {
|
||||||
const index = parseInt(e.msg.replace(/^#rss移除/, '').trim(), 10);
|
|
||||||
const feeds = configControl.get('feeds') || [];
|
const feeds = configControl.get('feeds') || [];
|
||||||
const groupId = e.group_id;
|
const groupId = e.group_id;
|
||||||
|
const currentGroupFeeds = feeds
|
||||||
|
.map((feed, index) => ({ index, ...feed }))
|
||||||
|
.filter((feed) => feed.targetGroups.includes(groupId));
|
||||||
|
|
||||||
if (index < 0 || index >= feeds.length) return e.reply('索引无效..', true);
|
if (currentGroupFeeds.length === 0) {
|
||||||
|
return e.reply('当前群组暂无任何RSS订阅..', true);
|
||||||
|
}
|
||||||
|
|
||||||
feeds[index].targetGroups = feeds[index].targetGroups.filter((id) => id !== groupId);
|
const msg = [
|
||||||
await configControl.set('feeds', feeds);
|
`当前群组订阅列表 (${currentGroupFeeds.length})`,
|
||||||
return await e.reply('群已移除该订阅');
|
...currentGroupFeeds.map((f) => `[${f.index}] ${f.url}`),
|
||||||
|
'----------------',
|
||||||
|
'提示: 使用 #rss移除+索引号 取消订阅'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
return e.reply(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 手动拉取
|
* 移除rss
|
||||||
* @param e
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
*/
|
||||||
|
async removeFeed(e) {
|
||||||
|
const match = e.msg.match(/#rss移除\s*(\d+)/);
|
||||||
|
if (!match || !match[1]) {
|
||||||
|
return e.reply('请指定要移除的订阅索引,例如:#rss移除0', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = parseInt(match[1], 10);
|
||||||
|
const feeds = configControl.get('feeds') || [];
|
||||||
|
const groupId = e.group_id;
|
||||||
|
if (isNaN(index) || index < 0 || index >= feeds.length) {
|
||||||
|
return e.reply('索引无效,请发送 #rss列表 查看正确索引..', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetFeed = feeds[index];
|
||||||
|
if (!targetFeed) return e.reply('未找到该配置..', true);
|
||||||
|
if (!Array.isArray(targetFeed.targetGroups)) {
|
||||||
|
targetFeed.targetGroups = [];
|
||||||
|
}
|
||||||
|
if (!targetFeed.targetGroups.includes(groupId)) {
|
||||||
|
return e.reply('当前群组未订阅此源,无需移除..', true);
|
||||||
|
}
|
||||||
|
targetFeed.targetGroups = targetFeed.targetGroups.filter((id) => id !== groupId);
|
||||||
|
await configControl.set('feeds', feeds);
|
||||||
|
|
||||||
|
return await e.reply(`已取消订阅:${targetFeed.title || targetFeed.url}`);
|
||||||
|
}
|
||||||
|
|
||||||
async pullFeedNow(e) {
|
async pullFeedNow(e) {
|
||||||
const url = e.msg.replace(/^#rss拉取/, '').trim();
|
const url = e.msg.replace(/^#rss拉取/, '').trim();
|
||||||
const latest = await rssTools.fetchFeed(url);
|
if (!url) return e.reply('请提供RSS链接', true);
|
||||||
//logger.info(latest);
|
|
||||||
|
let latest;
|
||||||
|
try {
|
||||||
|
latest = await rssTools.fetchFeed(url);
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(`[crystelf-rss] 手动拉取失败: ${err.message}`);
|
||||||
|
return await e.reply(`拉取失败: ${err.message}`, true);
|
||||||
|
}
|
||||||
|
|
||||||
if (!latest || !latest.length) {
|
if (!latest || !latest.length) {
|
||||||
return await e.reply('拉取失败或无内容..', true);
|
return await e.reply('拉取成功但无内容..', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const post = latest[0];
|
const post = latest[0];
|
||||||
//console.log(post);
|
|
||||||
const tempPath = path.join(process.cwd(), 'data', `rss-test-${Date.now()}.png`);
|
const tempPath = path.join(process.cwd(), 'data', `rss-test-${Date.now()}.png`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await e.reply(`最新文章:${post.title}\n正在生成预览...`);
|
||||||
await screenshot.generateScreenshot(post, tempPath);
|
await screenshot.generateScreenshot(post, tempPath);
|
||||||
await e.reply([segment.image(tempPath)]);
|
await e.reply([segment.image(tempPath)]);
|
||||||
fs.unlinkSync(tempPath);
|
} catch (err) {
|
||||||
|
logger.error(`[crystelf-rss] 截图失败: ${err}`);
|
||||||
|
await e.reply('生成预览图失败..', true);
|
||||||
|
} finally {
|
||||||
|
if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查rss更新
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
async pushFeeds() {
|
async pushFeeds() {
|
||||||
const feeds = configControl.get('feeds') || [];
|
const feeds = configControl.get('feeds') || [];
|
||||||
logger.mark(`正在检查rss流更新..`);
|
|
||||||
|
|
||||||
for (const feed of feeds) {
|
for (const feed of feeds) {
|
||||||
const latest = await rssTools.fetchFeed(feed.url);
|
let latest;
|
||||||
|
try {
|
||||||
|
latest = await rssTools.fetchFeed(feed.url);
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn(`[RSS] 自动检查 ${feed.url} 失败: ${error.message},跳过`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!latest || !latest.length) continue;
|
if (!latest || !latest.length) continue;
|
||||||
const todayStr = new Date().toISOString().split('T')[0];
|
|
||||||
const newItems = [];
|
const newItems = [];
|
||||||
for (const item of latest) {
|
const checkLimit = Math.min(latest.length, 3);
|
||||||
const pubDate = item.date;
|
|
||||||
if (!pubDate) continue;
|
for (let i = 0; i < checkLimit; i++) {
|
||||||
const itemDate = new Date(pubDate).toISOString().split('T')[0];
|
const item = latest[i];
|
||||||
if (itemDate !== todayStr) continue;
|
if (!item.link) continue;
|
||||||
if (!(await rssCache.has(feed.url, item.link))) {
|
const isCached = await rssCache.has(feed.url, item.link);
|
||||||
|
|
||||||
|
if (!isCached) {
|
||||||
|
const pubDate = item.date ? new Date(item.date).getTime() : Date.now();
|
||||||
|
if (!item.date || (Date.now() - pubDate < 172800000)) {
|
||||||
newItems.push(item);
|
newItems.push(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (newItems.length) {
|
}
|
||||||
await rssCache.set(feed.url, newItems[0].link);
|
|
||||||
|
if (newItems.length > 0) {
|
||||||
|
newItems.reverse();
|
||||||
|
for (const post of newItems) {
|
||||||
|
// 写入缓存
|
||||||
|
await rssCache.set(feed.url, post.link);
|
||||||
|
|
||||||
for (const groupId of feed.targetGroups) {
|
for (const groupId of feed.targetGroups) {
|
||||||
const post = newItems[0];
|
await tools.sleep(2000);
|
||||||
|
|
||||||
const tempPath = path.join(process.cwd(), 'data', `rss-${Date.now()}.png`);
|
const tempPath = path.join(process.cwd(), 'data', `rss-${Date.now()}.png`);
|
||||||
|
try {
|
||||||
if (feed.screenshot) {
|
if (feed.screenshot) {
|
||||||
|
logger.info(`[crystelf-rss] 推送更新: ${post.title} -> 群 ${groupId}`);
|
||||||
|
// 先发个文字提示
|
||||||
await Bot.pickGroup(groupId)?.sendMsg(
|
await Bot.pickGroup(groupId)?.sendMsg(
|
||||||
`${configControl.get('profile')?.nickName}发现了一条新的rss推送!`
|
`[RSS] ${post.feedTitle || '订阅更新'}\n${post.title}`
|
||||||
);
|
|
||||||
await tools.sleep(1000);
|
|
||||||
//await Bot.pickGroup(groupId)?.sendMsg(`让${configControl.get('nickName')}看看内容是什么..`);
|
|
||||||
// TODO 通过人工智能查看内容
|
|
||||||
await Bot.pickGroup(groupId)?.sendMsg(
|
|
||||||
`[标题] ${post.title}\n[作者] ${post.author}\n[来源] ${post.feedTitle}\n正在努力截图..`
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 再发图片
|
||||||
await screenshot.generateScreenshot(post, tempPath);
|
await screenshot.generateScreenshot(post, tempPath);
|
||||||
await Bot.pickGroup(groupId)?.sendMsg([segment.image(tempPath)]);
|
await Bot.pickGroup(groupId)?.sendMsg([segment.image(tempPath)]);
|
||||||
fs.unlinkSync(tempPath);
|
|
||||||
} else {
|
} else {
|
||||||
await Bot.pickGroup(groupId)?.sendMsg(`[RSS推送]\n${post.title}\n${post.link}`);
|
await Bot.pickGroup(groupId)?.sendMsg(`[RSS推送]\n${post.title}\n${post.link}`);
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(`[crystelf-rss] 推送消息异常: ${err.message}`);
|
||||||
|
} finally {
|
||||||
|
if (fs.existsSync(tempPath)) {
|
||||||
|
fs.unlinkSync(tempPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user