diff --git a/.gitignore b/.gitignore index f50dcce..03601ff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,5 @@ # IDE -.idea/ -.vscode/ .gradle/ .settings/ .metals/ diff --git a/.idea/icon.png b/.idea/icon.png new file mode 100644 index 0000000..065215d Binary files /dev/null and b/.idea/icon.png differ diff --git a/.run/.gitignore b/.run/.gitignore new file mode 100644 index 0000000..90355d9 --- /dev/null +++ b/.run/.gitignore @@ -0,0 +1 @@ +debug.env diff --git a/.run/Morny CLI [pr].run.xml b/.run/Morny CLI [pr].run.xml new file mode 100644 index 0000000..05c90d9 --- /dev/null +++ b/.run/Morny CLI [pr].run.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/.run/Morny Coeur [t][user_dev(env).u_q_c_r_m] [pr].run.xml b/.run/Morny Coeur [t][user_dev(env).u_q_c_r_m] [pr].run.xml new file mode 100644 index 0000000..65e7599 --- /dev/null +++ b/.run/Morny Coeur [t][user_dev(env).u_q_c_r_m] [pr].run.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/.run/Morny Coeur [user_dev(env).u_q_c] [pr].run.xml b/.run/Morny Coeur [user_dev(env).u_q_c] [pr].run.xml new file mode 100644 index 0000000..d1c04b5 --- /dev/null +++ b/.run/Morny Coeur [user_dev(env).u_q_c] [pr].run.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/.run/Morny Coeur [user_dev(env).u_q_c_r_m] [pr].run.xml b/.run/Morny Coeur [user_dev(env).u_q_c_r_m] [pr].run.xml new file mode 100644 index 0000000..a1854ef --- /dev/null +++ b/.run/Morny Coeur [user_dev(env).u_q_c_r_m] [pr].run.xml @@ -0,0 +1,20 @@ + + + + + diff --git a/.run/MornyTests #ALL.run.xml b/.run/MornyTests #ALL.run.xml new file mode 100644 index 0000000..0fae327 --- /dev/null +++ b/.run/MornyTests #ALL.run.xml @@ -0,0 +1,31 @@ + + + + + + + + \ No newline at end of file diff --git a/.run/ServerMain [only-hello].run.xml b/.run/ServerMain [only-hello].run.xml new file mode 100644 index 0000000..c3013eb --- /dev/null +++ b/.run/ServerMain [only-hello].run.xml @@ -0,0 +1,17 @@ + + + + \ No newline at end of file diff --git a/.run/ServerMain [version].run.xml b/.run/ServerMain [version].run.xml new file mode 100644 index 0000000..c1700ba --- /dev/null +++ b/.run/ServerMain [version].run.xml @@ -0,0 +1,17 @@ + + + + \ No newline at end of file diff --git a/.run/debug.env.example b/.run/debug.env.example new file mode 100644 index 0000000..87e5e7a --- /dev/null +++ b/.run/debug.env.example @@ -0,0 +1 @@ +MORNY_TG_TOKEN="2001015432:ABCDEFGH01234567890123456790123456" diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..6baf1fd --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "scalameta.metals", + "scala-lang.scala", + "scala-lang.scala-snippets", + "streetsidesoftware.code-spell-checker", + "vscjava.vscode-java-pack" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..51f7057 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,57 @@ +{ + // 使用 IntelliSense 以得知可用的屬性。 + // 暫留以檢視現有屬性的描述。 + // 如需詳細資訊,請瀏覽: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Morny Coeur [user:dev|q|d|mm|tc:rm|ac|ob|trd|dc|r:m|med] [pr]", + "type": "java", + "request": "launch", + "projectName": "Coeur Morny Cono", + "cwd": "${workspaceFolder}/run", + "mainClass": "cc.sukazyo.cono.morny.ServerMain", + "vmArgs": ["-Djava.net.useSystemProxies=true"], + "envFile": "${workspaceFolder}/.run/debug.env", + "args": [ + "--quiet", + "--debug", + "--username", "sukazyo_deving_bot", + "--master", "793274677", + "--trusted-chat", "-1", + "--auto-cmd", + "--outdated-block", + "--trusted-reader-dinner", "1040613596", + "--dinner-chat", "-1001670950261", + "--report-to", "793274677", + "-medc", "793274677", "-medt", "0,2,18,19", "-medtz", "8", + ] + }, + { + "name": "Morny Test [pr]", + "type": "java", + "request": "launch", + "cwd": "${workspaceFolder}/run", + "vmArgs": ["-Djava.net.useSystemProxies=true"], + "projectName": "Coeur Morny Cono", + "mainClass": "cc.sukazyo.cono.morny.MornyTest", + }, + { + "name": "UniversalTest [pr]", + "type": "java", + "request": "launch", + "cwd": "${workspaceFolder}/run", + "vmArgs": ["-Djava.net.useSystemProxies=true"], + "projectName": "Coeur Morny Cono", + "mainClass": "cc.sukazyo.cono.morny.UniversalTest", + }, + { + "name": "Current File [pr]", + "type": "java", + "request": "launch", + "cwd": "${workspaceFolder}/run", + "vmArgs": ["-Djava.net.useSystemProxies=true"], + "mainClass": "${file}" + }, + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..68bbd20 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,14 @@ +{ + "java.dependency.packagePresentation": "hierarchical", + "java.configuration.updateBuildConfiguration": "disabled", + "java.compile.nullAnalysis.mode": "disabled", + "files.watcherExclude": { + "**/target": true + }, + "cSpell.words": [ + "Coeur", + "Gson", + "Morny", + "userid" + ] +} diff --git a/src/main/scala/cc/sukazyo/cono/morny/core/Log.scala b/src/main/scala/cc/sukazyo/cono/morny/core/Log.scala index 134cd1d..0abcf9a 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/core/Log.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/core/Log.scala @@ -4,8 +4,10 @@ import cc.sukazyo.cono.morny.core.internal.logging.{MornyFormatterConsole, Morny import cc.sukazyo.messiva.appender.ConsoleAppender import cc.sukazyo.messiva.log.LogLevels +/** Logger controller of Morny Coeur program */ object Log { + /** The logger that Morny will use */ val logger: MornyLoggerBase = MornyLoggerBase( ConsoleAppender( MornyFormatterConsole() @@ -13,8 +15,14 @@ object Log { ) logger.minLevel(LogLevels.INFO) + /** If the debug level logging is enabled + * + * @return `true` when the debug logging is enabled, `false` otherwise. + */ def debug: Boolean = logger.levelSetting.minLevel.level <= LogLevels.DEBUG.level + /** Set if the debug logging should be enabled. + */ def debug (is: Boolean): Unit = if is then logger.minLevel(LogLevels.ALL) else logger.minLevel(LogLevels.INFO) diff --git a/src/main/scala/cc/sukazyo/cono/morny/core/MornySystem.scala b/src/main/scala/cc/sukazyo/cono/morny/core/MornySystem.scala index fe0c1ce..5b898fc 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/core/MornySystem.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/core/MornySystem.scala @@ -13,24 +13,100 @@ import java.security.NoSuchAlgorithmException object MornySystem { + /** The normal version string without extra context (`+000` fields). + * + * Should contains base version ([[VERSION_BASE]]), with additional [[VERSION_DELTA version delta]] + * or snapshot tag if exists. + * + * For example `1.0.0`, `2.1.1-beta2`, `0.19.2-SNAPSHOT`, `2.0.0-alpha19-SNAPSHOT` + */ @BuildConfigField val VERSION: String = BuildConfig.VERSION + /** The full specific version string with extra context (`+000` fields). + * + * Should contains all the normal version ([[VERSION]]), additional with git commit hash + * if exists. + * + * For example `2.0.0-alpha19-SNAPSHOT+git254ec2a5` + */ @BuildConfigField val VERSION_FULL: String = BuildConfig.VERSION_FULL + /** The base version string. + * + * Contains only the main version number (0.0.0) and alpha/beta/RC version (-alpha20). NO + * snapshot tag or [[VERSION_DELTA version delta]] is included. + */ @BuildConfigField val VERSION_BASE: String = BuildConfig.VERSION_BASE + /** The version delta string if exists. + * + * This delta is for debug purpose when it is designed, it adds a field to the normal + * version string to help shows if the code is updated in debug environment. + * + * The standard version delta is a string starts with `-ð`, attached after the + * [[VERSION_BASE base version]]. + * + * It is currently not in used in official environment yet. + */ @BuildConfigField val VERSION_DELTA: Option[String] = BuildConfig.VERSION_DELTA + /** The full length git commit id that this build is based on. + * + * Along the workspace is in a checked out git repo, this field will be the currently checked + * out git commit, no matter the git repo is clean or not. + * + * Only exists when this is built with git repo, if there's no git repo, this will only be + * [[None]]. + */ @BuildConfigField val GIT_COMMIT: Option[String] = Some(BuildConfig.COMMIT) + /** The build time in [[EpochMillis]]. + * + * If this build is built with a git repo and the git repo is clean, that means the source + * code should have no changes with [[GIT_COMMIT the currently checked out git commit]], + * then we call this is a **clean build**, and this timestamp will be the [[GIT_COMMIT]]'s + * commit timestamp. This can help us to do a *reproducible build*. + * + * On any other cases, this timestamp will be the [[System.currentTimeMillis]] when the + * `sbt compile` is called. + */ @BuildConfigField val CODE_TIMESTAMP: EpochMillis = BuildConfig.CODE_TIMESTAMP + /** The codename of this version. + * + * Usually a city name, and usually updates on and only on the minor version (X.Y.z) level + * update. + * + * Should be a lowercased english only string (maybe contains numbers at some point, at + * least not historically). + */ @BuildConfigField val CODENAME: String = BuildConfig.CODENAME + /** The HTTP url that a git repository of this project can be found. + */ @BuildConfigField val CODE_STORE: String = BuildConfig.CODE_STORE + /** The string template that the commit url of this build can be found. + * + * Should contains one `%s` placeholder, can be interpolated a [[GIT_COMMIT]] to get the + * page of commit that current build based on. + */ //noinspection ScalaWeakerAccess @BuildConfigField val COMMIT_PATH: String = BuildConfig.COMMIT_PATH + /** If this build is built with a clean git repo. */ @BuildConfigField def isCleanBuild: Boolean = BuildConfig.CLEAN_BUILD + /** The HTTP url of git commit which this build is based on. + * + * Based on [[GIT_COMMIT]] and [[COMMIT_PATH]]. + */ def currentCodePath: String|Null = if ((COMMIT_PATH eq null) || (GIT_COMMIT isEmpty)) null else COMMIT_PATH.formatted(GIT_COMMIT get) + /** The MD5 hash of current running coeur execution jar. + * + * Only works on the current running coeur is from a jar file. + * + * If read jar file failed, it possibly means currently is not run from a jar file, then + * this will return `""`. + * + * If failed on calculating MD5 hash, it will return `""`. + */ def getJarMD5: String = { try { FileUtils.getMD5Three(MornySystem.getClass.getProtectionDomain.getCodeSource.getLocation.toURI.getPath) diff --git a/src/main/scala/cc/sukazyo/cono/morny/core/bot/api/BotExtension.scala b/src/main/scala/cc/sukazyo/cono/morny/core/bot/api/BotExtension.scala index edec824..04a9050 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/core/bot/api/BotExtension.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/core/bot/api/BotExtension.scala @@ -26,6 +26,19 @@ trait BotExtension { extension (user: User) { + /** Get this telegram [[User]]'s prefer language. + * + * It will check the [[User.languageCode]] provided by Telegram API. + * + * If a language code is provided, it will be [[LangTag.normalizeLangTag normalized]] + * to [[LangTag]] and return. + * + * If cannot find a language code associated with this [[User]], the empty string will + * be return. + * + * @return A [[LangTag.normalizeLangTag normalized]] [[LangTag]] that should be this + * [[User]]'s prefer language, or a empty string. + */ def prefer_language: String = user.languageCode match case null => "" diff --git a/src/main/scala/cc/sukazyo/cono/morny/core/bot/api/messages/ErrorMessage.scala b/src/main/scala/cc/sukazyo/cono/morny/core/bot/api/messages/ErrorMessage.scala index 5257a0c..0f3edb3 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/core/bot/api/messages/ErrorMessage.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/core/bot/api/messages/ErrorMessage.scala @@ -27,9 +27,28 @@ import com.pengrad.telegrambot.request.AbstractSendRequest */ trait ErrorMessage[T1 <: AbstractSendRequest[T1], T2 <: AbstractSendRequest[T2]] { + /** The simple variant of this error message. + * + * In Morny, this is usually a sticker that shows the generic type of the error. + * + * Can be get by using [[getByType]] with variant tag [[ErrorMessage.Types.Simple]] + * @since 2.0.0 + */ val simple: T1 + /** The complex variant of this error message. + * + * In Morny, this should be a text-based message that shows key information of the error + * which want to describe. + * + * Can be get by using [[getByType]] with variant tag [[ErrorMessage.Types.Complex]] + * @since 2.0.0 + */ val complex: T2 + /** An context that defines where this error message belongs. Will be used to publish this + * message (aka. send message). + * @since 2.0.0 + */ val context: MessagingContext.WithMessage /** Get the simple or complex message by the given [[ErrorMessage.Types]] infer. @@ -58,12 +77,30 @@ trait ErrorMessage[T1 <: AbstractSendRequest[T1], T2 <: AbstractSendRequest[T2]] } +/** @see [[ErrorMessage]] */ object ErrorMessage { + /** An enum that contains the tag of an [[ErrorMessage]]'s variant type. + * + * Can be used to get the specific type's message of that type using [[ErrorMessage.getByType]]. + * + * @since 2.0.0 + */ enum Types: + /** @see [[ErrorMessage.simple]] */ case Simple + /** @see [[ErrorMessage.complex]] */ case Complex + /** A new [[ErrorMessage]]. + * + * @since 2.0.0 + * @see [[ErrorMessage]] + * + * @param _simple The [[ErrorMessage.simple]] message of this error message. + * @param _complex The [[ErrorMessage.complex]] message of this error message. + * @param cxt The context of this error message, defines where the error message belongs. + */ def apply [T1 <: AbstractSendRequest[T1], T2<: AbstractSendRequest[T2]] (_simple: T1, _complex: T2)(using cxt: MessagingContext.WithMessage): ErrorMessage[T1, T2] = new ErrorMessage[T1, T2]: diff --git a/src/main/scala/cc/sukazyo/cono/morny/core/internal/BuildConfigField.java b/src/main/scala/cc/sukazyo/cono/morny/core/internal/BuildConfigField.java index 92a324c..2b76ae6 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/core/internal/BuildConfigField.java +++ b/src/main/scala/cc/sukazyo/cono/morny/core/internal/BuildConfigField.java @@ -6,7 +6,7 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Target; /** - * 这个注解表示当前字段是由 gradle 任务 {@code generateBuildConfig} 自动生成的. + * The annotation indicates that the field is generated by the sbt plugin {@code sbtBuildInfo}. * @since 1.0.0-alpha4 */ @Documented