Miao-Yunzai/plugins/genshin/model/mysNews.js

515 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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'
import YAML from 'yaml'
import fs from 'fs'
let emoticon
export default class MysNews extends base {
constructor(e) {
super(e)
this.model = 'mysNews'
}
async getNews(gid) {
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: gid, page_size: this.e.msg.includes('列表') ? 5 : 20, type })
if (!res) return
const data = res.data.list
if (data.length == 0) {
return true
}
let param = {}
let game = this.game(gid)
if (this.e.msg.includes('列表')) {
this.model = 'mysNews-list'
data.forEach(element => {
element.post.created_at = new Date(element.post.created_at * 1000).toLocaleString()
})
param = {
...this.screenData,
saveId: this.e.user_id,
data,
game,
typeName
}
} else {
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
param = await this.newsDetail(postId, gid)
}
const img = await this.render(param)
return this.replyMsg(img, `${game}${typeName}${param?.data?.post?.subject || `米游社${game}${typeName}列表`}`)
}
render(param) {
return puppeteer.screenshots(this.model, param)
}
async newsDetail(postId, gid) {
const res = await this.postData('getPostFull', { gids: gid, read: 1, post_id: postId })
if (!res) return
const data = await this.detalData(res.data.post, gid)
return {
...this.screenData,
saveId: postId,
dataConent: data.post.content,
data
}
}
postApi(type, data) {
let host = 'https://bbs-api.miyoushe.com/'
let param = []
lodash.forEach(data, (v, i) => param.push(`${i}=${v}`))
param = param.join('&')
switch (type) {
// 搜索
case 'searchPosts':
host = 'https://bbs-api.miyoushe.com/post/wapi/searchPosts?'
break
case 'userInstantSearchPosts':
host = 'https://bbs-api.miyoushe.com/painter/api/user_instant/search/list?'
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://www.miyoushe.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, gid) {
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 = ` <div class="ql-image-box"><img src="${val}?x-oss-process=image//resize,s_600/quality,q_80/auto-orient,0/interlace,1/format,png"></div>`
}
}
} 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(gid)
}
data.post.content = data.post.content.replace(/_\([^)]*\)/g, function (t, e) {
t = t.replace(/_\(|\)/g, '')
if (emoticon.has(t)) {
return `<img class="emoticon-image" src="${emoticon.get(t)}"/>`
} 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(gid) {
const emp = new Map()
const res = await this.postData('emoticon', { gids: gid })
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 mysSearch() {
let msg = this.e.msg
msg = msg.replace(/#|米游社|mys/g, '')
if (!msg) {
await this.e.reply('请输入关键字,如#米游社七七')
return false
}
let page = msg.match(/.*(\d){1}$/) || 0
if (page && page[1]) {
page = page[1]
}
msg = lodash.trim(msg, page)
let res = await this.postData('searchPosts', { gids: 2, size: 20, keyword: msg })
if (!res) return
if (res?.data?.posts.length <= 0) {
await this.e.reply('搜索不到您要的结果,换个关键词试试呗~')
return false
}
let postId = res.data.posts[page].post.post_id
const param = await this.newsDetail(postId)
const img = await this.render(param)
return this.replyMsg(img, `${param.data.post.subject}`)
}
async mysUrl() {
let msg = this.e.msg
let postId = /[0-9]+/g.exec(msg)[0]
if (!postId) return false
const param = await this.newsDetail(postId)
const img = await this.render(param)
return this.replyMsg(img, `${param.data.post.subject}`)
}
async mysEstimate(keyword, uid) {
let res = await this.postData('userInstantSearchPosts', { keyword, uid, size: 20, offset: 0, sort_type: 2 })
let postList = res?.data?.list
if (postList.length <= 0) {
await this.e.reply('暂无数据')
return false
}
let postId = postList[0].post.post.post_id
if (!postId) {
await this.e.reply('暂无数据')
return false
}
const param = await this.newsDetail(postId)
const img = await this.render(param)
if (img.length > 1) {
img.push(segment.image(param.data.post.images[0] + '?x-oss-process=image//resize,s_600/quality,q_80/auto-orient,0/interlace,1/format,jpg'))
}
return this.replyMsg(img, `${param.data.post.subject}`)
}
replyMsg(img, title) {
if (!img || img.length <= 0) return false
if (title) img = [title, ...img]
if (img.length <= 2) return img
return common.makeForwardMsg(this.e, [img])
}
async mysNewsTask() {
let cfg = gsCfg.getConfig('mys', 'pushNews')
// 推送2小时内的公告资讯
let interval = 7200
// 最多同时推送两条
this.maxNum = cfg.maxNum
for (let gid of [1, 2, 3, 4, 6, 8]) {
let type = gid == 1 ? 'bbb' : gid == 2 ? 'gs' : gid == 3 ? 'bb' : gid == 4 ? 'wd' : gid == 6 ? 'sr' : 'zzz'
let news = []
if (!lodash.isEmpty(cfg[`${type}announceGroup`])) {
let anno = await this.postData('getNewsList', { gids: gid, page_size: 10, type: 1 })
if (anno) anno.data.list.forEach(v => { news.push({ ...v, typeName: '公告', post_id: v.post.post_id }) })
}
if (!lodash.isEmpty(cfg[`${type}infoGroup`])) {
let info = await this.postData('getNewsList', { gids: gid, page_size: 10, type: 3 })
if (info) info.data.list.forEach(v => { news.push({ ...v, typeName: '资讯', post_id: v.post.post_id }) })
}
if (news.length <= 0) continue
news = lodash.orderBy(news, ['post_id'], ['asc'])
let now = Date.now() / 1000
this.key = `Yz:${type}:mys:newPush:`
this.e.isGroup = true
this.pushGroup = []
for (let val of news) {
if (Number(now - val.post.created_at) > interval)
continue
if (cfg.banWord[type] && new RegExp(cfg.banWord[type]).test(val.post.subject))
continue
if (val.typeName == '公告')
for (let botId in cfg[`${type}announceGroup`])
for (let groupId of cfg[`${type}announceGroup`][botId])
await this.sendNews(botId, groupId, val.typeName, val.post.post_id, gid)
if (val.typeName == '资讯')
for (let botId in cfg[`${type}infoGroup`])
for (let groupId of cfg[`${type}infoGroup`][botId])
await this.sendNews(botId, groupId, val.typeName, val.post.post_id, gid)
}
}
}
async ActivityPush() {
let now = new Date()
now = now.getHours();
if(now < 10) return
let pushGroupList
try {
pushGroupList = YAML.parse(fs.readFileSync(`./plugins/genshin/config/mys.pushNews.yaml`, `utf8`))
} catch (error) {
logger.error(`[米游社活动到期推送] 活动到期预警推送失败:无法获取配置文件信息\n${error}`)
return
}
if((!pushGroupList.gsActivityPush || pushGroupList.gsActivityPush == {}) && (!pushGroupList.srActivityPush || pushGroupList.srActivityPush == {})) return
let BotidList = []
let ActivityPushYaml = {...pushGroupList.gsActivityPush, ...pushGroupList.srActivityPush}
for (let item in ActivityPushYaml) {
BotidList.push(item)
}
let gsActivityList = await this.getGsActivity()
let srActivityList = await this.getSrActivity()
let ActivityList = []
for (let item of srActivityList) {
ActivityList.push({ game: `sr`, subtitle: item.title, banner: item.img, title: item.title, end_time: item.end_time })
}
for (let item of gsActivityList) {
ActivityList.push({ game: 'gs', subtitle: item.subtitle, banner: item.banner, title: item.title, end_time: item.end_time})
}
if(ActivityList.length === 0) return
for (let item of BotidList) {
let redisapgl = await redis.get(`Yz:apgl:${item}`)
let date = await this.getDate()
redisapgl = JSON.parse(redisapgl)
if(!redisapgl || redisapgl.date !== date) {
redisapgl = {
date,
GroupList: ActivityPushYaml[item]
}
}
if(!Array.isArray(redisapgl.GroupList) || redisapgl.GroupList.length == 0) continue
if(!Bot[item]) {
redisapgl.GroupList.shift()
await redis.set(`Yz:apgl:${item}`, JSON.stringify(redisapgl))
continue
}
for (let a of ActivityList) {
if((!pushGroupList.srActivityPush || !pushGroupList.srActivityPush[item] || !pushGroupList.srActivityPush[item].includes(redisapgl.GroupList[0])) && a.game === `sr`) continue
if((!pushGroupList.gsActivityPush || !pushGroupList.gsActivityPush[item] || !pushGroupList.gsActivityPush[item].includes(redisapgl.GroupList[0])) && a.game === `gs`) continue
let pushGame
if(a.game === `sr`) pushGame = `星铁`
if(a.game === `gs`) pushGame = `原神`
let endDt = a.end_time
endDt = endDt.replace(/\s/, `T`)
let todayt = new Date()
endDt = new Date(endDt)
let sydate = await this.calculateRemainingTime(todayt, endDt)
let msgList = [
`${pushGame}活动即将结束通知】`,
`\n活动:${a.subtitle}`,
segment.image(a.banner),
`描述:${a.title}`,
`\n活动剩余时间:${sydate.days}${sydate.hours}小时${sydate.minutes}分钟${sydate.seconds}`,
`\n活动结束时间:${a.end_time}`
]
logger.mark(`[米游社活动到期推送] 开始推送 ${item}:${redisapgl.GroupList[0]} ${a.subtitle}`)
await common.sleep(5000)
Bot[item].pickGroup(redisapgl.GroupList[0]).sendMsg(msgList)
.then(() => {}).catch((err) => logger.error(`[米游社活动到期推送] ${item}:${redisapgl.GroupList[0]} 推送失败,错误信息${err}`))
}
redisapgl.GroupList.shift()
await redis.set(`Yz:apgl:${item}`, JSON.stringify(redisapgl))
}
return
}
async getDate() {
const currentDate = new Date();
const year = currentDate.getFullYear();
const month = (currentDate.getMonth() + 1).toString().padStart(2, '0');
const day = currentDate.getDate().toString().padStart(2, '0');
return `${year}-${month}-${day}`
}
async getGsActivity() {
let gshd
try {
gshd = await fetch(`https://hk4e-api.mihoyo.com/common/hk4e_cn/announcement/api/getAnnList?game=hk4e&game_biz=hk4e_cn&lang=zh-cn&bundle_id=hk4e_cn&platform=pc&region=cn_gf01&level=55&uid=100000000`)
gshd = await gshd.json()
} catch {
return []
}
let hdlist = []
let result = []
for (let item of gshd.data.list[1].list) {
if(item.tag_label.includes(`活动`) && !item.title.includes(`传说任务`) && !item.title.includes(`游戏公告`)) hdlist.push(item)
}
for (let item of hdlist) {
let endDt = item.end_time
endDt = endDt.replace(/\s/, `T`)
let todayt = new Date()
endDt = new Date(endDt)
let sydate = await this.calculateRemainingTime(todayt, endDt)
if(sydate.days <= 1) result.push(item)
}
return result
}
async getSrActivity() {
let srhd
try {
srhd = await fetch(`https://hkrpg-api.mihoyo.com/common/hkrpg_cn/announcement/api/getAnnList?game=hkrpg&game_biz=hkrpg_cn&lang=zh-cn&auth_appid=announcement&authkey_ver=1&bundle_id=hkrpg_cn&channel_id=1&level=65&platform=pc&region=prod_gf_cn&sdk_presentation_style=fullscreen&sdk_screen_transparent=true&sign_type=2&uid=100000000`)
srhd = await srhd.json()
} catch {
return []
}
let hdlist = []
let result = []
for (let item of srhd.data.pic_list[0].type_list[0].list) {
if (item.title) hdlist.push(item)
}
for (let item of hdlist) {
let endDt = item.end_time
endDt = endDt.replace(/\s/, `T`)
let todayt = new Date()
endDt = new Date(endDt)
let sydate = await this.calculateRemainingTime(todayt, endDt)
if (sydate.days <= 1) result.push(item)
}
return result
}
async calculateRemainingTime(startDate, endDate) {
const difference = endDate - startDate;
const days = Math.floor(difference / (1000 * 60 * 60 * 24));
const hours = Math.floor((difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((difference % (1000 * 60)) / 1000);
return { days, hours, minutes, seconds };
}
async sendNews(botId, groupId, typeName, postId, gid) {
if (!this.pushGroup[groupId]) this.pushGroup[groupId] = 0
if (this.pushGroup[groupId] >= this.maxNum) return
let sended = await redis.get(`${this.key}${botId}:${groupId}:${postId}`)
if (sended) return
let game = this.game(gid)
// 判断是否存在群关系
this.e.group = Bot[botId]?.pickGroup(groupId)
if (!this.e.group) {
logger.mark(`[米游社${game}${typeName}推送] 群${botId}:${groupId}未关联`)
return
}
if (!this[postId]) {
const param = await this.newsDetail(postId, gid)
logger.mark(`[米游社${game}${typeName}推送] ${param.data.post.subject}`)
this[postId] = {
img: await this.render(param),
title: param.data.post.subject
}
}
this.pushGroup[groupId]++
await redis.set(`${this.key}${botId}:${groupId}:${postId}`, '1', { EX: 3600 * 10 })
// 随机延迟10-90秒
await common.sleep(lodash.random(10000, 90000))
const msg = await this.replyMsg(this[postId].img, `${game}${typeName}推送:${this[postId].title}`)
return this.e.group.sendMsg(msg)
}
game(gid) {
switch (gid) {
case 1:
return '崩坏三'
case 2:
return '原神'
case 3:
return '崩坏二'
case 4:
return '未定事件簿'
case 6:
return '崩坏星穹铁道'
case 8:
return '绝区零'
}
return ''
}
}