diff --git a/gradle.properties b/gradle.properties index 9d43267..d8d160f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ MORNY_ARCHIVE_NAME = morny-coeur MORNY_CODE_STORE = https://github.com/Eyre-S/Coeur-Morny-Cono MORNY_COMMIT_PATH = https://github.com/Eyre-S/Coeur-Morny-Cono/commit/%s -VERSION = 1.0.0-alpha5 +VERSION = 1.0.0-alpha6 USE_DELTA = false VERSION_DELTA = diff --git a/src/main/java/cc/sukazyo/cono/morny/MornyCoeur.java b/src/main/java/cc/sukazyo/cono/morny/MornyCoeur.java index d887b99..49f8655 100644 --- a/src/main/java/cc/sukazyo/cono/morny/MornyCoeur.java +++ b/src/main/java/cc/sukazyo/cono/morny/MornyCoeur.java @@ -60,6 +60,8 @@ public class MornyCoeur { */ public static final long coeurStartTimestamp = ServerMain.systemStartupTime; + private Object whileExitReason = null; + private record LogInResult(TelegramBot account, String username, long userid) { } /** @@ -152,7 +154,6 @@ public class MornyCoeur { * 用于退出时进行缓存的任务处理等进行安全退出 */ private void exitCleanup () { - logger.info("clean:save tracker data."); MornyDaemons.stop(); if (config.commandLogoutClear) { commandManager.automaticRemoveList(); @@ -304,4 +305,13 @@ public class MornyCoeur { public static long getUserid () { return INSTANCE.userid; } + public static void exit (int status, Object reason) { + INSTANCE.whileExitReason = reason; + System.exit(status); + } + + public static Object getExitReason () { + return INSTANCE.whileExitReason; + } + } diff --git a/src/main/java/cc/sukazyo/cono/morny/MornyConfig.java b/src/main/java/cc/sukazyo/cono/morny/MornyConfig.java index 0e02f9f..be61e15 100644 --- a/src/main/java/cc/sukazyo/cono/morny/MornyConfig.java +++ b/src/main/java/cc/sukazyo/cono/morny/MornyConfig.java @@ -2,11 +2,20 @@ package cc.sukazyo.cono.morny; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.lang.annotation.*; import java.util.HashSet; import java.util.Set; public class MornyConfig { + /** + * 表示一个字段的值属于敏感数据,不应该被执行打印等操作。 + */ + @Retention(RetentionPolicy.RUNTIME) + @Documented + @Target({ElementType.FIELD, ElementType.METHOD}) + public @interface Sensitive {} + /* ======================================= * * Config props Names Definition * * ======================================= */ @@ -37,7 +46,7 @@ public class MornyConfig { *

