package net.lamgc.cgj.bot.event; import com.google.common.base.Strings; import com.google.common.base.Throwables; import com.google.common.util.concurrent.ThreadFactoryBuilder; import net.lamgc.cgj.bot.BotAdminCommandProcess; import net.lamgc.cgj.bot.BotCommandProcess; import net.lamgc.cgj.bot.MessageEventExecutionDebugger; import net.lamgc.cgj.bot.SettingProperties; import net.lamgc.cgj.bot.util.parser.DateParser; import net.lamgc.cgj.bot.util.parser.PagesQualityParser; import net.lamgc.cgj.bot.util.parser.RankingContentTypeParser; import net.lamgc.cgj.bot.util.parser.RankingModeParser; import net.lamgc.cgj.util.TimeLimitThreadPoolExecutor; import net.lamgc.utils.base.runner.ArgumentsRunner; import net.lamgc.utils.base.runner.ArgumentsRunnerConfig; import net.lamgc.utils.base.runner.exception.DeveloperRunnerException; import net.lamgc.utils.base.runner.exception.NoSuchCommandException; import net.lamgc.utils.base.runner.exception.ParameterNoFoundException; import net.lamgc.utils.event.EventObject; import net.lamgc.utils.event.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Method; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; 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 static Logger log = LoggerFactory.getLogger(BotEventHandler.class); /** * 消息事件执行器 */ private final static EventExecutor executor = new EventExecutor(new TimeLimitThreadPoolExecutor( 180000, // 3min limit Math.max(Runtime.getRuntime().availableProcessors(), 4), // 4 ~ processors Math.min(Math.max(Runtime.getRuntime().availableProcessors() * 2, 8), 32),// (8 ~ processors * 2) ~ 32 30L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1536), new ThreadFactoryBuilder() .setNameFormat("CommandProcess-%d") .build() )); private static boolean initialled = false; /** * 初始化BotEventHandler */ public synchronized static void initial() { if(initialled) { log.warn("BotEventHandler已经执行过初始化方法, 可能存在多次执行的问题, 堆栈信息: \n {}", Throwables.getStackTraceAsString(new Exception())); return; } executor.setEnableEventResend(true); executor.setEventUncaughtExceptionHandler(new EventUncaughtExceptionHandler() { private final Logger log = LoggerFactory.getLogger(this.getClass()); @Override public void exceptionHandler( Thread executeThread, EventHandler handler, Method handlerMethod, EventObject event, Throwable cause ) { log.error("EventExecutor@{} 发生未捕获异常:\n\t" + "Thread:{}\n\tEventHandler: {}\n\tHandlerMethod: {}\n\tEventObject: {}\n" + "------------------ Stack Trace ------------------\n{}", executor.hashCode(), executeThread.getName(), handler.toString(), handlerMethod.getName(), event.toString(), Throwables.getStackTraceAsString(cause)); } }); try { executor.addHandler(new BotEventHandler()); Thread shutdownThread = new Thread(() -> executor.shutdown(true)); shutdownThread.setName("Thread-EventHandlerShutdown"); Runtime.getRuntime().addShutdownHook(shutdownThread); } catch (IllegalAccessException e) { log.error("添加Handler时发生异常", e); } try { BotAdminCommandProcess.loadPushList(); } catch(Throwable e) { log.error("加载推送列表失败", e); } initialled = true; } private BotEventHandler() { ArgumentsRunnerConfig runnerConfig = new ArgumentsRunnerConfig(); runnerConfig.setUseDefaultValueInsteadOfException(true); runnerConfig.setCommandIgnoreCase(true); runnerConfig.addStringParameterParser(new DateParser(new SimpleDateFormat("yyyy-MM-dd"))); runnerConfig.addStringParameterParser(new PagesQualityParser()); runnerConfig.addStringParameterParser(new RankingModeParser()); runnerConfig.addStringParameterParser(new RankingContentTypeParser()); processRunner = new ArgumentsRunner(BotCommandProcess.class, runnerConfig); adminRunner = new ArgumentsRunner(BotAdminCommandProcess.class, runnerConfig); BotCommandProcess.initialize(); } /** * 投递消息事件 * @param event 事件对象 */ @NotAccepted public static void executeMessageEvent(MessageEvent event) { try { executeMessageEvent(event, false); } catch (InterruptedException e) { log.error("执行时发生异常", e); throw new RuntimeException(e); } } /** * 投递消息事件 * @param event 事件对象 * @param sync 是否同步执行事件 */ @NotAccepted public static void executeMessageEvent(MessageEvent event, boolean sync) throws InterruptedException { String debuggerName = SettingProperties.getProperty(0, "debug.debugger"); if(!event.getMessage().startsWith(ADMIN_COMMAND_PREFIX) && !Strings.isNullOrEmpty(debuggerName)) { try { MessageEventExecutionDebugger debugger = MessageEventExecutionDebugger.valueOf(debuggerName.toUpperCase()); debugger.debugger.accept(executor, event, SettingProperties.getProperties(SettingProperties.GLOBAL), MessageEventExecutionDebugger.getDebuggerLogger(debugger)); } catch(IllegalArgumentException e) { log.warn("未找到指定调试器: '{}'", debuggerName); } catch (Exception e) { log.error("事件调试处理时发生异常", e); } } else { if(sync) { BotEventHandler.executor.executorSync(event); } else { BotEventHandler.executor.executor(event); } } } private final static Pattern MESSAGE_PATTERN = Pattern.compile("/\\s*(\".+?\"|[^:\\s])+((\\s*:\\s*(\".+?\"|[^\\s])+)|)|(\".+?\"|[^\"\\s])+"); /** * 以事件形式处理消息事件 * @param event 消息事件对象 */ @SuppressWarnings("unused") public void processMessage(MessageEvent event) { String msg = event.getMessage(); log.debug(event.toString()); if(mismatch(msg)) { return; } Matcher matcher = MESSAGE_PATTERN.matcher(Strings.nullToEmpty(msg)); List argsList = new ArrayList<>(); while (matcher.find()) { String arg = matcher.group(); int startIndex = 0; int endIndex = arg.length(); if(arg.startsWith("\"")) { while(arg.indexOf("\"", startIndex) == startIndex) { startIndex++; } } if(arg.endsWith("\"")) { while(arg.charAt(endIndex - 1) == '\"') { endIndex--; } } argsList.add(arg.substring(startIndex, endIndex)); } String[] args = new String[argsList.size()]; argsList.toArray(args); log.debug("传入参数: {}", Arrays.toString(args)); argsList.add("-$fromGroup"); argsList.add(String.valueOf(event.getFromGroup())); argsList.add("-$fromQQ"); argsList.add(String.valueOf(event.getFromQQ())); args = Arrays.copyOf(args, args.length + 4); argsList.toArray(args); String[] runnerArguments = args.length <= 1 ? new String[0] : Arrays.copyOfRange(args, 1, args.length); log.info("正在处理命令..."); long time = System.currentTimeMillis(); Object result; try { if(msg.toLowerCase().startsWith(ADMIN_COMMAND_PREFIX)) { if(!String.valueOf(event.getFromQQ()) .equals(SettingProperties.getProperty(0, "admin.adminId"))) { result = "你没有执行该命令的权限!"; } else { result = adminRunner.run(runnerArguments); } } else { result = processRunner.run(runnerArguments); } } catch(NoSuchCommandException e) { result = "没有这个命令!请使用“.cgj”查看帮助说明!"; } catch(ParameterNoFoundException e) { result = "命令缺少参数: " + e.getParameterName(); } catch(DeveloperRunnerException e) { Throwable cause = e.getCause(); if (cause instanceof InterruptedException) { log.error("命令执行超时, 终止执行.", cause); result = "色图姬查阅图库太久,被赶出来了!"; } else if(cause instanceof NoSuchElementException && cause.getMessage().startsWith("No work found: ")) { String message = cause.getMessage(); log.error("指定作品不存在.(Id: {})", message.substring(message.lastIndexOf(": ") + 2)); result = "色图姬找不到这个作品!"; } else { log.error("执行命令时发生异常", e); result = "色图姬在执行命令时遇到了一个错误!"; } } long processTime = System.currentTimeMillis() - time; if(!Objects.isNull(result) && result instanceof String) { try { int sendResult = event.sendMessage((String) result); if (sendResult < 0) { log.warn("消息发送失败, Sender {} 返回错误代码: {}", event.getClass().getName(), sendResult); } } catch(InterruptedException e) { log.info("事件在发送消息时超时, 重新投递该事件.(Event: {})", event); EventExecutor.resendCurrentEvent(); } catch (Exception e) { log.error("发送消息时发生异常", e); } } long totalTime = System.currentTimeMillis() - time; log.info("命令反馈完成.(事件耗时: {}ms, P: {}%({}ms), R: {}%({}ms))", totalTime, String.format("%.3f", ((double) processTime / (double)totalTime) * 100F), processTime, String.format("%.3f", ((double) (totalTime - processTime) / (double)totalTime) * 100F), totalTime - processTime); } /** * 检查消息是否需要提交 * @param message 要检查的消息 * @return 如果为true则提交 */ public static boolean mismatch(String message) { return !message.startsWith(COMMAND_PREFIX) && !message.startsWith(ADMIN_COMMAND_PREFIX); } }