feat: 挑战定时提醒 (#143)

* feat: 挑战定时提醒

* fix: 修复定时任务无法获取api

* fix: 修复导入

* 增加优先级

* feat: 增加全局和个人提醒时间

* fix: 修复消息正则

* fix: 修复时间设置正则

* fix

* 修复导入顺序

* fix: 修复管理函数位置

* fix: 修复cron

* fix: 修复全局时间配置和获取

* 删俩空格

* fix: 修复无法查询别人

* fix: 修复没有config不能查询

* 添加错误信息

* 删注释

* fix: 修复时间正则匹配

* feat: 支持整十分钟

* fix: 修复消息发送

* 修复cron

* 阈值命令添加别名

* 添加help

* 移动全局提醒时间函数

* fix

* perf: 减少导入,提升性能

* perf: 提取辅助函数

* 优化锅巴提示

* 添加指令设置全局开启/关闭

* perf: 优化逻辑

* 好友、启用状态判断;指令修改全局式舆/危局阈值;删除部分return false;调整帮助图

* fix: 推送时好友判断

* style: 删除一个行尾空格

---------

Co-authored-by: UCPr <2032385471@qq.com>
This commit is contained in:
Qian 2025-09-08 23:28:24 +08:00 committed by GitHub
parent 48b3146ad9
commit 46af7f3482
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 556 additions and 0 deletions

View file

@ -183,6 +183,75 @@ const helpData = [
},
],
},
{
title: '挑战提醒',
icon: 'dungeon',
items: [
{
title: '开关挑战提醒',
desc: '开启或关闭式舆防卫战/危局强袭战未完成提醒功能',
needCK: true,
needSK: false,
commands: ['开启挑战提醒', '关闭挑战提醒'],
},
{
title: '开关全局挑战提醒',
desc: '开启或关闭全局式舆防卫战/危局强袭战未完成提醒功能',
needCK: true,
needSK: false,
commands: ['开启全局挑战提醒', '关闭全局挑战提醒'],
},
{
title: '设置全局提醒时间',
desc: '设置全局提醒时间,所有未单独设置个人提醒时间的用户将使用此时间。格式同上,仅限主人可用。',
needCK: true,
needSK: false,
commands: ['设置全局提醒时间+时间'],
},
{
title: '设置全局提醒阈值',
desc: '设置全局默认防卫战检查层数阈值(1~7),或全局默认危局强袭战星星阈值(1~9)',
needCK: true,
needSK: false,
commands: ['设置全局式舆阈值+数字', '设置全局危局阈值+数字'],
},
{
title: '设置提醒阈值',
desc: '设置防卫战检查层数阈值(1~7),或危局强袭战星星阈值(1~9)。例如设置危局阈值6',
needCK: true,
needSK: false,
commands: ['设置防卫战阈值+数字', '设置危局阈值+数字'],
},
{
title: '设置个人提醒时间',
desc: '设置每日或每周的个人提醒时间格式如“每日20时”或“每周六20时10分”分钟需为整十数可不加分钟。',
needCK: true,
needSK: false,
commands: ['设置个人提醒时间+时间'],
},
{
title: '查看提醒时间',
desc: '查看个人或全局提醒时间',
needCK: true,
needSK: false,
commands: ['个人提醒时间', '全局提醒时间']
},
{
title: '重置个人提醒时间',
desc: '重置已设置的个人提醒时间为全局默认时间',
needCK: true,
needSK: false,
commands: ['重置个人提醒时间'],
},
{
title: '查询挑战状态',
desc: '主动查询当前式舆防卫战/危局强袭战完成情况',
needCK: true,
needSK: false,
commands: ['查询挑战状态'],
},
],
},
{
title: '角色攻略',
icon: 'physdmg',

430
apps/remind.js Normal file
View file

@ -0,0 +1,430 @@
import _ from 'lodash';
import settings from '../lib/settings.js';
import { rulePrefix } from '../lib/common.js';
import { ZZZPlugin } from '../lib/plugin.js';
const USER_CONFIGS_KEY = 'ZZZ:REMIND:USER_CONFIGS';
// 计算到指定等级为止的S评级数量
function getSRankCountUpTo(allFloorDetail, maxLevel) {
const sSet = new Set(
allFloorDetail.filter(f => f.rating === 'S').map(f => f.layer_index)
);
const minLevel = Math.min(...allFloorDetail.map(f => f.layer_index));
let sRankCount = 0;
for (let level = 1; level <= maxLevel; level++) {
if (sSet.has(level) || level < minLevel) {
sRankCount++;
}
}
return sRankCount;
}
export class Remind extends ZZZPlugin {
constructor() {
super({
name: '[ZZZ-Plugin]Remind',
dsc: '式舆防卫战/危局强袭战未完成提醒',
event: 'message',
priority: _.get(settings.getConfig('priority'), 'remind', 70),
rule: [
{
reg: `${rulePrefix}(开启|关闭)挑战提醒$`,
fnc: 'setSubscribeEnable',
},
{
reg: `${rulePrefix}(开启|启用|关闭|禁用)全局挑战提醒$`,
fnc: 'setGlobalRemindEnable',
permission: 'master',
},
{
reg: `${rulePrefix}设置(全局)?式舆阈值\\s*(\\d+)`,
fnc: 'setAbyssThreshold',
},
{
reg: `${rulePrefix}设置(全局)?危局阈值\\s*(\\d+)`,
fnc: 'setDeadlyThreshold',
},
{
reg: `${rulePrefix}查询挑战状态$`,
fnc: 'checkNow',
},
{
reg: `${rulePrefix}设置个人提醒时间\\s*(每日\\d+时(?:(\\d+)分)?|每周.\\d+时(?:(\\d+)分)?)`,
fnc: 'setMyRemindTime',
},
{
reg: `${rulePrefix}个人提醒时间$`,
fnc: 'viewMyRemindTime',
},
{
reg: `${rulePrefix}(重置|删除|取消)个人提醒时间`,
fnc: 'deleteMyRemindTime',
},
{
reg: `${rulePrefix}设置全局提醒时间\\s*(每日\\d+时(?:(\\d+)分)?|每周.\\d+时(?:(\\d+)分)?)`,
fnc: 'setGlobalRemindTime',
permission: 'master',
},
{
reg: `${rulePrefix}全局提醒时间$`,
fnc: 'viewGlobalRemindTime',
},
],
});
const globalRemindConfig = settings.getConfig('remind');
if (globalRemindConfig.enable) {
this.task = {
name: 'ZZZ-Plugin式舆防卫战/危局强袭战提醒任务',
cron: '0 */10 * * * ?',
fnc: () => this.runTask(),
};
}
}
async getUserConfig(userId) {
const userConfigJson = await redis.hGet(USER_CONFIGS_KEY, String(userId));
return userConfigJson ? JSON.parse(userConfigJson) : null;
}
async setUserConfig(userId, config) {
await redis.hSet(USER_CONFIGS_KEY, String(userId), JSON.stringify(config));
}
parseRemindTimeMessage(message) {
const pattern = /提醒时间\s*(每日\d+时(?:(\d+)分)?|每周.\d+时(?:(\d+)分)?)/;
const match = message.match(pattern);
if (!match) return { remindTime: null, error: '时间格式错误' };
const remindTime = match[1];
const minute = Number(match[2]) || Number(match[3]) || 0;
if (!(minute % 10 === 0 && minute >= 0 && minute < 60)) {
return { remindTime: null, error: '分钟必须为整十分钟' };
}
return { remindTime, error: null };
}
isTimeMatch(remindTime, date) {
if (!remindTime) return false;
const currentDay = date.getDay(); // 0 = 周日, 1 = 周一, ..., 6 = 周六
const currentHour = date.getHours();
const currentMinute = date.getMinutes();
if (remindTime.includes('每日')) {
const match = remindTime.match(/每日(\d+)时(?:(\d+)分)?/);
if (match) {
const hour = parseInt(match[1]);
const minute = match[2] ? parseInt(match[2]) : 0;
return currentHour === hour && currentMinute === minute;
}
} else if (remindTime.includes('每周')) {
const dayMap = { '日': 0, '天': 0, '一': 1, '二': 2, '三': 3, '四': 4, '五': 5, '六': 6 };
const match = remindTime.match(/每周(.)(\d+)时(?:(\d+)分)?/);
if (match) {
const dayChar = match[1];
const day = dayMap[dayChar];
const hour = parseInt(match[2]);
const minute = match[3] ? parseInt(match[3]) : 0;
return currentDay === day && currentHour === hour && currentMinute === minute;
}
}
return false;
}
checkEnableAndFriend() {
if (!settings.getConfig('remind').enable) {
this.reply('当前未启用防卫战/危局挑战提醒功能');
return false;
}
if (!(this.e.bot ?? Bot).fl.get(this.e.user_id)) {
this.reply('请添加好友后重试');
return false;
};
return true;
}
async setSubscribeEnable() {
if (!this.checkEnableAndFriend()) return;
const enable = /开启挑战提醒$/.test(this.e.msg);
const uid = await this.getUID();
if (enable && !uid) {
return this.reply('未绑定UID请先绑定');
}
let userConfig = await this.getUserConfig(this.e.user_id);
const defaultConfig = settings.getConfig('remind');
if (!userConfig) {
userConfig = {
enable: false,
abyssCheckLevel: defaultConfig.abyssCheckLevel,
deadlyStars: defaultConfig.deadlyStars,
};
}
if (userConfig.enable === enable) {
return this.reply(enable ? '提醒已开启,请勿重复操作' : '提醒功能尚未开启');
}
userConfig.enable = enable;
await this.setUserConfig(this.e.user_id, userConfig);
await this.reply(`提醒功能已${enable ? '开启' : '关闭'}${enable ? `,将在${userConfig.remindTime || defaultConfig.globalRemindTime}对防卫战<${userConfig.abyssCheckLevel}层或危局<${userConfig.deadlyStars}星进行提醒` : ''}`);
}
async setGlobalRemindEnable() {
if (!this.e.isMaster) {
return this.reply('仅限主人设置', false, { at: true, recallMsg: 100 });
}
const enable = /(开启|启用)全局挑战提醒$/.test(this.e.msg);
if (settings.getConfig('remind').enable === enable) {
return this.reply(enable ? '全局防卫战/危局挑战提醒功能已启用,请勿重复操作' : '全局防卫战/危局挑战提醒功能已禁用,请勿重复操作');
}
settings.setSingleConfig('remind', 'enable', enable);
await this.reply(`全局防卫战/危局挑战提醒功能已${enable ? '启用' : '禁用'}`);
}
async setAbyssThreshold() {
const isGlobal = this.e.msg.includes('全局');
if (isGlobal && !this.e.isMaster) {
return this.reply('仅限主人设置', false, { at: true, recallMsg: 100 });
}
if (!isGlobal && !this.checkEnableAndFriend()) return;
const match = this.e.msg.match(/设置(?:全局)?(?:式舆防卫战|式舆|深渊|防卫战|防卫)阈值\s*(\d+)/);
if (!match) return;
const threshold = Number(match[1]);
if (threshold < 1 || threshold > 7) {
return this.reply('防卫战阈值必须在1到7之间');
}
if (isGlobal) {
settings.setSingleConfig('remind', 'abyssCheckLevel', threshold);
} else {
let userConfig = await this.getUserConfig(this.e.user_id);
if (!userConfig) {
const defaultConfig = settings.getConfig('remind');
userConfig = {
enable: false,
abyssCheckLevel: defaultConfig.abyssCheckLevel,
deadlyStars: defaultConfig.deadlyStars,
};
}
userConfig.abyssCheckLevel = threshold;
await this.setUserConfig(this.e.user_id, userConfig);
};
await this.reply(`${isGlobal ? '全局默认' : ''}式舆防卫战阈值已设为: <${threshold}层时提醒`);
}
async setDeadlyThreshold() {
const isGlobal = this.e.msg.includes('全局');
if (isGlobal && !this.e.isMaster) {
return this.reply('仅限主人设置', false, { at: true, recallMsg: 100 });
}
if (!isGlobal && !this.checkEnableAndFriend()) return;
const match = this.e.msg.match(/设置(?:全局)?(?:危局强袭战|危局|强袭|强袭战)阈值\s*(\d+)/);
if (!match) return;
const threshold = Number(match[1]);
if (threshold < 1 || threshold > 9) {
return this.reply('危局阈值必须在1到9之间');
}
if (isGlobal) {
settings.setSingleConfig('remind', 'deadlyStars', threshold);
} else {
let userConfig = await this.getUserConfig(this.e.user_id);
if (!userConfig) {
const defaultConfig = settings.getConfig('remind');
userConfig = {
enable: false,
abyssCheckLevel: defaultConfig.abyssCheckLevel,
deadlyStars: defaultConfig.deadlyStars,
};
}
userConfig.deadlyStars = threshold;
await this.setUserConfig(this.e.user_id, userConfig);
}
await this.reply(`${isGlobal ? '全局默认' : ''}危局强袭战阈值已设为: <${threshold}星时提醒`);
}
async checkNow() {
const uid = await this.getUID();
if (!uid) return false;
const targetUserId = this.e.user_id;
let userConfig = await this.getUserConfig(targetUserId);
if (!userConfig) {
// 如果没有配置,使用默认配置
const defaultConfig = settings.getConfig('remind');
userConfig = {
enable: false, // 不开启提醒,仅用于查询
abyssCheckLevel: defaultConfig.abyssCheckLevel,
deadlyStars: defaultConfig.deadlyStars,
};
}
await this.reply('正在查询,请稍候...');
const messages = await this.checkUser(targetUserId, userConfig, true); // 主动查询,显示所有状态
if (messages.length > 0) {
await this.reply(messages.join('\n'));
} else {
await this.reply('查询失败,请稍后再试');
}
}
async checkUser(userId, userConfig, showAll = false, contextE = null) {
let messages = [];
const originalE = this.e;
this.e = contextE || this.e;
let api, deviceFp;
try {
// 获取 API 和玩家信息
({ api, deviceFp } = await this.getAPI());
await this.getPlayerInfo(this.e);
} catch (error) {
logger.error(`[ZZZ-Plugin] 为用户 ${userId} 获取API或玩家信息失败: ${error}`);
messages.push('查询失败,请稍后再试');
// 恢复原来的 this.e
this.e = originalE;
return messages;
}
const defaultConfig = settings.getConfig('remind');
// 检查式舆防卫战
try {
const abyssRawData = await api.getFinalData('zzzChallenge', { deviceFp });
if (!abyssRawData || !abyssRawData.has_data) {
messages.push('式舆防卫战S评级: 0/7');
} else {
const abyssCheckLevel = userConfig.abyssCheckLevel ?? defaultConfig.abyssCheckLevel;
const sCount = getSRankCountUpTo(abyssRawData.all_floor_detail, 7);
const status = sCount >= abyssCheckLevel ? ' ✓' : '';
if (showAll || sCount < abyssCheckLevel) {
messages.push(`式舆防卫战S评级: ${sCount}/7${status}`);
}
}
} catch (error) {
logger.error(`[ZZZ-Plugin] 为用户 ${userId} 检查式舆防卫战失败: ${error}`);
messages.push(`式舆防卫战查询失败: ${error}`);
}
// 检查危局强袭战
try {
const deadlyRawData = await api.getFinalData('zzzDeadly', { deviceFp });
if (!deadlyRawData || !deadlyRawData.has_data) {
messages.push('危局强袭战星星: 0/9');
} else {
const deadlyStars = userConfig.deadlyStars ?? defaultConfig.deadlyStars;
const totalStar = deadlyRawData.total_star || 0;
const status = totalStar >= deadlyStars ? ' ✓' : '';
if (showAll || totalStar < deadlyStars) {
messages.push(`危局强袭战星星: ${totalStar}/9${status}`);
}
}
} catch (error) {
logger.error(`[ZZZ-Plugin] 为用户 ${userId} 检查危局强袭战失败: ${error}`);
messages.push(`危局强袭战查询失败: ${error}`);
}
// 恢复原来的 this.e
this.e = originalE;
return messages;
}
async runTask() {
const globalRemindConfig = settings.getConfig('remind');
if (!globalRemindConfig.enable) {
return;
}
logger.info('[ZZZ-Plugin] 开始执行式舆防卫战/危局强袭战提醒任务');
const allUserConfigs = await redis.hGetAll(USER_CONFIGS_KEY);
const now = new Date();
const globalRemindTime = globalRemindConfig.globalRemindTime || '每日20时';
for (const key in allUserConfigs) {
const userConfig = JSON.parse(allUserConfigs[key]);
if (!userConfig.enable) continue;
const userId = +key;
if (!Bot.fl.get(userId)) continue;
const remindTime = userConfig.remindTime || globalRemindTime;
if (this.isTimeMatch(remindTime, now)) {
// 创建一个模拟的 e 对象,用于获取 API
const mockE = {
user_id: userId,
game: 'zzz',
reply: (msg) => logger.info(`[Remind Mock Reply] ${msg}`)
};
const messages = await this.checkUser(userId, userConfig, false, mockE);
if (messages.length > 0) {
await Bot.pickFriend(userId).sendMsg('【式舆/危局挑战提醒】\n' + messages.join('\n')).catch(err => {
logger.error(`[ZZZ-Plugin] 式舆/危局挑战推送用户 ${userId} 失败`, err);
});
}
}
}
logger.info('[ZZZ-Plugin] 式舆防卫战/危局强袭战提醒任务执行完毕');
}
async setMyRemindTime() {
if (!this.checkEnableAndFriend()) return;
const { remindTime, error } = this.parseRemindTimeMessage(this.e.msg);
if (!remindTime) return await this.reply(error);
let userConfig = await this.getUserConfig(this.e.user_id);
if (!userConfig) {
const defaultConfig = settings.getConfig('remind');
userConfig = {
enable: false,
abyssCheckLevel: defaultConfig.abyssCheckLevel,
deadlyStars: defaultConfig.deadlyStars,
};
}
userConfig.remindTime = remindTime;
await this.setUserConfig(this.e.user_id, userConfig);
await this.reply(`您的个人提醒时间已设置为: ${remindTime}`);
}
async viewMyRemindTime() {
const userConfig = await this.getUserConfig(this.e.user_id);
if (userConfig && userConfig.remindTime) {
await this.reply(`当前个人提醒时间: ${userConfig.remindTime}`);
} else {
const remindConfig = settings.getConfig('remind');
const globalRemindTime = remindConfig.globalRemindTime || '每日20时';
await this.reply(`个人提醒时间未设置,默认使用全局时间: ${globalRemindTime}`);
}
}
async deleteMyRemindTime() {
if (!this.checkEnableAndFriend()) return;
let userConfig = await this.getUserConfig(this.e.user_id);
if (userConfig && userConfig.remindTime) {
delete userConfig.remindTime;
await this.setUserConfig(this.e.user_id, userConfig);
await this.reply('个人提醒时间已重置为全局默认时间');
} else {
await this.reply('个人提醒时间尚未设置');
}
}
async setGlobalRemindTime() {
if (!this.e.isMaster) {
return this.reply('仅限主人设置', false, { at: true, recallMsg: 100 });
}
const { remindTime: globalRemindTime, error } = this.parseRemindTimeMessage(this.e.msg);
if (!globalRemindTime) return await this.reply(error);
settings.setSingleConfig('remind', 'globalRemindTime', globalRemindTime);
await this.reply(`全局提醒时间已更新为: ${globalRemindTime}`);
}
async viewGlobalRemindTime() {
const globalRemindTime = settings.getConfig('remind').globalRemindTime || '每日20时';
await this.reply(`当前全局提醒时间: ${globalRemindTime}`);
}
}

View file

@ -6,6 +6,7 @@ help: 70 # 帮助
manage: 70 # 管理
note: 70 # 体力
panel: 70 # 面板
remind: 70 # 挑战提醒
update: 70 # 更新
user: 70 # 账号操作
monthly: 70 # 菲林月历

4
defSet/remind.yaml Normal file
View file

@ -0,0 +1,4 @@
enable: true # 功能总开关
globalRemindTime: '每日20时' # 全局提醒时间,用于没有个人设置的用户
abyssCheckLevel: 7 # 式舆防卫战提醒检查的最高关卡用户可自行设置最高为7
deadlyStars: 6 # 危局强袭战星星阈值

View file

@ -194,6 +194,46 @@ export function supportGuoba() {
addonAfter: "s",
},
},
{
component: 'SOFT_GROUP_BEGIN',
label: '提醒功能设置',
},
{
field: 'remind.enable',
label: '开启提醒功能',
bottomHelpMessage: '是否启用式舆防卫战/危局强袭战的定时提醒功能',
component: 'Switch',
},
{
field: 'remind.globalRemindTime',
label: '全局提醒时间',
bottomHelpMessage: '设置全局默认提醒时间,格式如"每日20时"或"每周一20时40分"',
component: 'Input',
componentProps: {
placeholder: '每日20时',
},
},
{
field: 'remind.abyssCheckLevel',
label: '默认式舆S评级阈值',
bottomHelpMessage: '新用户订阅时S评级数量低于此值会收到提醒',
component: 'InputNumber',
componentProps: {
min: 1,
max: 7,
placeholder: '请输入数字(1-7)',
},
},
{
field: 'remind.deadlyStars',
label: '默认危局星星阈值',
bottomHelpMessage: '新用户订阅时,星星总数低于此值会收到提醒',
component: 'InputNumber',
componentProps: {
min: 0,
placeholder: '请输入数字',
},
},
{
component: 'SOFT_GROUP_BEGIN',
label: '攻略设置',
@ -416,6 +456,18 @@ export function supportGuoba() {
placeholder: '请输入数字',
},
},
{
field: 'priority.remind',
label: '挑战提醒',
bottomHelpMessage: '设置挑战提醒指令优先级',
component: 'InputNumber',
required: true,
componentProps: {
min: -1000,
max: 1000,
placeholder: '请输入数字',
},
},
{
field: 'priority.update',
label: '更新插件',