el-bot
Version:
A quick qq bot framework for mirai.
229 lines (202 loc) • 6.19 kB
text/typescript
import type { CustomFields } from 'rss-parser'
import consola from 'consola'
import dayjs from 'dayjs'
import { type Bot, defineBotPlugin } from 'el-bot'
import fs from 'fs-extra'
import { htmlToText } from 'html-to-text'
import schedule from 'node-schedule'
import Parser from 'rss-parser'
interface RssConfig {
name: string
url: string
cron: string
customFields: CustomFields<string[], string[]>
content: string[]
target: object
}
class Rss {
config: RssConfig
parser: Parser
constructor(public ctx: Bot, rssConfig: RssConfig) {
const defaultConfig = {
cron: '*/15 * * * *',
customFields: {
item: ['updated'],
},
content: [
// eslint-disable-next-line no-template-curly-in-string
'标题:${item.title}',
// eslint-disable-next-line no-template-curly-in-string
'链接:${item.link}',
// eslint-disable-next-line no-template-curly-in-string
'时间:${item.updated}',
],
}
rssConfig = Object.assign(defaultConfig, rssConfig)
this.config = rssConfig
this.parser = new Parser({
customFields: rssConfig.customFields,
})
}
init() {
schedule.scheduleJob(this.config.cron, () => {
this.parse()
})
}
async parse() {
let feed: Parser.Output<any>
try {
feed = await this.parser.parseURL(this.config.url)
}
catch {
this.ctx.logger.error(
`[rss] ${this.config.name} 超时,${this.config.url} 解析失败`,
)
return
}
if (feed.items && this.save(feed)) {
// only semd first
const content = feed.title + format(feed.items[0], this.config.content)
this.ctx.sender.sendMessageByConfig(content, this.config.target)
}
}
save(feed: any) {
if (feed.items && feed.items.length <= 0)
return false
const path = `${this.ctx.tmpDir}rss.json`
let rssJson: any = {}
if (fs.existsSync(path)) {
const data = fs.readFileSync(path)
rssJson = JSON.parse(data.toString())
}
else {
fs.mkdirSync(this.ctx.tmpDir, { recursive: true })
}
// 缓存文件不存在 或 对应对象不存在则更新
if (
Object.keys(rssJson).length === 0
|| !rssJson[this.config.name]
|| (feed.items
&& rssJson[this.config.name].items[0].pubDate !== feed.items[0].pubDate)
) {
this.ctx.logger.info(`[rss] ${feed.title} 已更新`)
rssJson[this.config.name] = {
title: feed.title,
lastBuildDate: feed.lastBuildDate,
items: [
{
title: (feed.items as any)[0].title,
pubDate: (feed.items as any)[0].pubDate,
},
],
}
fs.writeFileSync(path, JSON.stringify(rssJson))
this.ctx.logger.success(`[rss] 已在本地记录 ${feed.title} 新的 RSS 信息`)
return true
}
else {
this.ctx.logger.info(`[rss] ${feed.title} 未更新`)
return false
}
}
}
function format(item: any, content: string[]) {
item.updated = dayjs(item.updated).format('YYYY-MM-DD HH:mm:ss')
item.pubDate = dayjs(item.pubDate).format('YYYY-MM-DD HH:mm:ss')
if (item.summary)
item.summary = htmlToText(item.summary)
if (item.content)
item.content = htmlToText(item.content)
if (item.description)
item.description = htmlToText(item.description)
let template = ''
// default template
if (!content) {
content = [
// eslint-disable-next-line no-template-curly-in-string
'标题:${item.title}',
// eslint-disable-next-line no-template-curly-in-string
'链接:${item.link}',
// eslint-disable-next-line no-template-curly-in-string
'时间:${item.updated}',
]
}
content.forEach((line) => {
template += `\n${line}`
})
// not use eval
// eslint-disable-next-line no-new-func
return new Function('item', `return \`${template}\``)(item)
}
/**
* 立即触发 RSS 抓取
* @param ctx
* @param options
*/
export function triggerRss(ctx: Bot, options: RssConfig[]) {
ctx.logger.success('[rss] 立即触发 RSS 抓取')
let content = '您当前订阅的所有 RSS 源:'
options.forEach((rssConfig: RssConfig) => {
content += `\n${rssConfig.name}: ${rssConfig.url}`
const rss = new Rss(ctx, rssConfig)
rss.parse()
})
return content
}
export type RssOptions = RssConfig[]
export default defineBotPlugin<RssOptions>((options) => {
return {
extendCli(cli) {
cli
.command('rss', 'RSS 订阅', (args) => {
return args
.option('trigger', {
alias: 't',
type: 'boolean',
describe: '立即触发 RSS 抓取',
})
.option('list', {
alias: 'l',
type: 'string',
describe: '订阅列表',
})
}, ({ trigger, list }) => {
consola.info(trigger, list)
// const content = triggerRss(ctx, rssOptions)
// const msg = mirai.curMsg as MessageType.GroupMessage
// if (cmdOptions.list === 'current' && msg.sender.group) {
// // 列出当前群 rss 订阅
// let rssList = ''
// let count = 0
// options.forEach((rssConfig: RssConfig) => {
// if (ctx.status.isListening(msg.sender, rssConfig.target)) {
// count += 1
// rssList += `\n${rssConfig.name}: ${rssConfig.url}`
// }
// })
// if (count !== 0)
// msg.reply(`本群共订阅了 ${count} 个 RSS 源:${rssList}`)
// else
// msg.reply('本群尚未订阅 RSS')
// }
// else if (cmdOptions.list === 'all') {
// // 列出所有 rss 订阅
// if (ctx.user.isAllowed(msg.sender.id, true))
// msg.reply(content)
// }
})
},
setup(ctx) {
// const { cli, napcat } = ctx
const rssOptions = options
consola.log(rssOptions)
// 初始化定时
if (options) {
options.forEach((rssConfig: RssConfig) => {
const rss = new Rss(ctx, rssConfig)
rss.init()
})
}
},
}
})