diff --git a/build.gradle b/build.gradle index 4d45291..f0041ee 100644 --- a/build.gradle +++ b/build.gradle @@ -52,7 +52,7 @@ final JavaVersion proj_java = JavaVersion.VERSION_17 final Charset proj_file_encoding = StandardCharsets.UTF_8 final proj_scala_api = 3 //final proj_scala_lib = proj_scala_api+'.4.0-RC1-bin-20230901-89e8dba-NIGHTLY' -final proj_scala_lib = proj_scala_api+'.3.1-RC7' +final proj_scala_lib = proj_scala_api+'.3.1' String publish_local_url = null String publish_remote_url = null String publish_remote_username = null @@ -89,35 +89,26 @@ dependencies { } -sourceSets { - main { - scala { srcDirs = ['src/main/scala', 'src/main/old'] } - } +tasks.withType(JavaCompile).configureEach { + + sourceCompatibility proj_java.getMajorVersion() + targetCompatibility proj_java.getMajorVersion() + + options.encoding = proj_file_encoding.name() + } -scala { +tasks.withType(ScalaCompile).configureEach { - compileJava { - - sourceCompatibility proj_java.getMajorVersion() - targetCompatibility proj_java.getMajorVersion() - - options.encoding = proj_file_encoding.name() - - } + sourceCompatibility proj_java.getMajorVersion() + targetCompatibility proj_java.getMajorVersion() - compileScala { - - sourceCompatibility proj_java.getMajorVersion() - targetCompatibility proj_java.getMajorVersion() - - options.encoding = proj_file_encoding.name() - scalaCompileOptions.encoding = proj_file_encoding.name() - -// scalaCompileOptions.additionalParameters.add("-Yexplicit-nulls") - - } + options.encoding = proj_file_encoding.name() + scalaCompileOptions.encoding = proj_file_encoding.name() + scalaCompileOptions.additionalParameters.add "-language:postfixOps" +// scalaCompileOptions.additionalParameters.add("-Yexplicit-nulls") + } test { diff --git a/gradle.properties b/gradle.properties index 4f1df34..9ce1455 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ MORNY_COMMIT_PATH = https://github.com/Eyre-S/Coeur-Morny-Cono/commit/%s VERSION = 1.0.0-RC4 USE_DELTA = true -VERSION_DELTA = scalaport2 +VERSION_DELTA = scalaport3 CODENAME = beiping diff --git a/src/main/old/cc/sukazyo/cono/morny/util/BiliTool.java b/src/main/java/cc/sukazyo/cono/morny/util/BiliTool.java similarity index 100% rename from src/main/old/cc/sukazyo/cono/morny/util/BiliTool.java rename to src/main/java/cc/sukazyo/cono/morny/util/BiliTool.java diff --git a/src/main/old/cc/sukazyo/cono/morny/util/CommonConvert.java b/src/main/java/cc/sukazyo/cono/morny/util/CommonConvert.java similarity index 100% rename from src/main/old/cc/sukazyo/cono/morny/util/CommonConvert.java rename to src/main/java/cc/sukazyo/cono/morny/util/CommonConvert.java diff --git a/src/main/old/cc/sukazyo/cono/morny/util/CommonEncrypt.java b/src/main/java/cc/sukazyo/cono/morny/util/CommonEncrypt.java similarity index 100% rename from src/main/old/cc/sukazyo/cono/morny/util/CommonEncrypt.java rename to src/main/java/cc/sukazyo/cono/morny/util/CommonEncrypt.java diff --git a/src/main/old/cc/sukazyo/cono/morny/util/CommonFormat.java b/src/main/java/cc/sukazyo/cono/morny/util/CommonFormat.java similarity index 100% rename from src/main/old/cc/sukazyo/cono/morny/util/CommonFormat.java rename to src/main/java/cc/sukazyo/cono/morny/util/CommonFormat.java diff --git a/src/main/old/cc/sukazyo/cono/morny/util/CommonRandom.java b/src/main/java/cc/sukazyo/cono/morny/util/CommonRandom.java similarity index 100% rename from src/main/old/cc/sukazyo/cono/morny/util/CommonRandom.java rename to src/main/java/cc/sukazyo/cono/morny/util/CommonRandom.java diff --git a/src/main/old/cc/sukazyo/cono/morny/util/FileUtils.java b/src/main/java/cc/sukazyo/cono/morny/util/FileUtils.java similarity index 100% rename from src/main/old/cc/sukazyo/cono/morny/util/FileUtils.java rename to src/main/java/cc/sukazyo/cono/morny/util/FileUtils.java diff --git a/src/main/old/cc/sukazyo/cono/morny/util/OkHttpPublic.java b/src/main/java/cc/sukazyo/cono/morny/util/OkHttpPublic.java similarity index 100% rename from src/main/old/cc/sukazyo/cono/morny/util/OkHttpPublic.java rename to src/main/java/cc/sukazyo/cono/morny/util/OkHttpPublic.java diff --git a/src/main/old/cc/sukazyo/cono/morny/util/UniversalCommand.java b/src/main/java/cc/sukazyo/cono/morny/util/UniversalCommand.java similarity index 100% rename from src/main/old/cc/sukazyo/cono/morny/util/UniversalCommand.java rename to src/main/java/cc/sukazyo/cono/morny/util/UniversalCommand.java diff --git a/src/main/old/cc/sukazyo/cono/morny/util/tgapi/ExtraAction.java b/src/main/java/cc/sukazyo/cono/morny/util/tgapi/ExtraAction.java similarity index 100% rename from src/main/old/cc/sukazyo/cono/morny/util/tgapi/ExtraAction.java rename to src/main/java/cc/sukazyo/cono/morny/util/tgapi/ExtraAction.java diff --git a/src/main/old/cc/sukazyo/cono/morny/util/tgapi/InputCommand.java b/src/main/java/cc/sukazyo/cono/morny/util/tgapi/InputCommand.java similarity index 100% rename from src/main/old/cc/sukazyo/cono/morny/util/tgapi/InputCommand.java rename to src/main/java/cc/sukazyo/cono/morny/util/tgapi/InputCommand.java diff --git a/src/main/old/cc/sukazyo/cono/morny/util/tgapi/Standardize.java b/src/main/java/cc/sukazyo/cono/morny/util/tgapi/Standardize.java similarity index 100% rename from src/main/old/cc/sukazyo/cono/morny/util/tgapi/Standardize.java rename to src/main/java/cc/sukazyo/cono/morny/util/tgapi/Standardize.java diff --git a/src/main/old/cc/sukazyo/cono/morny/util/tgapi/event/EventRuntimeException.java b/src/main/java/cc/sukazyo/cono/morny/util/tgapi/event/EventRuntimeException.java similarity index 100% rename from src/main/old/cc/sukazyo/cono/morny/util/tgapi/event/EventRuntimeException.java rename to src/main/java/cc/sukazyo/cono/morny/util/tgapi/event/EventRuntimeException.java diff --git a/src/main/old/cc/sukazyo/cono/morny/util/tgapi/formatting/MsgEscape.java b/src/main/java/cc/sukazyo/cono/morny/util/tgapi/formatting/MsgEscape.java similarity index 100% rename from src/main/old/cc/sukazyo/cono/morny/util/tgapi/formatting/MsgEscape.java rename to src/main/java/cc/sukazyo/cono/morny/util/tgapi/formatting/MsgEscape.java diff --git a/src/main/old/cc/sukazyo/cono/morny/util/tgapi/formatting/NamedUtils.java b/src/main/java/cc/sukazyo/cono/morny/util/tgapi/formatting/NamedUtils.java similarity index 100% rename from src/main/old/cc/sukazyo/cono/morny/util/tgapi/formatting/NamedUtils.java rename to src/main/java/cc/sukazyo/cono/morny/util/tgapi/formatting/NamedUtils.java diff --git a/src/main/old/cc/sukazyo/cono/morny/util/tgapi/formatting/TGToString.java b/src/main/java/cc/sukazyo/cono/morny/util/tgapi/formatting/TGToString.java similarity index 100% rename from src/main/old/cc/sukazyo/cono/morny/util/tgapi/formatting/TGToString.java rename to src/main/java/cc/sukazyo/cono/morny/util/tgapi/formatting/TGToString.java diff --git a/src/main/old/cc/sukazyo/cono/morny/util/tgapi/formatting/TGToStringFromChat.java b/src/main/java/cc/sukazyo/cono/morny/util/tgapi/formatting/TGToStringFromChat.java similarity index 100% rename from src/main/old/cc/sukazyo/cono/morny/util/tgapi/formatting/TGToStringFromChat.java rename to src/main/java/cc/sukazyo/cono/morny/util/tgapi/formatting/TGToStringFromChat.java diff --git a/src/main/old/cc/sukazyo/cono/morny/util/tgapi/formatting/TGToStringFromMessage.java b/src/main/java/cc/sukazyo/cono/morny/util/tgapi/formatting/TGToStringFromMessage.java similarity index 100% rename from src/main/old/cc/sukazyo/cono/morny/util/tgapi/formatting/TGToStringFromMessage.java rename to src/main/java/cc/sukazyo/cono/morny/util/tgapi/formatting/TGToStringFromMessage.java diff --git a/src/main/old/cc/sukazyo/cono/morny/util/tgapi/formatting/TGToStringFromUser.java b/src/main/java/cc/sukazyo/cono/morny/util/tgapi/formatting/TGToStringFromUser.java similarity index 100% rename from src/main/old/cc/sukazyo/cono/morny/util/tgapi/formatting/TGToStringFromUser.java rename to src/main/java/cc/sukazyo/cono/morny/util/tgapi/formatting/TGToStringFromUser.java diff --git a/src/main/old/cc/sukazyo/cono/morny/util/tgapi/formatting/TelegramUserInformation.java b/src/main/java/cc/sukazyo/cono/morny/util/tgapi/formatting/TelegramUserInformation.java similarity index 100% rename from src/main/old/cc/sukazyo/cono/morny/util/tgapi/formatting/TelegramUserInformation.java rename to src/main/java/cc/sukazyo/cono/morny/util/tgapi/formatting/TelegramUserInformation.java diff --git a/src/main/old/cc/sukazyo/cono/morny/Log.java b/src/main/old/cc/sukazyo/cono/morny/Log.java deleted file mode 100644 index 57ab124..0000000 --- a/src/main/old/cc/sukazyo/cono/morny/Log.java +++ /dev/null @@ -1,58 +0,0 @@ -package cc.sukazyo.cono.morny; - -import cc.sukazyo.messiva.formatter.SimpleFormatter; -import cc.sukazyo.messiva.log.LogLevel; -import cc.sukazyo.messiva.logger.Logger; -import cc.sukazyo.messiva.appender.ConsoleAppender; - -import javax.annotation.Nonnull; -import java.io.PrintWriter; -import java.io.StringWriter; - -/** - * Morny 的 log 管理器 - */ -public class Log { - - /** - * Morny 的 Logger 实例, - * messiva 更新 - * @since 0.4.1.1 - */ - public static final Logger logger = new Logger(new ConsoleAppender(new SimpleFormatter())).minLevel(LogLevel.INFO); - - /** - * Is the Debug mode enabled. - * - * @return if the minimal log level is equal or lower than DEBUG level. - */ - public static boolean debug () { - return logger.levelSetting.minLevel().level <= LogLevel.DEBUG.level; - } - - /** - * Switch the Debug log output enabled. - *
- * if enable the debug log output, all the Log regardless of LogLevel will be output. - * As default, if the debug log output is disabled, Logger will ignore the Logs level lower than INFO. - * - * @param debug switch enable the debug log output as true, or disable it as false. - */ - public static void debug (boolean debug) { - if (debug) logger.minLevel(LogLevel.ALL); - else logger.minLevel(LogLevel.INFO); - } - - /** - * 获取异常的堆栈信息. - * @param e 异常体 - * @return {@link String} 格式的异常的堆栈报告信息. - * @see 1.0.0-alpha5 - */ - public static String exceptionLog (@Nonnull Throwable e) { - final StringWriter stackTrace = new StringWriter(); - e.printStackTrace(new PrintWriter(stackTrace)); - return stackTrace.toString(); - } - -} diff --git a/src/main/old/cc/sukazyo/cono/morny/MornyAbout.java b/src/main/old/cc/sukazyo/cono/morny/MornyAbout.java deleted file mode 100644 index 3933d9b..0000000 --- a/src/main/old/cc/sukazyo/cono/morny/MornyAbout.java +++ /dev/null @@ -1,31 +0,0 @@ -package cc.sukazyo.cono.morny; - -import java.io.IOException; - -/** - * Some of the static information of Morny. - */ -public class MornyAbout { - - /** - * ASCII art of Morny Featured Image. - *
- * used for Coeur starting welcome screen. - *
- * stored at /assets_morny/texts/server-hello.txt
- */
- public static final String MORNY_PREVIEW_IMAGE_ASCII;
- static {
- try {
- MORNY_PREVIEW_IMAGE_ASCII = MornyAssets.pack.getResource("texts/server-hello.txt").readAsString();
- } catch (IOException e) {
- throw new RuntimeException("Cannot read MORNY_PREVIEW_IMAGE_ASCII from assets pack", e);
- }
- }
-
- public static final String MORNY_SOURCECODE_LINK = "https://github.com/Eyre-S/Coeur-Morny-Cono";
- public static final String MORNY_SOURCECODE_SELF_HOSTED_MIRROR_LINK = "https://storage.sukazyo.cc/Eyre_S/Coeur-Morny-Cono";
- public static final String MORNY_ISSUE_TRACKER_LINK = "https://github.com/Eyre-S/Coeur-Morny-Cono/issues";
- public static final String MORNY_USER_GUIDE_LINK = "https://book.sukazyo.cc/morny";
-
-}
diff --git a/src/main/old/cc/sukazyo/cono/morny/MornyAssets.java b/src/main/old/cc/sukazyo/cono/morny/MornyAssets.java
deleted file mode 100644
index 1fdfe7f..0000000
--- a/src/main/old/cc/sukazyo/cono/morny/MornyAssets.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package cc.sukazyo.cono.morny;
-
-import cc.sukazyo.restools.ResourcesPackage;
-
-/**
- * Morny assets manager.
- *
- * @see #pack
- * @since 1.0.0-RC4
- */
-public class MornyAssets {
-
- /**
- * Instance mirror of the Morny assets, the assets root is /src/main/resources/assets_morny/.
- * @since 1.0.0-RC4
- */
- public static final ResourcesPackage pack = new ResourcesPackage(MornyAssets.class, "assets_morny");
-
-}
diff --git a/src/main/old/cc/sukazyo/cono/morny/MornyCoeur.java b/src/main/old/cc/sukazyo/cono/morny/MornyCoeur.java
deleted file mode 100644
index e6d201d..0000000
--- a/src/main/old/cc/sukazyo/cono/morny/MornyCoeur.java
+++ /dev/null
@@ -1,310 +0,0 @@
-package cc.sukazyo.cono.morny;
-
-import cc.sukazyo.cono.morny.bot.api.TelegramUpdatesListener$;
-import cc.sukazyo.cono.morny.bot.command.MornyCommands;
-import cc.sukazyo.cono.morny.bot.event.MornyEventListeners;
-import cc.sukazyo.cono.morny.bot.query.MornyQueries;
-import cc.sukazyo.cono.morny.daemon.MornyDaemons;
-import cc.sukazyo.cono.morny.daemon.TrackerDataManager;
-import cc.sukazyo.cono.morny.util.tgapi.ExtraAction;
-import com.pengrad.telegrambot.TelegramBot;
-import com.pengrad.telegrambot.impl.FileApi;
-import com.pengrad.telegrambot.model.User;
-import com.pengrad.telegrambot.request.GetMe;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import static cc.sukazyo.cono.morny.Log.logger;
-
-/**
- * Morny Cono 核心
- * - 的程序化入口类,保管着 morny 的核心属性
- */
-public class MornyCoeur {
-
- /** 当前程序的 Morny Coeur 实例 */
- private static MornyCoeur INSTANCE;
-
- /** 当前 Morny 的启动配置 */
- public final MornyConfig config;
-
- /** 当前 Morny 的{@link MornyTrusted 信任验证机}实例 */
- private final MornyTrusted trusted;
- private final MornyQueries queryManager = new MornyQueries();
-
- /** morny 的 bot 账户 */
- private final TelegramBot account;
- private final ExtraAction extraActionInstance;
- /**
- * morny 的 bot 账户的用户名
- *
- * 这个字段将会在登陆成功后赋值为登录到的 bot 的 username。
- * 它应该是和 {@link #account} 的 username 同步的
- *
- * 如果在登陆之前就定义了此字段,则登陆代码会验证登陆的 bot 的 username
- * 是否与定义的 username 符合。如果不符合则会报错。
- */
- public final String username;
- /**
- * morny 的 bot 账户的 telegram id
- *
- * 这个字段将会在登陆成功后赋值为登录到的 bot 的 id。
- */
- public final long userid;
- /**
- * morny 主程序启动时间
- * 用于统计数据
- */
- public static final long coeurStartTimestamp = ServerMain.systemStartupTime();
-
- private Object whileExitReason = null;
-
- private record LogInResult(TelegramBot account, String username, long userid) { }
-
- /**
- * 执行 bot 初始化
- *
- * @param config Morny 实例的配置选项数据
- */
- private MornyCoeur (MornyConfig config) {
-
- this.config = config;
-
- configureSafeExit();
-
- logger.info("args key:\n " + config.telegramBotKey);
- if (config.telegramBotUsername != null) {
- logger.info("login as:\n " + config.telegramBotUsername);
- }
-
- try {
- final LogInResult loginResult = login(config.telegramBotApiServer, config.telegramBotApiServer4File, config.telegramBotKey, config.telegramBotUsername);
- this.account = loginResult.account;
- this.username = loginResult.username;
- this.userid = loginResult.userid;
- this.trusted = new MornyTrusted(this);
- StringBuilder trustedReadersDinnerIds = new StringBuilder();
- trusted.getTrustedReadersOfDinnerSet().forEach(id -> trustedReadersDinnerIds.append("\n ").append(id));
- logger.info(String.format("""
- trusted param set:
- - master (id)
- %d
- - trusted chat (id)
- %d
- - trusted reader-of-dinner (id)%s""",
- config.trustedMaster, config.trustedChat, trustedReadersDinnerIds
- ));
- }
- catch (Exception e) {
- RuntimeException ex = new RuntimeException("Cannot login to bot/api. :\n " + e.getMessage());
- logger.error(ex.getMessage());
- throw ex;
- }
-
- this.extraActionInstance = ExtraAction.as(account);
-
- logger.info("Bot login succeed.");
-
- }
-
- /**
- * 向外界暴露的 morny 初始化入口.
- *
- * 如果 morny 已经初始化,则不会进行初始化,抛出错误消息并直接退出方法。 - * - * @see #MornyCoeur 程序初始化方法 - * @param config morny 实例的配置选项数据 - */ - public static void init (MornyConfig config) { - if (INSTANCE == null) { - - logger.info("Coeur Starting"); - INSTANCE = new MornyCoeur(config); - - MornyDaemons.start(); - - logger.info("start telegram events listening"); - MornyEventListeners.registerAllEvents(); - INSTANCE.account.setUpdatesListener(TelegramUpdatesListener$.MODULE$); - - if (config.commandLoginRefresh) { - logger.info("resetting telegram command list"); - MornyCommands.automaticTGListUpdate(); - } - - logger.info("Coeur start complete"); - return; - - } - logger.error("Coeur already started!!!"); - } - - /** - * 向所有的数据管理器发起保存数据的指令 - * @since 0.4.3.0 - */ - public void saveDataAll () { - TrackerDataManager.save(); - } - - /** - * 用于退出时进行缓存的任务处理等进行安全退出 - */ - private void exitCleanup () { - MornyDaemons.stop(); - if (config.commandLogoutClear) { - MornyCommands.automaticTGListRemove(); - } - } - - /** - * 为程序在虚拟机上添加退出钩子 - */ - private void configureSafeExit () { - Runtime.getRuntime().addShutdownHook(new Thread(this::exitCleanup, "exit-cleaning")); - } - - /** - * 登录 bot. - *
- * 会反复尝试三次进行登录。如果登录失败,则会直接抛出 RuntimeException 结束处理。 - * 会通过 GetMe 动作验证是否连接上了 telegram api 服务器, - * 同时也要求登录获得的 username 和 {@link #username} 声明值相等 - * - * @param api bot client 将会连接到的 telegram bot api 位置。 - * 填入 {@code null} 则使用默认的 {@code "https://api.telegram.org/bot"} - * @param api4File bot client 将会连接到的 telegram file api 位置。 - * 如果传入 {@code null} 则会跟随 {@param api} 选项的设定(具体为在 {@param api} 路径的后面添加 {@code /file} 路径)。 - * 如果两者都为 {@code null},则跟随默认的 {@value FileApi#FILE_API} - * @param key bot 的 api-token. 必要值 - * @param requireName 要求登录到的需要的 username,如果登陆后的 username 与此不同则会报错退出。 - * 填入 {@code null} 则表示不对 username 作要求 - * @return 成功登录后的 {@link TelegramBot} 对象 - */ - @Nonnull - private static LogInResult login ( - @Nullable String api, @Nullable String api4File, - @Nonnull String key, @Nullable String requireName - ) { - final TelegramBot.Builder accountConfig = new TelegramBot.Builder(key); - boolean isCustomApi = false; - String apiUrlSet = "https://api.telegram.org/bot"; - String api4FileUrlSet = FileApi.FILE_API; - if (api != null) { - api = api.endsWith("/") ? api.substring(0, api.length() - 1) : api; - accountConfig.apiUrl(apiUrlSet = api.endsWith("/bot")? api : api + "/bot"); - isCustomApi = true; - } - if (api4File != null) { - api4File = api4File.endsWith("/") ? api4File : api4File + "/"; - accountConfig.fileApiUrl(api4FileUrlSet = api4File.endsWith("/file/bot")? api4File : api4File + "/file/bot"); - isCustomApi = true; - } else if (api != null && !api.endsWith("/bot")) { - accountConfig.fileApiUrl(api4FileUrlSet = api + "/file/bot"); - } - if (isCustomApi) { - logger.info(String.format(""" - Telegram Bot API set to : - - %s - - %s""", - apiUrlSet, api4FileUrlSet - )); - } - final TelegramBot account = accountConfig.build(); - logger.info("Trying to login..."); - for (int i = 1; i < 4; i++) { - if (i != 1) logger.info("retrying..."); - try { - final User remote = account.execute(new GetMe()).user(); - if (requireName != null && !requireName.equals(remote.username())) - throw new RuntimeException("Required the bot @" + requireName + " but @" + remote.username() + " logged in!"); - logger.info("Succeed login to @" + remote.username()); - return new LogInResult(account, remote.username(), remote.id()); - } catch (Exception e) { - logger.error(Log.exceptionLog(e)); - logger.error("login failed."); - } - } - throw new RuntimeException("Login failed.."); - } - - /** - * @see #saveDataAll() - * @since 0.4.3.0 - */ - public static void callSaveData () { - INSTANCE.saveDataAll(); - logger.info("done all save action."); - } - - /** - * 检查 Coeur 是否已经完成初始化. - * @since 1.0.0-alpha5 - */ - public static boolean available() { - return INSTANCE != null; - } - - /** - * 获取登录成功后的 telegram bot 对象 - * - * @return {@link #account MornyCoeur.account} - */ - @Nonnull - public static TelegramBot getAccount () { - return INSTANCE.account; - } - - /** - * 获取登录 bot 的 username - * - * @return {@link #username MornyCoeur.username} - */ - @Nonnull - public static String getUsername () { - return INSTANCE.username; - } - - /** - * 获取当前 morny 的配置数据 - * - * @return {@link #config MornyCoeur.config} - */ - @Nonnull - public static MornyConfig config () { - return INSTANCE.config; - } - - /** - * 获取 Morny 的{@link MornyTrusted 信任验证机} - * - * @return {@link #trusted MornyCoeur.trusted} - */ - @Nonnull - public static MornyTrusted trustedInstance () { - return INSTANCE.trusted; - } - - @Nonnull - public static MornyQueries queryManager () { - return INSTANCE.queryManager; - } - - @Nonnull - public static ExtraAction extra () { - return INSTANCE.extraActionInstance; - } - - 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/old/cc/sukazyo/cono/morny/MornySystem.java b/src/main/old/cc/sukazyo/cono/morny/MornySystem.java deleted file mode 100644 index edd4719..0000000 --- a/src/main/old/cc/sukazyo/cono/morny/MornySystem.java +++ /dev/null @@ -1,145 +0,0 @@ -package cc.sukazyo.cono.morny; - -import cc.sukazyo.cono.morny.daemon.MornyReport; -import cc.sukazyo.cono.morny.internal.BuildConfigField; -import cc.sukazyo.cono.morny.util.FileUtils; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.io.IOException; -import java.net.URISyntaxException; -import java.security.NoSuchAlgorithmException; - -/** - * Morny Cono 的 Coeur 的程序属性存放类 - */ -public class MornySystem { - - /** - * 程序的语义化版本号. - *
- * 这个版本号包含了以下的 {@link #VERSION_BASE}, {@link #VERSION_DELTA} 字段, - * 但不包含作为附加属性的构建时的{@link BuildConfig#COMMIT git 状态}属性 - *
- * 这个格式的版本号也是在 maven 包仓库中所使用的版本号 - * @since 1.0.0-alpha4 - */ - @BuildConfigField @Nonnull public static final String VERSION = BuildConfig.VERSION; - /** - * 程序的完整语义化版本号. - *
- * 包含了全部的 {@link #VERSION_BASE}, {@link #VERSION_DELTA}, 以及{@link BuildConfig#COMMIT git 状态}属性。 - * 虽然仍旧不包含{@link #CODENAME}属性 - *
- * 这个格式的版本号也是 gradle 构建配置使用的版本号,也在普通打包时生成文件时使用 - * @since 1.0.0-alpha4.2 - */ - @BuildConfigField @Nonnull public static final String VERSION_FULL = BuildConfig.VERSION_FULL; - /** - * 程序的基础版本号. - *
- * 它只包含了版本号中的主要信息:例如 {@code 0.8.0.5}, {@code 1.0.0-alpha-3}, - * 而不会有用于精确定义的 {@link #VERSION_DELTA} 字段和作为附加使用的 {@link BuildConfig#COMMIT git commit 信息} - * @since 1.0.0-alpha4 - */ - @BuildConfigField @Nonnull public static final String VERSION_BASE = BuildConfig.VERSION_BASE; - /** - * 程序的版本 delta. - * —— 设计上用于在一个基版本当中分出不同构建的版本. - *
- * {@link null} 作为值,表示这个字段没有被使用. - *
- * 版本 delta 会以 {@code -δversion-delta} 的形式附着在 {@link #VERSION_BASE} 之后. - * 两者合并后的版本号格式即为 {@link #VERSION} - *
- * 在发行版本中一般不应该被使用. - *
- * 目前并不多被使用. - * @since 1.0.0-alpha4 - */ - @BuildConfigField @Nullable public static final String VERSION_DELTA = BuildConfig.VERSION_DELTA; - - /** - * Morny Coeur 当前的版本代号. - *
- * 一个单个单词,一般作为一个大版本的名称,只在重大更新改变
- * 格式保持为仅由小写字母和数字组成
- * 有时也可能是复合词或特殊的词句
- *
- */
- @BuildConfigField @Nonnull public static final String CODENAME = BuildConfig.CODENAME;
-
- /**
- * Coeur 的代码仓库的链接. 它应该链接到当前程序的源码主页.
- *
- * {@link null} 表示这个属性在构建时未被设置(或没有源码主页) - * @since 1.0.0-alpha4 - */ - @BuildConfigField @Nullable public static final String CODE_STORE = BuildConfig.CODE_STORE; - /** - * Coeur 的 git commit 链接. - *
- * 它应该是一个可以通过 {@link String#format(String, Object...)} 要求格式的链接模板,带有一个 {@link String} 类型的槽位 ——
- * 通过 String.format(COMMIT_PATH, {@link BuildConfig#COMMIT})
即可取得当前当前程序所基于的 commit 的链接。
- * @since 1.0.0-alpha4
- */
- @BuildConfigField @Nullable public static final String COMMIT_PATH = BuildConfig.COMMIT_PATH;
-
- /** @see #VERSION_DELTA */
- @BuildConfigField
- public static boolean isUseDelta () { return VERSION_DELTA != null; }
-
- /** @see BuildConfig#COMMIT */
- @BuildConfigField
- @SuppressWarnings("ConstantConditions")
- public static boolean isGitBuild () { return BuildConfig.COMMIT != null; }
-
- /** @see BuildConfig#COMMIT */
- @BuildConfigField
- public static boolean isCleanBuild () { return BuildConfig.CLEAN_BUILD; }
-
- /**
- * 获取程序的当前构建所基于的 git commit 的链接.
- *
- * 如果 {@link #COMMIT_PATH}(一般表示没有公开储存库)
- * 或是 {@link BuildConfig#COMMIT}(一般表示程序的构建环境没有使用 git)
- * 任何一个不可用,则此方法也不可用。
- *
- * @return 当前构建的 git commit 链接,为空则表示不可用。
- * @see #COMMIT_PATH
- * @since 1.0.0-alpha4
- */
- @Nullable
- @BuildConfigField
- @SuppressWarnings("ConstantConditions")
- public static String currentCodePath () {
- if (COMMIT_PATH == null || !isGitBuild()) return null;
- return String.format(COMMIT_PATH, BuildConfig.COMMIT);
- }
-
-
-
- /**
- * 获取程序 jar 文件的 md5-hash 值
- * 命令将在 {@link cc.sukazyo.cono.morny.bot.command.MornyCommands} 当中实例化并注册管理,并通过事件
- * {@link cc.sukazyo.cono.morny.bot.event.OnTelegramCommand} 调用.
- */
-package cc.sukazyo.cono.morny.bot.command;
diff --git a/src/main/old/cc/sukazyo/cono/morny/bot/event/OnActivityRecord.java b/src/main/old/cc/sukazyo/cono/morny/bot/event/OnActivityRecord.java
deleted file mode 100644
index e099fb7..0000000
--- a/src/main/old/cc/sukazyo/cono/morny/bot/event/OnActivityRecord.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package cc.sukazyo.cono.morny.bot.event;
-
-import cc.sukazyo.cono.morny.bot.api.EventListener;
-import cc.sukazyo.cono.morny.daemon.TrackerDataManager;
-import com.pengrad.telegrambot.model.Chat;
-import com.pengrad.telegrambot.model.Update;
-
-import javax.annotation.Nonnull;
-
-@Deprecated
-public class OnActivityRecord implements EventListener {
-
- @Override
- public boolean onMessage (@Nonnull Update update) {
- if (
- update.message().chat().type() == Chat.Type.supergroup ||
- update.message().chat().type() == Chat.Type.group
- ) {
- TrackerDataManager.record(
- update.message().chat().id(),
- update.message().from().id(),
- (long)update.message().date() * 1000
- );
- }
- return false;
- }
-
-}
diff --git a/src/main/old/cc/sukazyo/cono/morny/bot/event/OnKuohuanhuanNeedSleep.java b/src/main/old/cc/sukazyo/cono/morny/bot/event/OnKuohuanhuanNeedSleep.java
deleted file mode 100644
index 8af5bc5..0000000
--- a/src/main/old/cc/sukazyo/cono/morny/bot/event/OnKuohuanhuanNeedSleep.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package cc.sukazyo.cono.morny.bot.event;
-
-import cc.sukazyo.cono.morny.MornyCoeur;
-import cc.sukazyo.cono.morny.bot.api.EventListener;
-import com.pengrad.telegrambot.model.Update;
-import com.pengrad.telegrambot.request.DeleteMessage;
-
-import javax.annotation.Nonnull;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
-import java.util.Locale;
-
-@Deprecated
-public class OnKuohuanhuanNeedSleep implements EventListener {
-
- @Override
- public boolean onMessage (@Nonnull Update update) {
- final GregorianCalendar time = new GregorianCalendar(Locale.TAIWAN);
- time.setTimeInMillis(System.currentTimeMillis());
- if (
- ( update.message().from().id() == 786563752L && (
- time.get(Calendar.HOUR_OF_DAY) >= 23 ||
- time.get(Calendar.HOUR_OF_DAY) < 5
- )) || ( update.message().from().id() == 1075871712L && (
- (time.get(Calendar.HOUR_OF_DAY) >= 22 && time.get(Calendar.MINUTE) >= 30) ||
- time.get(Calendar.HOUR_OF_DAY) >= 23 ||
- time.get(Calendar.HOUR_OF_DAY) < 5
- ))
- ) {
- MornyCoeur.extra().exec(
- new DeleteMessage(update.message().chat().id(),
- update.message().messageId())
- );
- return true;
- }
- return false;
- }
-
-}
diff --git a/src/main/old/cc/sukazyo/cono/morny/bot/event/OnRandomlyTriggered.java b/src/main/old/cc/sukazyo/cono/morny/bot/event/OnRandomlyTriggered.java
deleted file mode 100644
index 24c8c2b..0000000
--- a/src/main/old/cc/sukazyo/cono/morny/bot/event/OnRandomlyTriggered.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package cc.sukazyo.cono.morny.bot.event;
-
-import cc.sukazyo.cono.morny.bot.api.EventListener;
-
-@Deprecated
-public class OnRandomlyTriggered implements EventListener {
-
-// /**
-// * function CODE_IK0XA1
-// */
-// // @Override
-// public boolean onMessage (@Nonnull Update update) {
-//
-// if (update.message().text() == null) return false;
-//
-// if (update.message().text().contains("急") && Math.random()<(1d/20)) {
-// MornyCoeur.extra().exec(new SendMessage(
-// update.message().chat().id(),
-// "急也没用"
-// ));
-// }
-//
-// return false;
-//
-// }
-
-}
diff --git a/src/main/old/cc/sukazyo/cono/morny/daemon/MedicationTimer.java b/src/main/old/cc/sukazyo/cono/morny/daemon/MedicationTimer.java
deleted file mode 100644
index 6760b4e..0000000
--- a/src/main/old/cc/sukazyo/cono/morny/daemon/MedicationTimer.java
+++ /dev/null
@@ -1,98 +0,0 @@
-package cc.sukazyo.cono.morny.daemon;
-
-import cc.sukazyo.cono.morny.MornyCoeur;
-import cc.sukazyo.cono.morny.util.CommonFormat;
-import com.pengrad.telegrambot.model.Message;
-import com.pengrad.telegrambot.model.MessageEntity;
-import com.pengrad.telegrambot.model.request.ParseMode;
-import com.pengrad.telegrambot.request.EditMessageText;
-import com.pengrad.telegrambot.request.SendMessage;
-import com.pengrad.telegrambot.response.SendResponse;
-
-import java.time.LocalDateTime;
-import java.time.ZoneOffset;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-import static cc.sukazyo.cono.morny.Log.exceptionLog;
-import static cc.sukazyo.cono.morny.Log.logger;
-
-public class MedicationTimer extends Thread {
-
- private final ZoneOffset USE_TIME_ZONE = MornyCoeur.config().medicationTimerUseTimezone;
- private final Set
- * 基于 java 的程序关闭钩子,因此仍然无法在意外宕机的情况下发送报告.
- * @param causedBy
- * 关闭的原因。
- * 可以使用 {@link User Telegram 用户对象} 表示由一个用户执行了关闭,
- * 传入其它数据将使用 {@code #toString} 输出其内容。
- * 传入 {@link null} 则表示不表明原因。
- */
- static void onMornyExit (@Nullable Object causedBy) {
- if (unsupported()) return;
- String causedTag = null;
- if (causedBy != null) {
- if (causedBy instanceof User)
- causedTag = TGToString.as((User)causedBy).fullnameRefHtml();
- else
- causedTag = " 为保证对 Tracker 缓存的操作不会造成线程冲突,在操作缓存数据前应先取得此锁。 */
- private static final ReentrantLock recordLock = new ReentrantLock();
- /** Tracker 数据的内存缓存 进行数据操作前请先取得对应的{@link #recordLock 锁} */
- private static HashMap
- * 这个方法对于 Tracker 缓存是原子化的。
- *
- * @param chat tracker 所属的 Telegram Chat ID
- * @param user tracker 所记录的 Telegram User ID
- * @param timestamp tracker 被生成时的 UTC 时间戳
- */
- public static void record (long chat, long user, long timestamp) {
- recordLock.lock();
- if (!record.containsKey(chat)) record.put(chat, new HashMap<>());
- HashMap
- * 由于 Tracker 已废弃,这个方法已无作用。
- */
- @SuppressWarnings("unused")
- public static void init () {
- DAEMON.start();
- }
-
- /**
- * 执行 Tracker 的保存逻辑.
- * @see #reset() 弹出 Tracker 缓存
- * @see #save(HashMap) 执行硬盘写入操作
- */
- public static void save () {
- logger.info("start writing tracker data.");
- save(reset());
- logger.info("done writing tracker data.");
- }
-
- /**
- * 将 Tracker 的缓存数据弹出.
- *
- * 这个方法将返回现在 Tracker 的所有缓存数据,然后清除缓存。
- *
- * 这个方法对于 Tracker 缓存是原子化的。
- *
- * @return 当前 Tracker 所包含的内容
- */
- private static HashMap
- * It has a final {@link #assetsPath} record that is its store location,
- * and has a {@link #cache} of the binary data.
- *
- * @since 1.0.0-RC4
- */
- public static class AssetsFileImage {
-
- /** the path where the image is stored in {@link MornyAssets#pack}. */
- @Nonnull private final String assetsPath;
- /** the binary data cache of the image. {@link null} means it hasn't been cached. */
- @Nullable private byte[] cache = null;
-
- /**
- * An {@link AssetsFileImage}.
- *
- * @param path the image path relative to {@link MornyAssets#pack}'s root.
- */
- AssetsFileImage (@Nonnull String path) {
- this.assetsPath = path;
- }
-
- /**
- * Get the binary data.
- *
- * Will read the {@link #cache} firstly. If read {@link null},
- * then it will try {@link #read load the file}, and read again.
- *
- * @return The binary data of the image.
- * @throws IllegalStateException While the {@link #read()} failed to read data and
- * the result {@link #cache} is still null
- */
- @Nonnull public byte[] get() {
- if (cache == null) read();
- if (cache == null) throw new IllegalStateException("Failed get assets file image.");
- return cache;
- }
-
- /**
- * Load the file from {@link MornyAssets#pack}, and stored the binary data to {@link #cache}.
- *
- * If failed, it will output the exception to the log and the {@link MornyReport},
- * and remains the cache's current data.
- */
- private void read() {
- try (InputStream stream = MornyAssets.pack.getResource(assetsPath).read()) {
- this.cache = stream.readAllBytes();
- } catch (IOException e) {
- logger.error("Cannot read resource file");
- logger.error(exceptionLog(e));
- MornyReport.exception(e, "Cannot read resource file");
- }
- }
-
- }
-
- public static final AssetsFileImage IMG_ABOUT = new AssetsFileImage("images/featured-image@0.5x.jpg");
-
-}
diff --git a/src/main/old/cc/sukazyo/cono/morny/data/TelegramStickers.java b/src/main/old/cc/sukazyo/cono/morny/data/TelegramStickers.java
deleted file mode 100644
index d23d1fe..0000000
--- a/src/main/old/cc/sukazyo/cono/morny/data/TelegramStickers.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package cc.sukazyo.cono.morny.data;
-
-import cc.sukazyo.cono.morny.util.tgapi.ExtraAction;
-import com.pengrad.telegrambot.request.SendMessage;
-import com.pengrad.telegrambot.request.SendSticker;
-import com.pengrad.telegrambot.response.SendResponse;
-
-import javax.annotation.Nonnull;
-import java.lang.reflect.Field;
-
-/**
- * 存放 bot 使用到的贴纸
- * @since 0.4.2.0
- */
-public class TelegramStickers {
-
- public static final String ID_ONLINE_STATUS_RETURN = "CAACAgEAAx0CW-CvvgAC5eBhhhODGRuu0pxKLwoQ3yMsowjviAACcycAAnj8xgVVU666si1utiIE";
- public static final String ID_HELLO = "CAACAgEAAxkBAAMnYYYWKNXO4ibo9dlsmDctHhhV6fIAAqooAAJ4_MYFJJhrHS74xUAiBA";
- public static final String ID_EXIT = "CAACAgEAAxkBAAMoYYYWt8UjvP0N405SAyvg2SQZmokAAkMiAAJ4_MYFw6yZLu06b-MiBA";
- public static final String ID_403 = "CAACAgEAAxkBAAMqYYYa_7hpXH6hMOYMX4Nh8AVYd74AAnQnAAJ4_MYFRdmmsQKLDZgiBA";
- public static final String ID_404 = "CAACAgEAAx0CSQh32gABA966YbRJpbmi2lCHINBDuo1DknSTsbsAAqUoAAJ4_MYFUa8SIaZriAojBA";
- public static final String ID_WAITING = "CAACAgEAAx0CSQh32gABA-8DYbh7W2VhJ490ucfZMUMrgMR2FW4AAm4nAAJ4_MYFjx6zpxJPWsQjBA";
- public static final String ID_SENT = "CAACAgEAAx0CSQh32gABA--zYbiyU_wOijEitp-0tSl_k7W6l3gAAgMmAAJ4_MYF4GrompjXPx4jBA";
- public static final String ID_SAVED = "CAACAgEAAx0CSQh32gABBExuYdB_G0srfhQldRWkBYxWzCOv4-IAApooAAJ4_MYFcjuNZszfQcQjBA";
- public static final String ID_PROGYNOVA = "CAACAgUAAxkBAAICm2KEuL7UQqNP7vSPCg2DHJIND6UsAAKLAwACH4WSBszIo722aQ3jJAQ";
- public static final String ID_NETWORK_ERR = "CAACAgEAAxkBAAID0WNJgNEkD726KW4vZeFlw0FlVVyNAAIXJgACePzGBb50o7O1RbxoKgQ";
-
- /**
- * 向 telegram 输出当前的 {@link TelegramStickers} 中的所有 stickers.
- * @param actionObject 要使用的 telegram account 包装实例
- * @param sentChat 目标 telegram chat id
- * @param replyToMessageId 输出时回复指定的消息的 id。使用 {@link -1} 表示不回复消息
- * @since 0.8.0.6
- */
- public static void echoAllStickers (@Nonnull ExtraAction actionObject, long sentChat, int replyToMessageId) {
-
- for (Field object : TelegramStickers.class.getFields()) {
- if (object.getType()==String.class && object.getName().startsWith("ID_")) {
- try {
-
- final String stickerId = (String)object.get("");
- SendSticker echo = new SendSticker(sentChat, stickerId);
- SendMessage echoName = new SendMessage(sentChat, object.getName());
- if (replyToMessageId!=-1) echo.replyToMessageId(replyToMessageId);
- SendResponse echoedName = actionObject.exec(echoName);
- actionObject.exec(echo.replyToMessageId(echoedName.message().messageId()));
-
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
- }
- }
- }
-
- }
-
- /**
- * 向 telegram 输出当前的 {@link TelegramStickers} 中的某个特定 sticker.
- * @param stickerFieldID 要输出的 sticker 在 {@link TelegramStickers} 当中的字段名
- * @param actionObject 要使用的 telegram account 包装实例
- * @param sentChat 目标 telegram chat id
- * @param replyToMessageId 输出时回复指定的消息的 id。使用 {@link -1} 表示不回复消息
- * @since 0.8.0.6
- */
- public static void echoStickerByID (
- @Nonnull String stickerFieldID,
- @Nonnull ExtraAction actionObject, long sentChat, int replyToMessageId
- ) {
- try {
- // normally get the sticker and echo
- Field sticker = TelegramStickers.class.getField(stickerFieldID);
- SendMessage echoName = new SendMessage(sentChat, sticker.getName());
- SendSticker echo = new SendSticker(sentChat, (String)sticker.get(""));
- if (replyToMessageId!=-1) echo.replyToMessageId(replyToMessageId);
- SendResponse echoedName = actionObject.exec(echoName);
- actionObject.exec(echo.replyToMessageId(echoedName.message().messageId()));
- } catch (NoSuchFieldException e) {
- // no such sticker found
- SendSticker echo404 = new SendSticker(sentChat, TelegramStickers.ID_404);
- if (replyToMessageId!=-1) echo404.replyToMessageId(replyToMessageId);
- actionObject.exec(echo404);
- } catch (IllegalAccessException e) {
- // java-reflect get sticker FILE_ID failed
- throw new RuntimeException(e);
- }
- }
-
-}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/Log.scala b/src/main/scala/cc/sukazyo/cono/morny/Log.scala
new file mode 100644
index 0000000..5ca1a38
--- /dev/null
+++ b/src/main/scala/cc/sukazyo/cono/morny/Log.scala
@@ -0,0 +1,29 @@
+package cc.sukazyo.cono.morny
+
+import cc.sukazyo.messiva.logger.Logger
+import cc.sukazyo.messiva.appender.ConsoleAppender
+import cc.sukazyo.messiva.formatter.SimpleFormatter
+import cc.sukazyo.messiva.log.LogLevel
+
+import java.io.{PrintWriter, StringWriter}
+
+object Log {
+
+ val logger: Logger = Logger(
+ ConsoleAppender(
+ SimpleFormatter()
+ )
+ ).minLevel(LogLevel.INFO)
+
+ def debug: Boolean = logger.levelSetting.minLevel.level <= LogLevel.DEBUG.level
+
+ def debug(is: Boolean): Unit =
+ if is then logger.minLevel(LogLevel.ALL)
+ else logger.minLevel(LogLevel.INFO)
+
+ def exceptionLog (e: Throwable): String =
+ val stackTrace = StringWriter()
+ e printStackTrace PrintWriter(stackTrace)
+ stackTrace toString
+
+}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/MornyAbout.scala b/src/main/scala/cc/sukazyo/cono/morny/MornyAbout.scala
new file mode 100644
index 0000000..9d608ed
--- /dev/null
+++ b/src/main/scala/cc/sukazyo/cono/morny/MornyAbout.scala
@@ -0,0 +1,17 @@
+package cc.sukazyo.cono.morny
+
+import java.io.IOException
+
+object MornyAbout {
+
+ val MORNY_PREVIEW_IMAGE_ASCII: String =
+ try { MornyAssets.pack getResource "texts/server-hello.txt" readAsString }
+ catch case e: IOException =>
+ throw RuntimeException("Cannot read MORNY_PREVIEW_IMAGE_ASCII from assets pack", e)
+
+ val MORNY_SOURCECODE_LINK = "https://github.com/Eyre-S/Coeur-Morny-Cono"
+ val MORNY_SOURCECODE_SELF_HOSTED_MIRROR_LINK = "https://storage.sukazyo.cc/Eyre_S/Coeur-Morny-Cono"
+ val MORNY_ISSUE_TRACKER_LINK = "https://github.com/Eyre-S/Coeur-Morny-Cono/issues"
+ val MORNY_USER_GUIDE_LINK = "https://book.sukazyo.cc/morny"
+
+}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/MornyAssets.scala b/src/main/scala/cc/sukazyo/cono/morny/MornyAssets.scala
new file mode 100644
index 0000000..489ac36
--- /dev/null
+++ b/src/main/scala/cc/sukazyo/cono/morny/MornyAssets.scala
@@ -0,0 +1,9 @@
+package cc.sukazyo.cono.morny
+
+import cc.sukazyo.restools.ResourcesPackage
+
+object MornyAssets {
+
+ val pack: ResourcesPackage = ResourcesPackage(MornyAssets.getClass, "assets_morny")
+
+}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/MornyCoeur.scala b/src/main/scala/cc/sukazyo/cono/morny/MornyCoeur.scala
new file mode 100644
index 0000000..6bfad74
--- /dev/null
+++ b/src/main/scala/cc/sukazyo/cono/morny/MornyCoeur.scala
@@ -0,0 +1,149 @@
+package cc.sukazyo.cono.morny
+
+import cc.sukazyo.cono.morny.bot.command.MornyCommands
+import cc.sukazyo.cono.morny.daemon.MornyDaemons
+import cc.sukazyo.cono.morny.util.tgapi.ExtraAction
+import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
+import cc.sukazyo.cono.morny.MornyCoeur.THREAD_MORNY_EXIT
+import cc.sukazyo.cono.morny.bot.api.TelegramUpdatesListener
+import cc.sukazyo.cono.morny.bot.event.MornyEventListeners
+import com.pengrad.telegrambot.TelegramBot
+import com.pengrad.telegrambot.request.GetMe
+
+import scala.util.boundary
+import scala.util.boundary.break
+
+object MornyCoeur {
+
+ val THREAD_MORNY_EXIT = "morny-exiting"
+
+ private var INSTANCE: MornyCoeur|Null = _
+
+ val coeurStartTimestamp: Long = ServerMain.systemStartupTime
+
+ def account: TelegramBot = INSTANCE.account
+ def username: String = INSTANCE.username
+ def userid: Long = INSTANCE.userid
+ def config: MornyConfig = INSTANCE.my_config
+ def trusted: MornyTrusted = INSTANCE.trusted
+ def extra: ExtraAction = INSTANCE.extra
+
+ def available: Boolean = INSTANCE != null
+ def callSaveData(): Unit = INSTANCE.saveDataAll()
+
+ def exitReason: AnyRef|Null = INSTANCE.whileExit_reason
+
+ def exit (status: Int, reason: AnyRef): Unit =
+ INSTANCE.whileExit_reason = reason
+ System exit status
+
+ def init (using config: MornyConfig): Unit = {
+
+ if (INSTANCE ne null)
+ logger error "Coeur already started!!!"
+ return;
+
+ logger info "Coeur starting..."
+ INSTANCE = MornyCoeur()
+
+ MornyDaemons.start()
+
+ logger info "start telegram event listening"
+ MornyEventListeners.registerAllEvents()
+ INSTANCE.account.setUpdatesListener(TelegramUpdatesListener)
+
+ if config.commandLoginRefresh then
+ logger info "resetting telegram command list"
+ MornyCommands.automaticTGListUpdate()
+
+ logger info "Coeur start complete."
+
+ }
+
+}
+
+class MornyCoeur (using config: MornyConfig) {
+ def my_config: MornyConfig = config
+
+ logger info s"args key:\n ${config.telegramBotKey}"
+ if config.telegramBotUsername ne null then
+ logger info s"login as:\n ${config.telegramBotUsername}"
+
+ private val __loginResult = login()
+ if (__loginResult eq null)
+ logger error "Login to bot failed."
+ System exit -1
+
+ configure_exitCleanup()
+
+ val account: TelegramBot = __loginResult.account
+ val username: String = __loginResult.username
+ val userid: Long = __loginResult.userid
+
+ val trusted: MornyTrusted = MornyTrusted(using this)
+ val extra: ExtraAction = ExtraAction as __loginResult.account
+ var whileExit_reason: AnyRef|Null = _
+
+ def saveDataAll(): Unit = {
+ // nothing to do
+ logger info "done all save action."
+ }
+
+ private def exitCleanup (): Unit = {
+ MornyDaemons.stop()
+ if config.commandLogoutClear then
+ MornyCommands.automaticTGListRemove()
+ }
+
+ private def configure_exitCleanup (): Unit = {
+ Runtime.getRuntime.addShutdownHook(new Thread(() => exitCleanup(), THREAD_MORNY_EXIT))
+ }
+
+ private case class LoginResult(account: TelegramBot, username: String, userid: Long)
+
+ private def login (): LoginResult|Null = {
+
+ val builder = TelegramBot.Builder(config.telegramBotKey)
+ var api_bot = config.telegramBotApiServer
+ var api_file = config.telegramBotApiServer4File
+ if (api_bot ne null)
+ if api_bot endsWith "/" then api_bot = api_bot dropRight 1
+ if !(api_bot endsWith "/bot") then api_bot += "/bot"
+ builder.apiUrl(api_bot)
+ if (api_file ne null)
+ if api_file endsWith "/file/" then api_file = api_file dropRight 1
+ if !(api_file endsWith "/file/bot") then api_file += "/file/bot"
+ builder.apiUrl(api_bot)
+ if ((api_bot ne null) || (api_file ne null))
+ logger info
+ s"""Telegram bot api set to:
+ |- bot: $api_bot
+ |- file: $api_file"""
+ .stripMargin
+
+ val account = builder build
+
+ logger info "Trying to login..."
+ boundary[LoginResult|Null] {
+ for (i <- 0 to 3) {
+ if i > 0 then logger info "retrying..."
+ try {
+ val remote = (account execute GetMe()).user
+ if ((config.telegramBotUsername ne null) && config.telegramBotUsername != remote.username)
+ throw RuntimeException(s"Required the bot @${config.telegramBotUsername} but @${remote.username} logged in")
+ logger info s"Succeed logged in to @${remote.username}"
+ break(LoginResult(account, remote.username, remote.id))
+ } catch
+ case r: boundary.Break[LoginResult|Null] => throw r
+ case e =>
+ logger error
+ s"""${exceptionLog(e)}
+ |login failed"""
+ .stripMargin
+ }
+ null
+ }
+
+ }
+
+}
diff --git a/src/main/old/cc/sukazyo/cono/morny/MornyConfig.java b/src/main/scala/cc/sukazyo/cono/morny/MornyConfig.java
similarity index 97%
rename from src/main/old/cc/sukazyo/cono/morny/MornyConfig.java
rename to src/main/scala/cc/sukazyo/cono/morny/MornyConfig.java
index a3d9907..2702a61 100644
--- a/src/main/old/cc/sukazyo/cono/morny/MornyConfig.java
+++ b/src/main/scala/cc/sukazyo/cono/morny/MornyConfig.java
@@ -129,7 +129,7 @@ public class MornyConfig {
* End Configs | ConfigBuilder *
* ======================================= */
- public MornyConfig (@Nonnull Prototype prototype) throws CheckFailure {
+ private MornyConfig (@Nonnull Prototype prototype) throws CheckFailure {
this.telegramBotApiServer = prototype.telegramBotApiServer;
this.telegramBotApiServer4File = prototype.telegramBotApiServer4File;
if (prototype.telegramBotKey == null) throw new CheckFailure.NullTelegramBotKey();
@@ -159,6 +159,10 @@ public class MornyConfig {
public static class Prototype {
+ public MornyConfig build () {
+ return new MornyConfig(this);
+ }
+
@Nullable public String telegramBotApiServer = null;
@Nullable public String telegramBotApiServer4File = null;
@Nullable public String telegramBotKey = null;
diff --git a/src/main/scala/cc/sukazyo/cono/morny/MornySystem.scala b/src/main/scala/cc/sukazyo/cono/morny/MornySystem.scala
new file mode 100644
index 0000000..69fb74f
--- /dev/null
+++ b/src/main/scala/cc/sukazyo/cono/morny/MornySystem.scala
@@ -0,0 +1,48 @@
+package cc.sukazyo.cono.morny
+
+import cc.sukazyo.cono.morny.internal.BuildConfigField
+import cc.sukazyo.cono.morny.util.FileUtils
+
+import java.io.IOException
+import java.net.URISyntaxException
+import java.security.NoSuchAlgorithmException
+import Log.{exceptionLog, logger}
+import cc.sukazyo.cono.morny.daemon.MornyReport
+
+object MornySystem {
+
+ @BuildConfigField val VERSION: String = BuildConfig.VERSION
+ @BuildConfigField val VERSION_FULL: String = BuildConfig.VERSION_FULL
+ @BuildConfigField val VERSION_BASE: String = BuildConfig.VERSION_BASE
+ @BuildConfigField val VERSION_DELTA: String = BuildConfig.VERSION_DELTA
+ @BuildConfigField val CODENAME: String = BuildConfig.CODENAME
+ @BuildConfigField val CODE_STORE: String = BuildConfig.CODE_STORE
+ @BuildConfigField val COMMIT_PATH: String = BuildConfig.COMMIT_PATH
+
+ @BuildConfigField
+ def isUseDelta: Boolean = VERSION_DELTA ne null
+
+ @BuildConfigField
+ def isGitBuild: Boolean = BuildConfig.COMMIT ne null
+
+ @BuildConfigField
+ def isCleanBuild: Boolean = BuildConfig.CLEAN_BUILD
+
+ def currentCodePath: String|Null =
+ if ((COMMIT_PATH eq null) || (!isGitBuild)) null
+ else COMMIT_PATH.formatted(BuildConfig.COMMIT)
+
+ def getJarMD5: String = {
+ try {
+ FileUtils.getMD5Three(MornySystem.getClass.getProtectionDomain.getCodeSource.getLocation.toURI.getPath)
+ } catch
+ //noinspection ScalaUnnecessaryParentheses
+ case _: (IOException|URISyntaxException) =>
+ "
- *
- * 只支持 jar 文件方式启动的程序 ——
- * 如果是通过 classpath 来启动,程序无法找到本体jar文件,则会返回 {@code
- * 值格式为 {@link java.lang.String}
- *
- * @return 程序jar文件的 md5-hash 值字符串,或 {@code
- *
- * 用户需要受信任才能执行一些对程序甚至是宿主环境而言危险的操作,例如关闭程序
- *
- * 它的逻辑(目前)是检查群聊 {@link MornyConfig#trustedChat} 中这个用户是否为群组管理员
- *
- * @param userId 需要检查的用户的id
- * @return 所传递的用户id对应的用户是否受信任
- */
- public boolean isTrusted (long userId) {
- if (userId == instance.config.trustedMaster) return true;
- if (instance.config.trustedChat == -1) return false;
- return MornyCoeur.extra().isUserInGroup(userId, instance.config.trustedChat, Status.administrator);
- }
-
- public boolean isTrustedForDinnerRead (long userId) {
- return instance.config.dinnerTrustedReaders.contains(userId);
- }
-
- public Set
%s
- """,
- description == null ? "" : escapeHtml(description)+"\n",
- escapeHtml(Log.exceptionLog(e)),
- e instanceof EventRuntimeException.ActionFailed ? (String.format(
- "\n\ntg-api error:\n%s
",
- new GsonBuilder().setPrettyPrinting().create().toJson(((EventRuntimeException.ActionFailed)e).getResponse()))
- ) : ""
- )
- ).parseMode(ParseMode.HTML));
- }
-
- public static void exception (@Nonnull Exception e) { exception(e, null); }
-
- public static void unauthenticatedAction (@Nonnull String action, @Nonnull User user) {
- if (unsupported()) return;
- executeReport(new SendMessage(
- MornyCoeur.config().reportToChat,
- String.format("""
- ▌User unauthenticated action
- action: %s
- by user %s
- """,
- escapeHtml(action),
- TGToString.as(user).fullnameRefHtml()
- )
- ).parseMode(ParseMode.HTML));
- }
-
- /**
- * morny 登陆时的报告发送,包含已登录的账号 id 以及启动配置。
- * @since 1.0.0-alpha6
- */
- static void onMornyLogIn () {
- executeReport(new SendMessage(
- MornyCoeur.config().reportToChat,
- String.format("""
- ▌Morny Logged in
- -v %s
- as user @%s
-
- as config fields:
- %s
- """,
- MornyInformation.getVersionAllFullTagHTML(),
- 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("%s
").append(escapeHtml(fieldValue.toString())).append("
");
- }
- } catch (IllegalAccessException | IllegalArgumentException | NullPointerException e) {
- echo.append(": ").append(escapeHtml("" + 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));
- }
-
-}
diff --git a/src/main/old/cc/sukazyo/cono/morny/daemon/TrackerDataManager.java b/src/main/old/cc/sukazyo/cono/morny/daemon/TrackerDataManager.java
deleted file mode 100644
index f8a99fc..0000000
--- a/src/main/old/cc/sukazyo/cono/morny/daemon/TrackerDataManager.java
+++ /dev/null
@@ -1,174 +0,0 @@
-package cc.sukazyo.cono.morny.daemon;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.StandardOpenOption;
-import java.util.HashMap;
-import java.util.TreeSet;
-import java.util.concurrent.locks.ReentrantLock;
-
-import static cc.sukazyo.cono.morny.Log.exceptionLog;
-import static cc.sukazyo.cono.morny.Log.logger;
-
-public class TrackerDataManager {
-
- /** {@link TrackerDaemon} 的锁。保证在程序中只有一个 TrackerDaemon 会运行。 */
- public static final ReentrantLock trackingLock = new ReentrantLock();
- /** {@link #record Tracker 缓存}的锁 ${h(e.getMessage)}
"""
.stripMargin
- ).parseMode(ParseMode.HTML).replyToMessageId(event.message().messageId()));
+ ).parseMode(ParseMode.HTML).replyToMessageId(event.message().messageId())
}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyCommands.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyCommands.scala
index 7fa9876..a6fbdf8 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyCommands.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyCommands.scala
@@ -3,20 +3,23 @@ package cc.sukazyo.cono.morny.bot.command
import cc.sukazyo.cono.morny.util.tgapi.InputCommand
import cc.sukazyo.cono.morny.MornyCoeur
import cc.sukazyo.cono.morny.data.TelegramStickers
+import cc.sukazyo.cono.morny.Log.logger
import com.pengrad.telegrambot.model.{BotCommand, DeleteMyCommands, Update}
import com.pengrad.telegrambot.request.{SendSticker, SetMyCommands}
import scala.collection.{mutable, SeqMap}
import scala.collection.mutable.ArrayBuffer
import scala.language.postfixOps
-import cc.sukazyo.cono.morny.Log.logger
object MornyCommands {
private type CommandMap = SeqMap[String, ISimpleCommand]
private def CommandMap (commands: ISimpleCommand*): CommandMap =
- val stash: mutable.SeqMap[String, ISimpleCommand] = mutable.SeqMap()
- for (i <- commands) stash += ((i.name, i))
+ val stash = mutable.SeqMap.empty[String, ISimpleCommand]
+ for (i <- commands)
+ stash += (i.name -> i)
+ if (i.aliases ne null) for (alias <- i.aliases)
+ stash += (alias.name -> i)
stash
private val commands: CommandMap = CommandMap(
@@ -41,12 +44,14 @@ object MornyCommands {
Testing,
DirectMsgClear,
+ //noinspection NonAsciiCharacters
私わね,
+ //noinspection NonAsciiCharacters
喵呜.Progynova
)
- @SuppressWarnings(Array("NonAsciiCharacters"))
+ //noinspection NonAsciiCharacters
val commands_uni: CommandMap = CommandMap(
喵呜.抱抱,
喵呜.揉揉,
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyInformation.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyInformation.scala
index 9b3dae3..f9c46bb 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyInformation.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyInformation.scala
@@ -1,10 +1,10 @@
package cc.sukazyo.cono.morny.bot.command
+import cc.sukazyo.cono.morny.{BuildConfig, MornyAbout, MornyCoeur, MornySystem}
import cc.sukazyo.cono.morny.data.{TelegramImages, TelegramStickers}
import cc.sukazyo.cono.morny.util.CommonFormat.{formatDate, formatDuration}
import cc.sukazyo.cono.morny.util.tgapi.InputCommand
import cc.sukazyo.cono.morny.util.tgapi.formatting.MsgEscape.escapeHtml as h
-import cc.sukazyo.cono.morny.{BuildConfig, MornyAbout, MornyCoeur, MornySystem}
import com.pengrad.telegrambot.model.Update
import com.pengrad.telegrambot.model.request.ParseMode
import com.pengrad.telegrambot.request.{SendMessage, SendPhoto, SendSticker}
@@ -38,7 +38,7 @@ object MornyInformation extends ITelegramCommand {
val action: String = command.getArgs()(0)
action match {
- case Subs.STICKERS => echoStickers
+ case s if s startsWith Subs.STICKERS => echoStickers
case Subs.RUNTIME => echoRuntime
case Subs.VERSION | Subs.VERSION_2 => echoVersion
case _ => echo404
@@ -93,24 +93,41 @@ object MornyInformation extends ITelegramCommand {
}
private def echoStickers (using command: InputCommand, event: Update): Unit = {
- val chat = event.message.chat.id
- val replyTo = event.message.messageId
- var sid: String|Null = null
- if (command.getArgs()(0) == Subs.STICKERS) {
- if (command.getArgs.length == 1) sid = ""
- else if (command.getArgs.length == 2) sid = command.getArgs()(1)
- } else if (command.getArgs.length == 1) {
- if ((command.getArgs()(0) startsWith s"${Subs.STICKERS}.") || (command.getArgs()(0) startsWith s"${Subs.STICKERS}#")) {
- sid = command.getArgs()(0) substring Subs.STICKERS.length+1
- }
- }
- if (sid == null) echo404
- else echoStickers(sid, chat, replyTo)
+ val mid: String|Null =
+ if (command.getArgs()(0) == Subs.STICKERS) {
+ if (command.getArgs.length == 1) ""
+ else if (command.getArgs.length == 2) command.getArgs()(1)
+ else null
+ } else if (command.getArgs.length == 1) {
+ if ((command.getArgs()(0) startsWith s"${Subs.STICKERS}.") || (command.getArgs()(0) startsWith s"${Subs.STICKERS}#")) {
+ command.getArgs()(0) substring Subs.STICKERS.length+1
+ } else null
+ } else null
+ if (mid == null) echo404
+ else echoStickers(mid)(using event.message.chat.id, event.message.messageId)
}
- private def echoStickers (sid: String, send_chat: Long, send_replyTo: Int): Unit = {
- if (sid isEmpty) TelegramStickers echoAllStickers(MornyCoeur.extra, send_chat, send_replyTo)
- else TelegramStickers echoStickerByID(sid, MornyCoeur.extra, send_chat, send_replyTo)
+ private def echoStickers (mid: String)(using send_chat: Long, send_replyTo: Int)(using Update): Unit = {
+ import scala.jdk.CollectionConverters.*
+ if (mid isEmpty) for ((_key, _file_id) <- TelegramStickers.map asScala)
+ echoSticker(_key, _file_id)
+ else {
+ try {
+ val sticker = TelegramStickers getById mid
+ echoSticker(sticker.getKey, sticker.getValue)
+ } catch case _: NoSuchFieldException => {
+ echo404
+ }
+ }
+ }
+
+ private def echoSticker (mid: String, file_id: String)(using send_chat: Long, send_replyTo: Int): Unit = {
+ val send_mid = SendMessage(send_chat, mid)
+ val send_sticker = SendSticker(send_chat, file_id)
+ if (send_replyTo != -1) send_mid.replyToMessageId(send_replyTo)
+ val result_send_mid = MornyCoeur.extra exec send_mid
+ send_sticker.replyToMessageId(result_send_mid.message.messageId)
+ MornyCoeur.extra exec send_sticker
}
private[command] def echoVersion (using event: Update): Unit = {
@@ -123,7 +140,7 @@ object MornyInformation extends ITelegramCommand {
|- Morny ${h(MornySystem.CODENAME toUpperCase)}
|- ${h(MornySystem.VERSION_BASE)}
$versionDeltaHTML${if (MornySystem.isGitBuild) "\n- " + versionGitHTML else ""}
|coeur md5_hash:
- |- ${h(MornySystem.getJarMd5)}
+ |- ${h(MornySystem.getJarMD5)}
|coding timestamp:
|- ${BuildConfig.CODE_TIMESTAMP}
|- ${h(formatDate(BuildConfig.CODE_TIMESTAMP, 0))} [UTC]
@@ -147,7 +164,7 @@ object MornyInformation extends ITelegramCommand {
|- ${Runtime.getRuntime.availableProcessors}
cores
|coeur version:
|- $getVersionAllFullTagHTML
- |- ${h(MornySystem.getJarMd5)}
+ |- ${h(MornySystem.getJarMD5)}
|- ${h(formatDate(BuildConfig.CODE_TIMESTAMP, 0))} [UTC]
|- [${BuildConfig.CODE_TIMESTAMP}
]
|continuous:
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyManagers.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyManagers.scala
index 7218dc9..bb69c48 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyManagers.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyManagers.scala
@@ -24,7 +24,7 @@ object MornyManagers {
val user = event.message.from
- if (MornyCoeur.trustedInstance isTrusted user.id) {
+ if (MornyCoeur.trusted isTrusted user.id) {
MornyCoeur.extra exec SendSticker(
event.message.chat.id,
@@ -59,7 +59,7 @@ object MornyManagers {
val user = event.message.from
- if (MornyCoeur.trustedInstance isTrusted user.id) {
+ if (MornyCoeur.trusted isTrusted user.id) {
logger info s"call save from command by ${(TGToString as user) toStringLogTag}"
MornyCoeur.callSaveData()
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMe.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMe.scala
index d8c334a..68ad75c 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMe.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMe.scala
@@ -1,6 +1,5 @@
package cc.sukazyo.cono.morny.bot.event
-import cc.sukazyo.cono.morny.Log.logger
import cc.sukazyo.cono.morny.MornyCoeur
import cc.sukazyo.cono.morny.bot.api.EventListener
import cc.sukazyo.cono.morny.data.TelegramStickers
@@ -20,6 +19,7 @@ object OnCallMe extends EventListener {
if update.message.text == null then return false
if update.message.chat.`type` != (Chat.Type Private) then return false
+ //noinspection ScalaUnnecessaryParentheses
(update.message.text toLowerCase) match
case "steam" | "sbeam" | "sdeam" =>
requestItem(update.message.from, "STEAM LIBRARY")
@@ -51,7 +51,7 @@ object OnCallMe extends EventListener {
private def requestLastDinner (req: Message): Unit = {
var isAllowed = false
var lastDinnerData: Message|Null = null
- if (MornyCoeur.trustedInstance isTrustedForDinnerRead req.from.id) {
+ if (MornyCoeur.trusted isTrusted_dinnerReader req.from.id) {
lastDinnerData = (MornyCoeur.extra exec GetChat(MornyCoeur.config.dinnerChatId)).chat.pinnedMessage
val sendResp = MornyCoeur.extra exec ForwardMessage(
req.from.id,
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMsgSend.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMsgSend.scala
index a6fee41..d4bb8ca 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMsgSend.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMsgSend.scala
@@ -46,6 +46,7 @@ object OnCallMsgSend extends EventListener {
if e.url ne null then _parsed.url(e.url)
if e.user ne null then _parsed.user(e.user)
if e.language ne null then _parsed.language(e.language)
+ if e.customEmojiId ne null then _parsed.language(e.language)
entities += _parsed
MessageToSend(_body, entities toArray, parseMode, target)
case _ => null
@@ -59,7 +60,7 @@ object OnCallMsgSend extends EventListener {
if message.text eq null then return false
if !(message.text startsWith "*msg") then return false
- if (!(MornyCoeur.trustedInstance isTrusted message.from.id))
+ if (!(MornyCoeur.trusted isTrusted message.from.id))
MornyCoeur.extra exec SendSticker(
message.chat.id,
TelegramStickers ID_403
@@ -71,7 +72,7 @@ object OnCallMsgSend extends EventListener {
if (message.replyToMessage eq null) return answer404
val messageToSend = MessageToSend from message.replyToMessage
if ((messageToSend eq null) || (messageToSend.message eq null)) return answer404
- val sendResponse = MornyCoeur.getAccount execute messageToSend.toSendMessage()
+ val sendResponse = MornyCoeur.account execute messageToSend.toSendMessage()
if (sendResponse isOk) {
MornyCoeur.extra exec SendSticker(
@@ -104,7 +105,7 @@ object OnCallMsgSend extends EventListener {
if _toSend eq null then return answer404
else _toSend
- val targetChatResponse = MornyCoeur.getAccount execute GetChat(messageToSend.targetId)
+ val targetChatResponse = MornyCoeur.account execute GetChat(messageToSend.targetId)
if (targetChatResponse isOk) {
def getChatDescriptionHTML (chat: Chat): String =
import cc.sukazyo.cono.morny.util.tgapi.formatting.MsgEscape.escapeHtml as h
@@ -128,7 +129,7 @@ object OnCallMsgSend extends EventListener {
}
if messageToSend.message eq null then return true
- val testSendResponse = MornyCoeur.getAccount execute messageToSend.toSendMessage(update.message.chat.id)
+ val testSendResponse = MornyCoeur.account execute messageToSend.toSendMessage(update.message.chat.id)
.replyToMessageId(update.message.messageId)
if (!(testSendResponse isOk))
MornyCoeur.extra exec SendMessage(
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnInlineQuery.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnInlineQuery.scala
index 970aa2d..d4df473 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnInlineQuery.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnInlineQuery.scala
@@ -2,7 +2,7 @@ package cc.sukazyo.cono.morny.bot.event
import cc.sukazyo.cono.morny.MornyCoeur
import cc.sukazyo.cono.morny.bot.api.EventListener
-import cc.sukazyo.cono.morny.bot.query.InlineQueryUnit
+import cc.sukazyo.cono.morny.bot.query.{InlineQueryUnit, MornyQueries}
import com.pengrad.telegrambot.model.Update
import com.pengrad.telegrambot.model.request.InlineQueryResult
import com.pengrad.telegrambot.request.AnswerInlineQuery
@@ -15,7 +15,7 @@ object OnInlineQuery extends EventListener {
override def onInlineQuery (using update: Update): Boolean = {
- val results: List[InlineQueryUnit[_]] = MornyCoeur.queryManager query update
+ val results: List[InlineQueryUnit[_]] = MornyQueries query update
var cacheTime = Int.MaxValue
var isPersonal = InlineQueryUnit.defaults.IS_PERSONAL
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnMedicationNotifyApply.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnMedicationNotifyApply.scala
index 5b2ab47..4e90c9b 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnMedicationNotifyApply.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnMedicationNotifyApply.scala
@@ -2,7 +2,7 @@ package cc.sukazyo.cono.morny.bot.event
import cc.sukazyo.cono.morny.bot.api.EventListener
import cc.sukazyo.cono.morny.MornyCoeur
-import cc.sukazyo.cono.morny.daemon.MornyDaemons
+import cc.sukazyo.cono.morny.daemon.{MedicationTimer, MornyDaemons}
import com.pengrad.telegrambot.model.{Message, Update}
object OnMedicationNotifyApply extends EventListener {
@@ -14,7 +14,7 @@ object OnMedicationNotifyApply extends EventListener {
private def editedMessageProcess (edited: Message): Boolean = {
if edited.chat.id != MornyCoeur.config.medicationNotifyToChat then return false
- MornyDaemons.medicationTimerInstance.refreshNotificationWrite(edited)
+ MedicationTimer.refreshNotificationWrite(edited)
true
}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnTelegramCommand.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnTelegramCommand.scala
index 526af48..b82a66a 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnTelegramCommand.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnTelegramCommand.scala
@@ -22,7 +22,7 @@ object OnTelegramCommand extends EventListener {
if (!(inputCommand.getCommand matches "^\\w+$"))
logger debug "not command"
false
- else if ((inputCommand.getTarget ne null) && (inputCommand.getTarget ne MornyCoeur.getUsername))
+ else if ((inputCommand.getTarget ne null) && (inputCommand.getTarget ne MornyCoeur.username))
logger debug "not morny command"
false
else
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/query/MornyQueries.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/query/MornyQueries.scala
index 5f13657..0c06dc4 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/query/MornyQueries.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/query/MornyQueries.scala
@@ -5,7 +5,7 @@ import com.pengrad.telegrambot.model.Update
import scala.collection.mutable.ListBuffer
-class MornyQueries {
+object MornyQueries {
private val queryInstances = Set[ITelegramQuery](
RawText,
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/query/MyInformation.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/query/MyInformation.scala
index 14a4e81..6275c2e 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/query/MyInformation.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/query/MyInformation.scala
@@ -14,7 +14,7 @@ object MyInformation extends ITelegramQuery {
override def query (event: Update): List[InlineQueryUnit[_]] | Null = {
- if (event.inlineQuery.query == null || (event.inlineQuery.query isBlank)) return null
+ if ((event.inlineQuery.query ne null) || (event.inlineQuery.query nonEmpty)) return null
List(
InlineQueryUnit(InlineQueryResultArticle(
diff --git a/src/main/scala/cc/sukazyo/cono/morny/daemon/MedicationTimer.scala b/src/main/scala/cc/sukazyo/cono/morny/daemon/MedicationTimer.scala
new file mode 100644
index 0000000..644abe2
--- /dev/null
+++ b/src/main/scala/cc/sukazyo/cono/morny/daemon/MedicationTimer.scala
@@ -0,0 +1,90 @@
+package cc.sukazyo.cono.morny.daemon
+
+import cc.sukazyo.cono.morny.MornyCoeur
+import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
+import com.pengrad.telegrambot.model.{Message, MessageEntity}
+import com.pengrad.telegrambot.request.{EditMessageText, SendMessage}
+import com.pengrad.telegrambot.response.SendResponse
+
+import java.time.{LocalDateTime, ZoneOffset}
+import scala.collection.mutable.ArrayBuffer
+import scala.language.implicitConversions
+
+object MedicationTimer extends Thread {
+
+ private val NOTIFY_MESSAGE = "🍥⏲"
+ private val DAEMON_THREAD_NAME_DEF = "MedicationTimer"
+
+ private val use_timeZone = MornyCoeur.config.medicationTimerUseTimezone
+ import scala.jdk.CollectionConverters.SetHasAsScala
+ private val notify_atHour: Set[Int] = MornyCoeur.config.medicationNotifyAt.asScala.toSet.map(_.intValue)
+ private val notify_toChat = MornyCoeur.config.medicationNotifyToChat
+
+ this.setName(DAEMON_THREAD_NAME_DEF)
+
+ private var lastNotify_messageId: Int|Null = _
+
+ override def run (): Unit = {
+ logger info "Medication Timer started."
+ while (!this.isInterrupted) {
+ try {
+ waitToNextRoutine()
+ sendNotification()
+ } catch
+ case _: InterruptedException =>
+ interrupt()
+ logger info "MedicationTimer was interrupted, will be exit now"
+ case ill: IllegalArgumentException =>
+ logger warn "MedicationTimer will not work due to: " + ill.getMessage
+ interrupt()
+ case e =>
+ logger error
+ s"""unexpected error occurred on NotificationTimer
+ |${exceptionLog(e)}"""
+ .stripMargin
+ MornyReport.exception(e)
+ }
+ logger info "Medication Timer stopped."
+ }
+
+ private def sendNotification(): Unit = {
+ val sendResponse: SendResponse = MornyCoeur.extra exec SendMessage(notify_toChat, NOTIFY_MESSAGE)
+ if sendResponse isOk then lastNotify_messageId = sendResponse.message.messageId
+ else lastNotify_messageId = null
+ }
+
+ def refreshNotificationWrite (edited: Message): Unit = {
+ if lastNotify_messageId != (edited.messageId toInt) then return
+ import cc.sukazyo.cono.morny.util.CommonFormat.formatDate
+ val editTime = formatDate(edited.editDate*1000, use_timeZone.getTotalSeconds/60/60)
+ val entities = ArrayBuffer.empty[MessageEntity]
+ if edited.entities ne null then entities ++= edited.entities
+ entities += MessageEntity(MessageEntity.Type.italic, edited.text.length + "\n-- ".length, editTime.length)
+ MornyCoeur.extra exec EditMessageText(
+ notify_toChat,
+ edited.messageId,
+ edited.text + s"\n-- $editTime --"
+ ).entities(entities toArray:_*)
+ lastNotify_messageId = null
+ }
+
+ @throws[IllegalArgumentException]
+ private[daemon] def calcNextRoutineTimestamp (baseTimeMillis: Long, zone: ZoneOffset, notifyAt: Set[Int]): Long = {
+ if (notifyAt isEmpty) throw new IllegalArgumentException("notify time is not set")
+ var time = LocalDateTime.ofEpochSecond(
+ baseTimeMillis/1000, ((baseTimeMillis%1000)*1000*1000) toInt,
+ zone
+ ).withMinute(0).withSecond(0).withNano(0)
+ time = time plusHours 1
+ while (!(notifyAt contains (time getHour))) {
+ time = time plusHours 1
+ }
+ (time toInstant zone) toEpochMilli
+ }
+
+ @throws[InterruptedException | IllegalArgumentException]
+ private def waitToNextRoutine (): Unit = {
+ Thread sleep calcNextRoutineTimestamp(System.currentTimeMillis, use_timeZone, notify_atHour)
+ }
+
+}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/daemon/MornyDaemons.scala b/src/main/scala/cc/sukazyo/cono/morny/daemon/MornyDaemons.scala
new file mode 100644
index 0000000..95c9415
--- /dev/null
+++ b/src/main/scala/cc/sukazyo/cono/morny/daemon/MornyDaemons.scala
@@ -0,0 +1,29 @@
+package cc.sukazyo.cono.morny.daemon
+
+import cc.sukazyo.cono.morny.Log.logger
+import cc.sukazyo.cono.morny.MornyCoeur
+
+object MornyDaemons {
+
+ def start (): Unit = {
+ logger info "ALL Morny Daemons starting..."
+ // TrackerDataManager.init();
+ MedicationTimer.start()
+ MornyReport.onMornyLogin()
+ logger info "Morny Daemons started."
+
+ }
+
+ def stop (): Unit = {
+ logger.info("ALL Morny Daemons stopping...")
+ // TrackerDataManager.DAEMON.interrupt();
+ MedicationTimer.interrupt()
+ // TrackerDataManager.trackingLock.lock();
+ try { MedicationTimer.join() }
+ catch case e: InterruptedException =>
+ e.printStackTrace(System.out)
+ MornyReport.onMornyExit(MornyCoeur.exitReason)
+ logger.info("ALL Morny Daemons STOPPED.")
+ }
+
+}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/daemon/MornyReport.scala b/src/main/scala/cc/sukazyo/cono/morny/daemon/MornyReport.scala
new file mode 100644
index 0000000..cba4ccf
--- /dev/null
+++ b/src/main/scala/cc/sukazyo/cono/morny/daemon/MornyReport.scala
@@ -0,0 +1,122 @@
+package cc.sukazyo.cono.morny.daemon
+
+import cc.sukazyo.cono.morny.{MornyCoeur, MornyConfig}
+import cc.sukazyo.cono.morny.util.tgapi.event.EventRuntimeException
+import com.pengrad.telegrambot.request.{BaseRequest, SendMessage}
+import com.pengrad.telegrambot.response.BaseResponse
+import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
+import cc.sukazyo.cono.morny.bot.command.MornyInformation
+import com.google.gson.GsonBuilder
+import com.pengrad.telegrambot.model.request.ParseMode
+import com.pengrad.telegrambot.model.User
+import cc.sukazyo.cono.morny.util.tgapi.formatting.MsgEscape.escapeHtml as h
+import cc.sukazyo.cono.morny.util.tgapi.formatting.TGToString
+
+object MornyReport {
+
+ private def unsupported: Boolean = (!MornyCoeur.available) || (MornyCoeur.config.reportToChat == -1)
+
+ private def executeReport[T <: BaseRequest[T, R], R<: BaseResponse] (report: T): Unit = {
+ if unsupported then return
+ try {
+ MornyCoeur.extra exec report
+ } catch case e: EventRuntimeException.ActionFailed => {
+ logger warn
+ s"""cannot execute report to telegram:
+ |${exceptionLog(e) indent 4}
+ | tg-api response:
+ |${(e.getResponse toString) indent 4}"""
+ .stripMargin
+ }
+ }
+
+ def exception (e: Throwable, description: String|Null = null): Unit = {
+ if unsupported then return
+ def _tgErrFormat: String = e match
+ case api: EventRuntimeException.ActionFailed =>
+ // language=html
+ "\n\ntg-api error:\n
"
+ .formatted(GsonBuilder().setPrettyPrinting().create.toJson(api.getResponse))
+ case _ => ""
+ executeReport(SendMessage(
+ MornyCoeur.config.reportToChat,
+ // language=html
+ s"""▌Coeur Unexpected Exception
+ |${if description ne null then h(description)+"\n" else ""}
+ |%s
$_tgErrFormat"""
+ .stripMargin
+ ).parseMode(ParseMode HTML))
+ }
+
+ def unauthenticatedAction (action: String, user: User): Unit = {
+ if unsupported then return
+ executeReport(SendMessage(
+ MornyCoeur.config.reportToChat,
+ // language=html
+ s"""▌User unauthenticated action
+ |action: ${h(action)}
+ |by user ${(TGToString as user) fullnameRefHtml}"""
+ .stripMargin
+ ).parseMode(ParseMode HTML))
+ }
+
+ def onMornyLogin(): Unit = {
+ executeReport(SendMessage(
+ MornyCoeur.config.reportToChat,
+ // language=html
+ s"""▌Morny Logged in
+ |-v ${MornyInformation.getVersionAllFullTagHTML}
+ |as user ${MornyCoeur.username}
+ |
+ |as config fields:
+ |${sectionConfigFields(MornyCoeur.config)}"""
+ .stripMargin
+ ).parseMode(ParseMode HTML))
+ }
+
+ def sectionConfigFields (config: MornyConfig): String = {
+ val echo = StringBuilder()
+ for (field <- config.getClass.getFields) {
+ // language=html
+ echo ++= s"- ${field.getName} "
+ try {
+ if (field.isAnnotationPresent(classOf[MornyConfig.Sensitive])) {
+ echo ++= /*language=html*/ ": sensitive_field"
+ } else {
+ val value = field.get(config)
+ // language=html
+ echo ++= "= " ++= (if value eq null then "null" else s"${h(exceptionLog(e))}
${h(value.toString)}
")
+ }
+
+ } catch
+ // noinspection ScalaUnnecessaryParentheses
+ case e: (IllegalAccessException|IllegalArgumentException|NullPointerException) =>
+ // language=html
+ echo ++= s": ${h("${h(a.toString)}
"
+ executeReport(SendMessage(
+ MornyCoeur.config.reportToChat,
+ // language=html
+ s"""▌Morny Exited
+ |from user @${MornyCoeur.username}
+ |
+ |by: $causedTag"""
+ .stripMargin
+ ).parseMode(ParseMode HTML))
+ }
+
+}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/data/TelegramImages.scala b/src/main/scala/cc/sukazyo/cono/morny/data/TelegramImages.scala
new file mode 100644
index 0000000..667d5d3
--- /dev/null
+++ b/src/main/scala/cc/sukazyo/cono/morny/data/TelegramImages.scala
@@ -0,0 +1,38 @@
+package cc.sukazyo.cono.morny.data
+
+import cc.sukazyo.cono.morny.MornyAssets
+
+import scala.language.postfixOps
+import scala.util.Using
+import java.io.IOException
+import cc.sukazyo.cono.morny.Log.{exceptionLog, logger}
+import cc.sukazyo.cono.morny.daemon.MornyReport
+
+object TelegramImages {
+
+ class AssetsFileImage (assetsPath: String) {
+
+ private var cache: Array[Byte]|Null = _
+
+ def get:Array[Byte] =
+ if cache eq null then read()
+ if cache eq null then throw IllegalStateException("Failed to get assets file image.")
+ cache
+
+ private def read (): Unit = {
+ Using ((MornyAssets.pack getResource assetsPath)read) { stream =>
+ try { this.cache = stream.readAllBytes() }
+ catch case e: IOException => {
+ logger error
+ s"""Cannot read resource file:
+ |${exceptionLog(e)}""".stripMargin
+ MornyReport.exception(e, "Cannot read resource file.")
+ }
+ }
+ }
+
+ }
+
+ val IMG_ABOUT: AssetsFileImage = AssetsFileImage("images/featured-image@0.5x.jpg")
+
+}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/data/TelegramStickers.java b/src/main/scala/cc/sukazyo/cono/morny/data/TelegramStickers.java
new file mode 100644
index 0000000..10a017a
--- /dev/null
+++ b/src/main/scala/cc/sukazyo/cono/morny/data/TelegramStickers.java
@@ -0,0 +1,53 @@
+package cc.sukazyo.cono.morny.data;
+
+import javax.annotation.Nonnull;
+import java.lang.reflect.Field;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * 存放 bot 使用到的贴纸
+ * @since 0.4.2.0
+ */
+public class TelegramStickers {
+
+ public static final String ID_ONLINE_STATUS_RETURN = "CAACAgEAAx0CW-CvvgAC5eBhhhODGRuu0pxKLwoQ3yMsowjviAACcycAAnj8xgVVU666si1utiIE";
+ public static final String ID_HELLO = "CAACAgEAAxkBAAMnYYYWKNXO4ibo9dlsmDctHhhV6fIAAqooAAJ4_MYFJJhrHS74xUAiBA";
+ public static final String ID_EXIT = "CAACAgEAAxkBAAMoYYYWt8UjvP0N405SAyvg2SQZmokAAkMiAAJ4_MYFw6yZLu06b-MiBA";
+ public static final String ID_403 = "CAACAgEAAxkBAAMqYYYa_7hpXH6hMOYMX4Nh8AVYd74AAnQnAAJ4_MYFRdmmsQKLDZgiBA";
+ public static final String ID_404 = "CAACAgEAAx0CSQh32gABA966YbRJpbmi2lCHINBDuo1DknSTsbsAAqUoAAJ4_MYFUa8SIaZriAojBA";
+ public static final String ID_WAITING = "CAACAgEAAx0CSQh32gABA-8DYbh7W2VhJ490ucfZMUMrgMR2FW4AAm4nAAJ4_MYFjx6zpxJPWsQjBA";
+ public static final String ID_SENT = "CAACAgEAAx0CSQh32gABA--zYbiyU_wOijEitp-0tSl_k7W6l3gAAgMmAAJ4_MYF4GrompjXPx4jBA";
+ public static final String ID_SAVED = "CAACAgEAAx0CSQh32gABBExuYdB_G0srfhQldRWkBYxWzCOv4-IAApooAAJ4_MYFcjuNZszfQcQjBA";
+ public static final String ID_PROGYNOVA = "CAACAgUAAxkBAAICm2KEuL7UQqNP7vSPCg2DHJIND6UsAAKLAwACH4WSBszIo722aQ3jJAQ";
+ public static final String ID_NETWORK_ERR = "CAACAgEAAxkBAAID0WNJgNEkD726KW4vZeFlw0FlVVyNAAIXJgACePzGBb50o7O1RbxoKgQ";
+
+ @Nonnull
+ public static Map