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