diff --git a/plugins/genshin/apps/mysNews.js b/plugins/genshin/apps/mysNews.js index 654706d..8ca4cca 100644 --- a/plugins/genshin/apps/mysNews.js +++ b/plugins/genshin/apps/mysNews.js @@ -1,5 +1,6 @@ import plugin from '../../../lib/plugins/plugin.js' import MysNews from '../model/mysNews.js' +import srNews from '../model/srmysNews.js' import fs from 'node:fs' import lodash from 'lodash' import gsCfg from '../model/gsCfg.js' @@ -22,6 +23,19 @@ export class mysNews extends plugin { reg: '^(#米游社|#mys)(.*)', fnc: 'mysSearch' }, + { + reg: '^(#*铁道(公告|资讯|活动)|#*星铁(公告|资讯|活动)|#星穹公告|#星穹资讯|#星穹活动)[0-9]*$', + fnc: 'srnews' + }, + { + reg: '^#*(开启|关闭)(铁道|星铁|星穹)(公告|资讯)推送$', + fnc: 'srsetPush' + }, + { + reg: '^#推送(铁道|星铁|星穹)(公告|资讯)$', + permission: 'master', + fnc: 'srmysNewsTask' + }, { reg: '(.*)(bbs.mihoyo.com|miyoushe.com)/ys(.*)/article(.*)', fnc: 'mysUrl' @@ -66,8 +80,19 @@ export class mysNews extends plugin { await this.reply(data) } + async srnews () { + let data = await new srNews(this.e).getNews() + if (!data) return + await this.reply(data) + } + async mysNewsTask () { - let mysNews = new MysNews(this.e) + let mysNews = new srNews(this.e) + await mysNews.mysNewsTask() + } + + async srmysNewsTask () { + let mysNews = new srNews(this.e) await mysNews.mysNewsTask() } @@ -90,6 +115,45 @@ export class mysNews extends plugin { await this.reply(data) } + async srsetPush () { + if (!this.e.isGroup) { + await this.reply('推送请在群聊中设置') + return + } + if (!this.e.member?.is_admin && !this.e.isMaster) { + await this.reply('暂无权限,只有管理员才能操作', true) + return true + } + + let cfg = gsCfg.getConfig('mys', 'pushNews') + + let type = 'srannounceGroup' + let typeName = '公告' + if (this.e.msg.includes('资讯')) { + type = 'srinfoGroup' + typeName = '资讯' + } + + let model + let msg = `崩坏星穹铁道${typeName}推送已` + if (this.e.msg.includes('开启')) { + model = '开启' + cfg[type].push(this.e.group_id) + cfg[type] = lodash.uniq(cfg[type]) + msg += `${model}\n如有最新${typeName}将自动推送至此` + } else { + model = '关闭' + msg += `${model}` + cfg[type] = lodash.difference(cfg[type], [this.e.group_id]) + } + + let yaml = YAML.stringify(cfg) + fs.writeFileSync(this.file, yaml, 'utf8') + + logger.mark(`${this.e.logFnc} ${model}${typeName}推送:${this.e.group_id}`) + await this.reply(msg) + } + async setPush () { if (!this.e.isGroup) { await this.reply('推送请在群聊中设置') @@ -128,4 +192,4 @@ export class mysNews extends plugin { logger.mark(`${this.e.logFnc} ${model}${typeName}推送:${this.e.group_id}`) await this.reply(msg) } -} +} \ No newline at end of file diff --git a/plugins/genshin/defSet/mys/pushNews.yaml b/plugins/genshin/defSet/mys/pushNews.yaml index bc54376..6cca109 100644 --- a/plugins/genshin/defSet/mys/pushNews.yaml +++ b/plugins/genshin/defSet/mys/pushNews.yaml @@ -9,4 +9,10 @@ maxNum: 1 announceGroup: [] #资讯推送群 -infoGroup: [] \ No newline at end of file +infoGroup: [] + +#崩坏星穹铁道公告推送群 +srannounceGroup: [] + +#崩坏星穹铁道资讯推送群 +srinfoGroup: [] \ No newline at end of file diff --git a/plugins/genshin/model/srmysNews.js b/plugins/genshin/model/srmysNews.js new file mode 100644 index 0000000..934e91b --- /dev/null +++ b/plugins/genshin/model/srmysNews.js @@ -0,0 +1,347 @@ +import base from './base.js' +import fetch from 'node-fetch' +import lodash from 'lodash' +import puppeteer from '../../../lib/puppeteer/puppeteer.js' +import common from '../../../lib/common/common.js' +import gsCfg from '../model/gsCfg.js' + +const _path = process.cwd() +let emoticon + +export default class MysNews extends base { + constructor (e) { + super(e) + this.model = 'mysNews' + } + + async getNews () { + let type = 1 + let typeName = '公告' + if (this.e.msg.includes('资讯')) { + type = '3' + typeName = '资讯' + } + if (this.e.msg.includes('活动')) { + type = '2' + typeName = '活动' + } + + const res = await this.postData('getNewsList', { gids: 6, page_size: 20, type }) + if (!res) return + + const data = res.data.list + if (data.length == 0) { + return true + } + + const page = this.e.msg.replace(/#|#|星铁|星穹|铁道|公告|资讯|活动/g, '').trim() || 1 + if (page > data.length) { + await this.e.reply('目前只查前20条最新的公告,请输入1-20之间的整数。') + return true + } + + const postId = data[page - 1].post.post_id + + const param = await this.newsDetail(postId) + + const img = await this.rander(param) + + return await this.replyMsg(img, `崩坏星穹铁道${typeName}:${param.data.post.subject}`) + } + + async rander (param) { + const pageHeight = 7000 + + await puppeteer.browserInit() + + if (!puppeteer.browser) return false + + const savePath = puppeteer.dealTpl('mysNews', param) + if (!savePath) return false + + const page = await puppeteer.browser.newPage() + try { + await page.goto(`file://${_path}${lodash.trim(savePath, '.')}`, { timeout: 120000 }) + const body = await page.$('#container') || await page.$('body') + const boundingBox = await body.boundingBox() + + const num = Math.round(boundingBox.height / pageHeight) || 1 + + if (num > 1) { + await page.setViewport({ + width: boundingBox.width, + height: pageHeight + 100 + }) + } + + const img = [] + for (let i = 1; i <= num; i++) { + const randData = { + type: 'jpeg', + quality: 90 + } + + if (i != 1 && i == num) { + await page.setViewport({ + width: boundingBox.width, + height: parseInt(boundingBox.height) - pageHeight * (num - 1) + }) + } + + if (i != 1 && i <= num) { + await page.evaluate(() => window.scrollBy(0, 7000)) + } + + let buff + if (num == 1) { + buff = await body.screenshot(randData) + } else { + buff = await page.screenshot(randData) + } + + if (num > 2) await common.sleep(200) + + puppeteer.renderNum++ + /** 计算图片大小 */ + const kb = (buff.length / 1024).toFixed(2) + 'kb' + + logger.mark(`[图片生成][${this.model}][${puppeteer.renderNum}次] ${kb}`) + + img.push(segment.image(buff)) + } + + await page.close().catch((err) => logger.error(err)) + + if (num > 1) { + logger.mark(`[图片生成][${this.model}] 处理完成`) + } + return img + } catch (error) { + logger.error(`图片生成失败:${this.model}:${error}`) + /** 关闭浏览器 */ + if (puppeteer.browser) { + await puppeteer.browser.close().catch((err) => logger.error(err)) + } + puppeteer.browser = false + } + } + + async newsDetail (postId) { + const res = await this.postData('getPostFull', { gids: 6, read: 1, post_id: postId }) + if (!res) return + + const data = await this.detalData(res.data.post) + + return { + ...this.screenData, + saveId: postId, + dataConent: data.post.content, + data + } + } + + postApi (type, data) { + let host = 'https://bbs-api-static.mihoyo.com/' + let param = [] + lodash.forEach(data, (v, i) => param.push(`${i}=${v}`)) + param = param.join('&') + switch (type) { + // 搜索 + case 'searchPosts': + host = 'https://bbs-api.mihoyo.com/post/wapi/searchPosts?' + break + // 帖子详情 + case 'getPostFull': + host += 'post/wapi/getPostFull?' + break + // 公告列表 + case 'getNewsList': + host += 'post/wapi/getNewsList?' + break + case 'emoticon': + host += 'misc/api/emoticon_set?' + break + } + return host + param + } + + async postData (type, data) { + const url = this.postApi(type, data) + const headers = { + Referer: 'https://bbs.mihoyo.com/', + 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36' + } + let response + try { + response = await fetch(url, { method: 'get', headers }) + } catch (error) { + logger.error(error.toString()) + return false + } + + if (!response.ok) { + logger.error(`[米游社接口错误][${type}] ${response.status} ${response.statusText}`) + return false + } + const res = await response.json() + return res + } + + async detalData (data) { + let json + try { + json = JSON.parse(data.post.content) + } catch (error) { + + } + + if (typeof json == 'object') { + if (json.imgs && json.imgs.length > 0) { + for (const val of json.imgs) { + data.post.content = `
` + } + } + } else { + for (const img of data.post.images) { + data.post.content = data.post.content.replace(img, img + '?x-oss-process=image//resize,s_600/quality,q_80/auto-orient,0/interlace,1/format,jpg') + } + + if (!emoticon) { + emoticon = await this.mysEmoticon() + } + + data.post.content = data.post.content.replace(/_\([^)]*\)/g, function (t, e) { + t = t.replace(/_\(|\)/g, '') + if (emoticon.has(t)) { + return `` + } else { + return '' + } + }) + + const arrEntities = { lt: '<', gt: '>', nbsp: ' ', amp: '&', quot: '"' } + data.post.content = data.post.content.replace(/&(lt|gt|nbsp|amp|quot);/ig, function (all, t) { + return arrEntities[t] + }) + } + + data.post.created_time = new Date(data.post.created_at * 1000).toLocaleString() + + for (const i in data.stat) { + data.stat[i] = data.stat[i] > 10000 ? (data.stat[i] / 10000).toFixed(2) + '万' : data.stat[i] + } + + return data + } + + async mysEmoticon () { + const emp = new Map() + + const res = await this.postData('emoticon', { gids: 6 }) + + if (res.retcode != 0) { + return emp + } + + for (const val of res.data.list) { + if (!val.icon) continue + for (const list of val.list) { + if (!list.icon) continue + emp.set(list.name, list.icon) + } + } + + return emp + } + + async replyMsg (img, titile) { + if (!img || img.length <= 0) return false + if (img.length == 1) { + return img[0] + } else { + let msg = [titile, ...img] + return await common.makeForwardMsg(this.e, msg, titile).catch((err) => { + logger.error(err) + }) + } + } + + async mysNewsTask (type = 1) { + let cfg = gsCfg.getConfig('mys', 'pushNews') + + // 推送2小时内的公告资讯 + let interval = 7200 + // 最多同时推送两条 + this.maxNum = cfg.maxNum + // 包含关键字不推送 + let banWord = /冒险助力礼包|纪行|预下载|脚本外挂|集中反馈|已开奖|云·原神|魔神任务|传说任务说明/g + + let anno = await this.postData('getNewsList', { gids: 6, page_size: 10, type: 1 }) + let info = await this.postData('getNewsList', { gids: 6, page_size: 10, type: 3 }) + + let news = [] + if (anno) anno.data.list.forEach(v => { news.push({ ...v, typeName: '公告', post_id: v.post.post_id }) }) + if (info) info.data.list.forEach(v => { news.push({ ...v, typeName: '资讯', post_id: v.post.post_id }) }) + if (news.length <= 0) return + + news = lodash.orderBy(news, ['post_id'], ['asc']) + + let now = Date.now() / 1000 + + this.key = 'Yz:genshin:mys:newPush:' + this.e.isGroup = true + this.pushGroup = [] + for (let val of news) { + if (Number(now - val.post.created_at) > interval) { + continue + } + if (new RegExp(banWord).test(val.post.subject)) { + continue + } + if (val.typeName == '公告') { + for (let groupId of cfg.srannounceGroup) { + await this.sendNews(groupId, val.typeName, val.post.post_id) + } + } + if (val.typeName == '资讯') { + for (let groupId of cfg.srinfoGroup) { + await this.sendNews(groupId, val.typeName, val.post.post_id) + } + } + } + } + + async sendNews (groupId, typeName, postId) { + if (!this.pushGroup[groupId]) this.pushGroup[groupId] = 0 + if (this.pushGroup[groupId] >= this.maxNum) return + + let sended = await redis.get(`${this.key}${groupId}:${postId}`) + if (sended) return + + if (!this[postId]) { + const param = await this.newsDetail(postId) + + logger.mark(`[崩坏星穹铁道${typeName}推送] ${param.data.post.subject}`) + + this[postId] = { + img: await this.rander(param), + title: param.data.post.subject + } + } + + this.pushGroup[groupId]++ + this.e.group = Bot.pickGroup(Number(groupId)) + this.e.group_id = Number(groupId) + let tmp = await this.replyMsg(this[postId].img, `崩坏星穹铁道${typeName}推送:${this[postId].title}`) + + await common.sleep(1000) + if (!tmp) return + + if (tmp?.type != 'xml') { + tmp = [`崩坏星穹铁道${typeName}推送\n`, tmp] + } + + redis.set(`${this.key}${groupId}:${postId}`, '1', { EX: 3600 * 10 }) + await this.e.group.sendMsg(tmp) + } +}