From 19605a94016b3d87aab553659abcecd0a38db0b7 Mon Sep 17 00:00:00 2001 From: LamGC Date: Thu, 23 Apr 2020 16:23:06 +0800 Subject: [PATCH] =?UTF-8?q?[Add]=20BotEventHandler=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E9=A2=84=E5=A4=84=E7=90=86=E6=96=B9=E6=B3=95?= =?UTF-8?q?;=20[Add]=20BotAdminCommandProcess=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=8E=A8=E9=80=81=E5=8A=9F=E8=83=BD=E7=9B=B8=E5=85=B3=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=91=BD=E4=BB=A4;=20[Change]=20RandomIntervalSendTim?= =?UTF-8?q?er=20=E8=B0=83=E6=95=B4Timer=E7=AE=A1=E7=90=86=E8=BF=87?= =?UTF-8?q?=E7=A8=8B;=20[Change]=20BotCommandProcess=20ranking=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E5=AF=B9=E5=8F=82=E6=95=B0=E9=94=99=E8=AF=AF=E7=9A=84?= =?UTF-8?q?=E5=A4=84=E7=90=86=E8=B0=83=E6=95=B4=E4=B8=BA=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E4=BF=A1=E6=81=AF;=20[Fix]=20RankingUpdateTi?= =?UTF-8?q?mer=20=E4=BF=AE=E5=A4=8D=E5=8F=82=E6=95=B0=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lamgc/cgj/bot/BotAdminCommandProcess.java | 150 ++++++++++++++++-- .../net/lamgc/cgj/bot/BotCommandProcess.java | 12 +- .../cgj/bot/RandomIntervalSendTimer.java | 81 +++++++++- .../net/lamgc/cgj/bot/RankingUpdateTimer.java | 2 +- .../lamgc/cgj/bot/event/BotEventHandler.java | 28 +++- 5 files changed, 248 insertions(+), 25 deletions(-) diff --git a/src/main/java/net/lamgc/cgj/bot/BotAdminCommandProcess.java b/src/main/java/net/lamgc/cgj/bot/BotAdminCommandProcess.java index 4646b3a..e7ab185 100644 --- a/src/main/java/net/lamgc/cgj/bot/BotAdminCommandProcess.java +++ b/src/main/java/net/lamgc/cgj/bot/BotAdminCommandProcess.java @@ -1,29 +1,43 @@ package net.lamgc.cgj.bot; import com.google.common.base.Strings; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; +import net.lamgc.cgj.bot.message.MessageSenderBuilder; +import net.lamgc.cgj.bot.message.MessageSource; +import net.lamgc.cgj.pixiv.PixivDownload; import net.lamgc.utils.base.runner.Argument; import net.lamgc.utils.base.runner.Command; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; -import java.util.Date; -import java.util.Properties; +import java.util.*; public class BotAdminCommandProcess { private final static Logger log = LoggerFactory.getLogger(BotAdminCommandProcess.class.getSimpleName()); - private final static File globalPropFile = new File("./global.properties"); + private final static File globalPropFile = new File("global.properties"); + private final static File pushListFile = new File("pushList.json"); + + private final static Hashtable pushInfoMap = new Hashtable<>(); + + private final static Gson gson = new GsonBuilder() + .setPrettyPrinting() + .create(); + @Command - public String clearCache() { + public static String cleanCache() { BotCommandProcess.clearCache(); return "操作已完成."; } @Command - public String setGlobalProperty(@Argument(name = "key") String key, @Argument(name = "value") String value, @Argument(name = "save", force = false) boolean saveNow) { + public static String setGlobalProperty(@Argument(name = "key") String key, @Argument(name = "value") String value, @Argument(name = "save", force = false) boolean saveNow) { String lastValue = BotCommandProcess.globalProp.getProperty(key); BotCommandProcess.globalProp.setProperty(key, Strings.nullToEmpty(value)); if(saveNow) { @@ -33,12 +47,12 @@ public class BotAdminCommandProcess { } @Command - public String getGlobalProperty(@Argument(name = "key") String key) { + public static String getGlobalProperty(@Argument(name = "key") String key) { return "全局配置项 " + key + " 当前值: " + BotCommandProcess.globalProp.getProperty(key, "(Empty)"); } @Command - public String saveGlobalProperties() { + public static String saveGlobalProperties() { log.info("正在保存全局配置文件..."); try { @@ -58,7 +72,7 @@ public class BotAdminCommandProcess { } @Command - public String loadGlobalProperties(@Argument(name = "reload", force = false) boolean reload) { + public static String loadGlobalProperties(@Argument(name = "reload", force = false) boolean reload) { Properties cache = new Properties(); if(!globalPropFile.exists()) { return "未找到全局配置文件, 无法重载"; @@ -79,7 +93,7 @@ public class BotAdminCommandProcess { } @Command - public String runUpdateTask(@Argument(force = false, name = "date") Date queryTime) { + public static String runUpdateTask(@Argument(force = false, name = "date") Date queryTime) { try { BotCommandProcess.runUpdateTimer(queryTime); } catch (Exception e) { @@ -89,4 +103,122 @@ public class BotAdminCommandProcess { return "操作已完成."; } + @Command + public static String addPushGroup(@Argument(name = "group") long groupId, + @Argument(name = "minTime", force = false, defaultValue = "21600000") long minTime, + @Argument(name = "floatTime", force = false, defaultValue = "10800000") int floatTime, + @Argument(name = "rankingStart", force = false, defaultValue = "1") int rankingStart, + @Argument(name = "rankingStop", force = false, defaultValue = "150") int rankingStop, + @Argument(name = "original", force = false, defaultValue = "false") boolean original + ) { + JsonObject setting = new JsonObject(); + setting.addProperty("time.min", minTime); + setting.addProperty("time.float", floatTime); + setting.addProperty("ranking.start", rankingStart); + setting.addProperty("ranking.end", rankingStop); + setting.addProperty("pageQuality.original", original); + if(pushInfoMap.containsKey(groupId)) { + log.info("群 {} 已存在Timer, 删除Timer...", groupId); + removePushTimer(groupId); + } + + log.info("正在增加Timer...(Setting: {})", setting); + pushInfoMap.put(groupId, setting); + addPushTimer(groupId, setting); + return "已在 " + groupId + " 开启定时推送功能。"; + } + + /** + * 重载推送列表 + */ + @Command + public static String loadPushList() { + pushInfoMap.clear(); + if(!pushListFile.exists()) { + log.warn("推送列表文件不存在, 跳过加载."); + return "文件不存在, 跳过加载."; + } + + try (Reader reader = new BufferedReader(new FileReader(pushListFile))) { + pushInfoMap.putAll(gson.fromJson(reader, new TypeToken>(){}.getType())); + loadAllPushTimer(false); + return "列表重载完成"; + } catch (IOException e) { + log.error("重载推送列表时发生错误", e); + return "加载时发生异常"; + } + } + + @Command + public static String savePushList() { + try { + if(!pushListFile.exists()) { + pushListFile.createNewFile(); + } + } catch (IOException e) { + log.error("PushList.json文件创建失败", e); + return "保存失败!请检查控制台信息."; + } + + try (Writer writer = new FileWriter(pushListFile)) { + writer.write(gson.toJson(pushInfoMap)); + return "保存成功."; + } catch (IOException e) { + log.error("写入PushList.json文件失败!", e); + return "保存失败!请检查控制台信息."; + } + } + + public static void loadAllPushTimer(boolean flush) { + if(flush) { + RandomIntervalSendTimer.timerIdSet().forEach(id -> RandomIntervalSendTimer.getTimerById(id).destroy()); + } + cleanPushTimer(); + pushInfoMap.forEach(BotAdminCommandProcess::addPushTimer); + } + + private static void addPushTimer(long id, JsonObject setting) { + try { + RandomIntervalSendTimer.getTimerById(id); + return; + } catch(NoSuchElementException ignored) { + } + + AutoSender sender = new RandomRankingArtworksSender( + MessageSenderBuilder.getMessageSender(MessageSource.Group, id), + setting.get("ranking.start").getAsInt(), + setting.get("ranking.end").getAsInt(), + setting.get("pageQuality.original").getAsBoolean() ? PixivDownload.PageQuality.ORIGINAL : PixivDownload.PageQuality.REGULAR + ); + + RandomIntervalSendTimer.createTimer( + id, + sender, + setting.get("time.min").getAsLong(), + setting.get("time.float").getAsInt(), + true, true); + } + + /** + * 删除一个推送定时器 + * @param id 群号 + * @throws NoSuchElementException 当这个群号没有定时器的时候抛出异常 + */ + @Command + public static void removePushTimer(@Argument(name = "group") long id) { + RandomIntervalSendTimer.getTimerById(id).destroy(); + pushInfoMap.remove(id); + } + + /** + * 根据已修改的pushInfoMap将已经被删除的Timer取消 + */ + private static void cleanPushTimer() { + RandomIntervalSendTimer.timerIdSet().forEach(id -> { + if(!pushInfoMap.containsKey(id)) { + RandomIntervalSendTimer.getTimerById(id).destroy(); + } + }); + } + } diff --git a/src/main/java/net/lamgc/cgj/bot/BotCommandProcess.java b/src/main/java/net/lamgc/cgj/bot/BotCommandProcess.java index 3c9fde8..735aeb8 100644 --- a/src/main/java/net/lamgc/cgj/bot/BotCommandProcess.java +++ b/src/main/java/net/lamgc/cgj/bot/BotCommandProcess.java @@ -175,18 +175,22 @@ public class BotCommandProcess { } } - PixivURL.RankingMode mode = PixivURL.RankingMode.MODE_DAILY; + PixivURL.RankingMode mode; try { - mode = PixivURL.RankingMode.valueOf("MODE_" + contentMode.toUpperCase()); + String rankingModeValue = contentMode.toUpperCase(); + mode = PixivURL.RankingMode.valueOf(rankingModeValue.startsWith("MODE_") ? rankingModeValue : "MODE_" + rankingModeValue); } catch (IllegalArgumentException e) { log.warn("无效的RankingMode值: {}", contentMode); + return "参数无效, 请查看帮助信息"; } - PixivURL.RankingContentType type = PixivURL.RankingContentType.TYPE_ILLUST; + PixivURL.RankingContentType type; try { - type = PixivURL.RankingContentType.valueOf("TYPE_" + contentType.toUpperCase()); + String contentTypeValue = contentType.toUpperCase(); + type = PixivURL.RankingContentType.valueOf(contentTypeValue.startsWith("TYPE_") ? contentTypeValue : "TYPE_" + contentTypeValue); } catch (IllegalArgumentException e) { log.warn("无效的RankingContentType值: {}", contentType); + return "参数无效, 请查看帮助信息"; } if(!type.isSupportedMode(mode)) { diff --git a/src/main/java/net/lamgc/cgj/bot/RandomIntervalSendTimer.java b/src/main/java/net/lamgc/cgj/bot/RandomIntervalSendTimer.java index 5729650..bb3f65d 100644 --- a/src/main/java/net/lamgc/cgj/bot/RandomIntervalSendTimer.java +++ b/src/main/java/net/lamgc/cgj/bot/RandomIntervalSendTimer.java @@ -4,9 +4,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Field; -import java.util.Random; -import java.util.Timer; -import java.util.TimerTask; +import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -16,6 +14,9 @@ public class RandomIntervalSendTimer extends TimerTask { private final static Timer timer = new Timer("Thread-RIST"); private final static Logger log = LoggerFactory.getLogger("RandomIntervalSendTimer"); + private final static Map timerMap = new HashMap<>(); + + private final long timerId; private final Random timeRandom = new Random(); private final AutoSender sender; private final long time; @@ -23,21 +24,79 @@ public class RandomIntervalSendTimer extends TimerTask { private AtomicBoolean loop = new AtomicBoolean(); private final AtomicBoolean start = new AtomicBoolean(); + /** + * 创建一个随机延迟发送器 + * @param timerId 该Timer的标识, + * 标识必须是唯一的, 当使用相同id创建时, 将会返回该id所属的Timer对象 + * @param sender 自动发送器 + * @param time 最低时间(ms) + * @param floatTime 浮动时间(ms) + * @param startNow 现在开始 + * @param loop 是否循环 + */ + public static RandomIntervalSendTimer createTimer(long timerId, AutoSender sender, long time, int floatTime, boolean startNow, boolean loop) { + if(timerMap.containsKey(timerId)) { + return timerMap.get(timerId); + } - public RandomIntervalSendTimer(AutoSender sender, long time, int floatTime) { + RandomIntervalSendTimer timer = new RandomIntervalSendTimer(timerId, sender, time, floatTime, startNow, loop); + timerMap.put(timerId, timer); + return timer; + } + + /** + * 通过Id获取Timer + * @param id 待获取Timer对应的Id + * @return 返回RandomIntervalSendTimer对象 + * @throws NoSuchElementException 当不存在Timer时抛出 + */ + public static RandomIntervalSendTimer getTimerById(long id) { + if(!timerMap.containsKey(id)) { + throw new NoSuchElementException("id=" + id); + } + return timerMap.get(id); + } + + /** + * 获取所有id + * @return 所有TimerId的集合 + */ + public static Set timerIdSet() { + return new HashSet<>(timerMap.keySet()); + } + + /** + * 创建一个随机延迟发送器 + * @param timerId 该Timer的标识 + * @param sender 自动发送器 + * @param time 最低时间(ms) + * @param floatTime 浮动时间(ms) + * @param startNow 现在开始 + * @param loop 是否循环 + */ + private RandomIntervalSendTimer(long timerId, AutoSender sender, long time, int floatTime, boolean startNow, boolean loop) { + this.timerId = timerId; this.sender = sender; this.time = time; this.floatTime = floatTime; + timerMap.put(timerId, this); + if(startNow) { + start(loop); + } } public void start() { start(this.loop.get()); } + /** + * 启动定时器 + * @param loop 是否循环, 如果为true, 则任务完成后会自动调用start方法继续循环, 直到被调用{@code #}或总定时器被销毁; + */ public void start(boolean loop) { this.loop.set(loop); long nextDelay = time + timeRandom.nextInt(floatTime); - log.info("定时器 {} 下一延迟: {}ms", Integer.toHexString(this.hashCode()), nextDelay); + log.info("定时器 {} 下一延迟: {}ms ({}min)", Integer.toHexString(this.hashCode()), nextDelay, nextDelay / 1000F / 60F); if(start.get()) { try { Field state = this.getClass().getSuperclass().getDeclaredField("state"); @@ -61,10 +120,22 @@ public class RandomIntervalSendTimer extends TimerTask { } } + /** + * 取消该定时器 + * @return 取消成功返回true + */ @Override public boolean cancel() { start.set(false); return super.cancel(); } + /** + * 销毁这个定时器 + */ + public void destroy() { + cancel(); + timerMap.remove(this.timerId); + } + } diff --git a/src/main/java/net/lamgc/cgj/bot/RankingUpdateTimer.java b/src/main/java/net/lamgc/cgj/bot/RankingUpdateTimer.java index 20094be..ed98c27 100644 --- a/src/main/java/net/lamgc/cgj/bot/RankingUpdateTimer.java +++ b/src/main/java/net/lamgc/cgj/bot/RankingUpdateTimer.java @@ -70,7 +70,7 @@ public class RankingUpdateTimer { //BotCommandProcess.getRankingInfoByCache(contentType, rankingMode, calendar.getTime(), 1, 0, true); BotEventHandler.executor.executorSync( new VirtualLoadMessageEvent(0,0, - ".cgj ranking -type " + rankingMode.modeParam + " -mode " + rankingMode.modeParam)); + ".cgj ranking -type=" + contentType.name() + " -mode=" + rankingMode.name())); log.info("排行榜 {}.{} 更新完成.", rankingMode.name(), contentType.name()); } catch (InterruptedException e) { log.error("排行榜 {}.{} 更新时发生异常. \n{}", rankingMode.name(), contentType.name(), Throwables.getStackTraceAsString(e)); diff --git a/src/main/java/net/lamgc/cgj/bot/event/BotEventHandler.java b/src/main/java/net/lamgc/cgj/bot/event/BotEventHandler.java index 907d199..9e06bac 100644 --- a/src/main/java/net/lamgc/cgj/bot/event/BotEventHandler.java +++ b/src/main/java/net/lamgc/cgj/bot/event/BotEventHandler.java @@ -30,17 +30,19 @@ import java.util.Objects; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import java.util.regex.Pattern; public class BotEventHandler implements EventHandler { public final static String COMMAND_PREFIX = ".cgj "; + public final static String ADMIN_COMMAND_PREFIX = ".cgjadmin "; private final ArgumentsRunner processRunner; private final ArgumentsRunner adminRunner; - private final Logger log = LoggerFactory.getLogger("BotEventHandler"); + private final static Logger log = LoggerFactory.getLogger("BotEventHandler"); /** * 所有缓存共用的JedisPool @@ -95,6 +97,21 @@ public class BotEventHandler implements EventHandler { initialled = true; } + private final static AtomicBoolean preLoaded = new AtomicBoolean(); + /** + * 预加载 + */ + public synchronized static void preLoad() { + if(preLoaded.get()) { + return; + } + try { + BotAdminCommandProcess.loadPushList(); + } finally { + preLoaded.set(true); + } + } + private BotEventHandler() { ArgumentsRunnerConfig runnerConfig = new ArgumentsRunnerConfig(); runnerConfig.setUseDefaultValueInsteadOfException(true); @@ -151,12 +168,11 @@ public class BotEventHandler implements EventHandler { long time = System.currentTimeMillis(); Object result; try { - if(msg.toLowerCase().startsWith(COMMAND_PREFIX + "admin")) { + if(msg.toLowerCase().startsWith(ADMIN_COMMAND_PREFIX)) { if(!String.valueOf(event.getFromQQ()).equals(BotCommandProcess.globalProp.getProperty("admin.adminId"))) { - event.sendMessage("你没有执行该命令的权限!"); - return; + result = "你没有执行该命令的权限!"; } else { - result = adminRunner.run(new BotAdminCommandProcess(), args.length <= 1 ? new String[0] : Arrays.copyOfRange(args, 1, args.length)); + result = adminRunner.run(args.length <= 1 ? new String[0] : Arrays.copyOfRange(args, 1, args.length)); } } else { result = processRunner.run(args.length <= 1 ? new String[0] : Arrays.copyOfRange(args, 1, args.length)); @@ -186,7 +202,7 @@ public class BotEventHandler implements EventHandler { * @return 如果为true则提交 */ public static boolean match(String message) { - return message.startsWith(COMMAND_PREFIX); + return message.startsWith(COMMAND_PREFIX) || message.startsWith(ADMIN_COMMAND_PREFIX); } }