* 这个值必须设定。 */ - @Nonnull public final String telegramBotKey; + @Nonnull @Sensitive public final String telegramBotKey; /** * morny 所使用的 bot 的 username. *

diff --git a/src/main/java/cc/sukazyo/cono/morny/bot/command/MornyCommands.java b/src/main/java/cc/sukazyo/cono/morny/bot/command/MornyCommands.java index 5925046..9ad3087 100644 --- a/src/main/java/cc/sukazyo/cono/morny/bot/command/MornyCommands.java +++ b/src/main/java/cc/sukazyo/cono/morny/bot/command/MornyCommands.java @@ -213,7 +213,7 @@ public class MornyCommands { ).replyToMessageId(event.message().messageId()) ); logger.info("Morny exited by user " + TGToString.as(event.message().from()).toStringLogTag()); - System.exit(0); + MornyCoeur.exit(0, event.message().from()); } else { MornyCoeur.extra().exec(new SendSticker( event.message().chat().id(), diff --git a/src/main/java/cc/sukazyo/cono/morny/bot/event/OnCallMsgSend.java b/src/main/java/cc/sukazyo/cono/morny/bot/event/OnCallMsgSend.java index 0b362f9..b4eaaec 100644 --- a/src/main/java/cc/sukazyo/cono/morny/bot/event/OnCallMsgSend.java +++ b/src/main/java/cc/sukazyo/cono/morny/bot/event/OnCallMsgSend.java @@ -27,12 +27,12 @@ import static cc.sukazyo.cono.morny.util.tgapi.formatting.MsgEscape.escapeHtml; public class OnCallMsgSend extends EventListener { - private static final Pattern REGEX_MSG_SENDREQ_DATA_HEAD = Pattern.compile("^\\*msg([\\d-]+)(\\*\\S+)?\\n([\\s\\S]+)$"); + private static final Pattern REGEX_MSG_SENDREQ_DATA_HEAD = Pattern.compile("^\\*msg(-?\\d+)(\\*\\S+)?(?:\\n([\\s\\S]+))?$"); private record MessageToSend ( - String message, - MessageEntity[] entities, - ParseMode parseMode, + @Nullable String message, + @Nullable MessageEntity[] entities, + @Nullable ParseMode parseMode, long targetId ) { } @@ -62,7 +62,7 @@ public class OnCallMsgSend extends EventListener { // 发送体处理 if (update.message().replyToMessage() == null) return answer404(update); msgsendReqBody = parseRequest(update.message().replyToMessage()); - if (msgsendReqBody == null) return answer404(update); + if (msgsendReqBody == null || msgsendReqBody.message == null) return answer404(update); // 执行发送任务 SendResponse sendResponse = MornyCoeur.getAccount().execute(parseMessageToSend(msgsendReqBody)); if (!sendResponse.isOk()) { // 发送失败 @@ -150,7 +150,8 @@ public class OnCallMsgSend extends EventListener { ).replyToMessageId(update.message().messageId()).parseMode(ParseMode.HTML)); } // 发送文本测试 - SendResponse testSendResp = MornyCoeur.getAccount().execute( + if (msgsendReqBody.message == null) return true; + final SendResponse testSendResp = MornyCoeur.getAccount().execute( parseMessageToSend(msgsendReqBody, update.message().chat().id()).replyToMessageId(update.message().messageId()) ); if (!testSendResp.isOk()) { diff --git a/src/main/java/cc/sukazyo/cono/morny/daemon/MornyDaemons.java b/src/main/java/cc/sukazyo/cono/morny/daemon/MornyDaemons.java index d40ba47..710a07a 100644 --- a/src/main/java/cc/sukazyo/cono/morny/daemon/MornyDaemons.java +++ b/src/main/java/cc/sukazyo/cono/morny/daemon/MornyDaemons.java @@ -1,5 +1,7 @@ package cc.sukazyo.cono.morny.daemon; +import cc.sukazyo.cono.morny.MornyCoeur; + import static cc.sukazyo.cono.morny.Log.logger; public class MornyDaemons { @@ -10,6 +12,7 @@ public class MornyDaemons { logger.info("ALL Morny Daemons starting..."); // TrackerDataManager.init(); medicationTimerInstance.start(); + MornyReport.onMornyLogIn(); logger.info("Morny Daemons started."); } @@ -23,6 +26,7 @@ public class MornyDaemons { // TrackerDataManager.trackingLock.lock(); try { medicationTimerInstance.join(); } catch (InterruptedException e) { e.printStackTrace(System.out); } + MornyReport.onMornyExit(MornyCoeur.getExitReason()); logger.info("ALL Morny Daemons STOPPED."); } diff --git a/src/main/java/cc/sukazyo/cono/morny/daemon/MornyReport.java b/src/main/java/cc/sukazyo/cono/morny/daemon/MornyReport.java index a6d37e2..c79b223 100644 --- a/src/main/java/cc/sukazyo/cono/morny/daemon/MornyReport.java +++ b/src/main/java/cc/sukazyo/cono/morny/daemon/MornyReport.java @@ -2,6 +2,7 @@ package cc.sukazyo.cono.morny.daemon; import cc.sukazyo.cono.morny.Log; import cc.sukazyo.cono.morny.MornyCoeur; +import cc.sukazyo.cono.morny.MornyConfig; import cc.sukazyo.cono.morny.util.tgapi.event.EventRuntimeException; import cc.sukazyo.cono.morny.util.tgapi.formatting.TGToString; import com.google.gson.GsonBuilder; @@ -14,6 +15,9 @@ import com.pengrad.telegrambot.response.BaseResponse; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.lang.reflect.Field; + +import static cc.sukazyo.cono.morny.Log.logger; import static cc.sukazyo.cono.morny.util.tgapi.formatting.MsgEscape.escapeHtml; public class MornyReport { @@ -23,8 +27,10 @@ public class MornyReport { try { MornyCoeur.extra().exec(report); } catch (EventRuntimeException.ActionFailed e) { - Log.logger.warn("cannot execute report to telegram:"); - Log.logger.warn(Log.exceptionLog(e)); + logger.warn("cannot execute report to telegram:"); + logger.warn(Log.exceptionLog(e).indent(4)); + logger.warn("tg-api response:"); + logger.warn(e.getResponse().toString().indent(4)); } } @@ -64,4 +70,85 @@ public class MornyReport { ).parseMode(ParseMode.HTML)); } + /** + * morny 登陆时的报告发送,包含已登录的账号 id 以及启动配置。 + * @since 1.0.0-alpha6 + */ + public static void onMornyLogIn () { + executeReport(new SendMessage( + MornyCoeur.config().reportToChat, + String.format(""" + ▌Morny Logged in + as user @%s + + as config fields: + %s + """, + MornyCoeur.getUsername(), + sectionConfigFields(MornyCoeur.config()) + ) + ).parseMode(ParseMode.HTML)); + } + + /** + * 返回一个 config 字段与值的列表,可以作为 telegram html 格式输出 + * @since 1.0.0-alpha6 + */ + private static String sectionConfigFields (@Nonnull MornyConfig config) { + final StringBuilder echo = new StringBuilder(); + for (Field field : config.getClass().getFields()) { + echo.append("- ").append(field.getName()).append(" "); + try { + if (field.isAnnotationPresent(MornyConfig.Sensitive.class)) { + echo.append(": sensitive_field"); + } else { + final Object fieldValue = field.get(config); + echo.append("= "); + if (fieldValue == null) + echo.append("null"); + else echo.append("").append(escapeHtml(fieldValue.toString())).append(""); + } + } catch (IllegalAccessException | IllegalArgumentException | NullPointerException e) { + echo.append(": ").append(escapeHtml("")).append(""); + logger.error("error while reading config field " + field.getName()); + logger.error(Log.exceptionLog(e)); + exception(e, "error while reading config field " + field.getName()); + } + echo.append("\n"); + } + return echo.substring(0, echo.length()-1); + } + + /** + * morny 关闭/登出时发送的报告. + *

+ * 基于 java 的程序关闭钩子,因此仍然无法在意外宕机的情况下发送报告. + * @param causedBy + * 关闭的原因。 + * 可以使用 {@link User Telegram 用户对象} 表示由一个用户执行了关闭, + * 传入其它数据将使用 {@code #toString} 输出其内容。 + * 传入 {@link null} 则表示不表明原因。 + */ + static void onMornyExit (@Nullable Object causedBy) { + if (!MornyCoeur.available()) return; + String causedTag = null; + if (causedBy != null) { + if (causedBy instanceof User) + causedTag = TGToString.as((User)causedBy).fullnameRefHtml(); + else + causedTag = "" + escapeHtml(causedBy.toString()) + ""; + } + executeReport(new SendMessage( + MornyCoeur.config().reportToChat, + String.format(""" + ▌Morny Exited + from user @%s + %s + """, + MornyCoeur.getUsername(), + causedBy == null ? "with UNKNOWN reason" : "\nby " + causedTag + ) + ).parseMode(ParseMode.HTML)); + } + }