diff --git a/build.gradle b/build.gradle index 6ec4e7d..1ba1412 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { id 'application' id 'maven-publish' id 'com.github.johnrengelman.shadow' version '8.1.1' - id 'com.github.gmazzo.buildconfig' version '3.1.0' + id 'com.github.gmazzo.buildconfig' version '4.1.2' id 'org.ajoberstar.grgit' version '5.2.0' } @@ -73,15 +73,16 @@ repositories { dependencies { - compileOnlyApi "com.github.spotbugs:spotbugs-annotations:${libSpotbugsVersion}" + compileOnlyApi "com.github.spotbugs:spotbugs-annotations:${lib_spotbugs_v}" - api "cc.sukazyo:messiva:${libMessivaVersion}" + api "cc.sukazyo:messiva:${lib_messiva_v}" - implementation "com.github.pengrad:java-telegram-bot-api:${libJavaTelegramBotApiVersion}" + implementation "com.github.pengrad:java-telegram-bot-api:${lib_javatelegramapi_v}" + implementation "com.squareup.okhttp3:okhttp:${lib_okhttp_v}" + implementation "com.google.code.gson:gson:${lib_gson_v}" - testImplementation "org.junit.jupiter:junit-jupiter-api:${libJunitVersion}" - testImplementation "org.junit.jupiter:junit-jupiter-params:${libJunitVersion}" - testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${libJunitVersion}" + testImplementation platform("org.junit:junit-bom:${lib_junit_v}") + testImplementation "org.junit.jupiter:junit-jupiter" } @@ -91,6 +92,9 @@ application { test { useJUnitPlatform() + testLogging { + events "passed", "skipped", "failed" + } } java { diff --git a/gradle.properties b/gradle.properties index 9946e84..24b3c02 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ MORNY_ARCHIVE_NAME = morny-coeur MORNY_CODE_STORE = https://github.com/Eyre-S/Coeur-Morny-Cono MORNY_COMMIT_PATH = https://github.com/Eyre-S/Coeur-Morny-Cono/commit/%s -VERSION = 1.0.0-RC3.8 +VERSION = 1.0.0-RC3.9 USE_DELTA = false VERSION_DELTA = @@ -14,10 +14,13 @@ CODENAME = beiping # dependencies -libSpotbugsVersion = 4.7.3 +lib_spotbugs_v = 4.7.3 -libMessivaVersion = 0.1.1 +lib_messiva_v = 0.1.1 -libJavaTelegramBotApiVersion = 6.2.0 +lib_javatelegramapi_v = 6.2.0 -libJunitVersion = 5.9.0 +lib_okhttp_v = 4.11.0 +lib_gson_v = 2.10.1 + +lib_junit_v = 5.10.0 diff --git a/src/main/java/cc/sukazyo/cono/morny/MornyCoeur.java b/src/main/java/cc/sukazyo/cono/morny/MornyCoeur.java index 49f8655..d891a84 100644 --- a/src/main/java/cc/sukazyo/cono/morny/MornyCoeur.java +++ b/src/main/java/cc/sukazyo/cono/morny/MornyCoeur.java @@ -118,7 +118,7 @@ public class MornyCoeur { * @see #MornyCoeur 程序初始化方法 * @param config morny 实例的配置选项数据 */ - public static void main (MornyConfig config) { + public static void init (MornyConfig config) { if (INSTANCE == null) { logger.info("Coeur Starting"); diff --git a/src/main/java/cc/sukazyo/cono/morny/MornyConfig.java b/src/main/java/cc/sukazyo/cono/morny/MornyConfig.java index bdc8893..a3d9907 100644 --- a/src/main/java/cc/sukazyo/cono/morny/MornyConfig.java +++ b/src/main/java/cc/sukazyo/cono/morny/MornyConfig.java @@ -169,12 +169,12 @@ public class MornyConfig { public long eventOutdatedTimestamp = -1; public boolean commandLoginRefresh = false; public boolean commandLogoutClear = false; - @Nonnull public Set dinnerTrustedReaders = new HashSet<>(); + @Nonnull public final Set dinnerTrustedReaders = new HashSet<>(); public long dinnerChatId = -1001707106392L; public long reportToChat = -1001650050443L; public long medicationNotifyToChat = -1001729016815L; @Nonnull public ZoneOffset medicationTimerUseTimezone = ZoneOffset.UTC; - @Nonnull public Set medicationNotifyAt = new HashSet<>(); + @Nonnull public final Set medicationNotifyAt = new HashSet<>(); } diff --git a/src/main/java/cc/sukazyo/cono/morny/MornySystem.java b/src/main/java/cc/sukazyo/cono/morny/MornySystem.java index a4e2933..edd4719 100644 --- a/src/main/java/cc/sukazyo/cono/morny/MornySystem.java +++ b/src/main/java/cc/sukazyo/cono/morny/MornySystem.java @@ -1,7 +1,7 @@ package cc.sukazyo.cono.morny; import cc.sukazyo.cono.morny.daemon.MornyReport; -import cc.sukazyo.cono.morny.util.BuildConfigField; +import cc.sukazyo.cono.morny.internal.BuildConfigField; import cc.sukazyo.cono.morny.util.FileUtils; import javax.annotation.Nonnull; diff --git a/src/main/java/cc/sukazyo/cono/morny/ServerMain.java b/src/main/java/cc/sukazyo/cono/morny/ServerMain.java index 9a0e931..c437d96 100644 --- a/src/main/java/cc/sukazyo/cono/morny/ServerMain.java +++ b/src/main/java/cc/sukazyo/cono/morny/ServerMain.java @@ -79,7 +79,7 @@ public class ServerMain { * 自 {@code 0.5.0.4},旧的直接通过参数为 bot token & username 赋值的方式已被删除 * 使用参数所进行取值的 token 和 username 已被转移至 {@code --token} 和 {@code --username} 参数
* - * @see MornyCoeur#main + * @see MornyCoeur#init * @since 0.4.0.0 * @param args 参数组 */ @@ -143,12 +143,17 @@ public class ServerMain { config.trustedChat = Long.parseLong(args[i]); continue; } - //noinspection SpellCheckingInspection + // noinspection SpellCheckingInspection case "--trusted-reader-dinner", "-trsd" -> { i++; config.dinnerTrustedReaders.add(Long.parseLong(args[i])); continue; } + case "--dinner-chat", "-chd" -> { + i++; + config.dinnerChatId = Long.parseLong(args[i]); + continue; + } case "--auto-cmd", "-cmd", "-c" -> { config.commandLoginRefresh = true; config.commandLogoutClear = true; @@ -278,7 +283,7 @@ public class ServerMain { Thread.currentThread().setName(THREAD_MORNY_INIT); try { - MornyCoeur.main(new MornyConfig(config)); + MornyCoeur.init(new MornyConfig(config)); } catch (MornyConfig.CheckFailure.NullTelegramBotKey ignore) { logger.info("Parameter required has no value:\n --token."); } catch (MornyConfig.CheckFailure e) { diff --git a/src/main/java/cc/sukazyo/cono/morny/bot/command/EventHack.java b/src/main/java/cc/sukazyo/cono/morny/bot/command/EventHack.java index ddbe9e2..dbcfc81 100644 --- a/src/main/java/cc/sukazyo/cono/morny/bot/command/EventHack.java +++ b/src/main/java/cc/sukazyo/cono/morny/bot/command/EventHack.java @@ -37,50 +37,57 @@ public class EventHack implements ITelegramCommand { @Override public void execute (@Nonnull InputCommand command, @Nonnull Update event) { - boolean isOk = false; + enum Status { + OK, + FORBIDDEN_FOR_ANY + } + Status status; String x_mode = ""; if (command.hasArgs()) { x_mode = command.getArgs()[0]; } - switch (x_mode) { - case "any": + case "any" -> { if (MornyCoeur.trustedInstance().isTrusted(event.message().from().id())) { OnEventHackHandle.registerHack( event.message().messageId(), event.message().from().id(), event.message().chat().id(), OnEventHackHandle.HackType.ANY - );isOk = true; + ); + status = Status.OK; + } else { + status = Status.FORBIDDEN_FOR_ANY; } - break; - case "group": + } + case "group" -> { OnEventHackHandle.registerHack( event.message().messageId(), event.message().from().id(), event.message().chat().id(), OnEventHackHandle.HackType.GROUP - );isOk = true; - break; - default: + ); + status = Status.OK; + } + default -> { OnEventHackHandle.registerHack( event.message().messageId(), event.message().from().id(), event.message().chat().id(), OnEventHackHandle.HackType.USER - );isOk = true; - break; + ); + status = Status.OK; + } } - if (isOk) { - MornyCoeur.extra().exec(new SendSticker( + switch (status) { + case OK -> MornyCoeur.extra().exec(new SendSticker( event.message().chat().id(), TelegramStickers.ID_WAITING ).replyToMessageId(event.message().messageId()) ); - } else { - MornyCoeur.extra().exec(new SendSticker( + case FORBIDDEN_FOR_ANY -> MornyCoeur.extra().exec(new SendSticker( event.message().chat().id(), TelegramStickers.ID_403 ).replyToMessageId(event.message().messageId()) diff --git a/src/main/java/cc/sukazyo/cono/morny/bot/event/OnRandomlyTriggered.java b/src/main/java/cc/sukazyo/cono/morny/bot/event/OnRandomlyTriggered.java index d710459..5481a08 100644 --- a/src/main/java/cc/sukazyo/cono/morny/bot/event/OnRandomlyTriggered.java +++ b/src/main/java/cc/sukazyo/cono/morny/bot/event/OnRandomlyTriggered.java @@ -4,10 +4,10 @@ import cc.sukazyo.cono.morny.bot.api.EventListener; public class OnRandomlyTriggered extends EventListener { - /** - * function CODE_IK0XA1 - */ - // @Override +// /** +// * function CODE_IK0XA1 +// */ +// // @Override // public boolean onMessage (@Nonnull Update update) { // // if (update.message().text() == null) return false; diff --git a/src/main/java/cc/sukazyo/cono/morny/daemon/TrackerDataManager.java b/src/main/java/cc/sukazyo/cono/morny/daemon/TrackerDataManager.java index 264a69a..f8a99fc 100644 --- a/src/main/java/cc/sukazyo/cono/morny/daemon/TrackerDataManager.java +++ b/src/main/java/cc/sukazyo/cono/morny/daemon/TrackerDataManager.java @@ -1,11 +1,11 @@ package cc.sukazyo.cono.morny.daemon; import java.io.File; -import java.io.FileOutputStream; 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; @@ -15,9 +15,11 @@ import static cc.sukazyo.cono.morny.Log.logger; public class TrackerDataManager { + /** {@link TrackerDaemon} 的锁。保证在程序中只有一个 TrackerDaemon 会运行。 */ public static final ReentrantLock trackingLock = new ReentrantLock(); - + /** {@link #record Tracker 缓存}的锁

为保证对 Tracker 缓存的操作不会造成线程冲突,在操作缓存数据前应先取得此锁。 */ private static final ReentrantLock recordLock = new ReentrantLock(); + /** Tracker 数据的内存缓存

进行数据操作前请先取得对应的{@link #recordLock 锁} */ private static HashMap>> record = new HashMap<>(); public static final TrackerDaemon DAEMON = new TrackerDaemon(); @@ -55,6 +57,15 @@ public class TrackerDataManager { } + /** + * 向 Tracker 缓存写入一条 tracker 数据. + *

+ * 这个方法对于 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<>()); @@ -65,16 +76,36 @@ public class TrackerDataManager { recordLock.unlock(); } + /** + * 开启 {@link TrackerDaemon}. + *

+ * 由于 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>> reset () { recordLock.lock(); HashMap>> recordOld = record; @@ -83,6 +114,11 @@ public class TrackerDataManager { return recordOld; } + /** + * 将 Tracker 数据写入到硬盘. + * + * @param record 需要保存的 Tracker 数据集 + */ private static void save (HashMap>> record) { { @@ -109,10 +145,12 @@ public class TrackerDataManager { } assert channelCurrent != null; - channelCurrent.write(ByteBuffer.wrap( + final int result = channelCurrent.write(ByteBuffer.wrap( String.format("%d\n", timestamp).getBytes(StandardCharsets.UTF_8) )); + if (result == 0) logger.warn("writing tracker data %d/%d/%d: write only 0 bytes! is anything wrong?"); + } catch (Exception e) { final String message = String.format("exception in write tracker data: %d/%d/%d", chat, user, timestamp); logger.error(message); @@ -130,7 +168,7 @@ public class TrackerDataManager { if (!data.isDirectory()) if (!data.mkdirs()) throw new IOException("Cannot create file directory " + data.getPath()); File file = new File(data, String.valueOf(day)); if (!file.isFile()) if (!file.createNewFile()) throw new IOException("Cannot create file " + file.getPath()); - return new FileOutputStream(file, true).getChannel(); + return FileChannel.open(file.toPath(), StandardOpenOption.APPEND); } } diff --git a/src/main/java/cc/sukazyo/cono/morny/data/NbnhhshQuery.java b/src/main/java/cc/sukazyo/cono/morny/data/NbnhhshQuery.java index cd1be74..2ed6657 100644 --- a/src/main/java/cc/sukazyo/cono/morny/data/NbnhhshQuery.java +++ b/src/main/java/cc/sukazyo/cono/morny/data/NbnhhshQuery.java @@ -4,7 +4,7 @@ import java.io.IOException; import com.google.gson.Gson; -import okhttp3.MediaType; +import cc.sukazyo.cono.morny.util.OkHttpPublic.MediaTypes; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; @@ -23,21 +23,18 @@ public class NbnhhshQuery { public Word[] words; } - public record GuessReq (String text) { - } + public record GuessReq (String text) {} public static final String API_URL = "https://lab.magiconch.com/api/nbnhhsh/"; public static final String API_GUESS_METHOD = "guess/"; - public static final String API_GUESS_DATA_TEMPLATE = "{ \"text\": \"%s\" }"; private static final OkHttpClient httpClient = new OkHttpClient(); - public static final MediaType JSON = MediaType.get("application/json; charset=utf-8"); public static GuessResult sendGuess (String text) throws IOException { final String reqJsonText = new Gson().toJson(new GuessReq(text)); Request request = new Request.Builder() .url(API_URL + API_GUESS_METHOD) - .post(RequestBody.create(JSON, reqJsonText)) + .post(RequestBody.create(reqJsonText, MediaTypes.JSON)) .build(); try (Response response = httpClient.newCall(request).execute()) { final ResponseBody body = response.body(); diff --git a/src/main/java/cc/sukazyo/cono/morny/util/BuildConfigField.java b/src/main/java/cc/sukazyo/cono/morny/internal/BuildConfigField.java similarity index 89% rename from src/main/java/cc/sukazyo/cono/morny/util/BuildConfigField.java rename to src/main/java/cc/sukazyo/cono/morny/internal/BuildConfigField.java index c584a8b..f9931ce 100644 --- a/src/main/java/cc/sukazyo/cono/morny/util/BuildConfigField.java +++ b/src/main/java/cc/sukazyo/cono/morny/internal/BuildConfigField.java @@ -1,4 +1,4 @@ -package cc.sukazyo.cono.morny.util; +package cc.sukazyo.cono.morny.internal; import java.lang.annotation.Documented; diff --git a/src/main/java/cc/sukazyo/cono/morny/util/OkHttpPublic.java b/src/main/java/cc/sukazyo/cono/morny/util/OkHttpPublic.java new file mode 100644 index 0000000..fb6b32a --- /dev/null +++ b/src/main/java/cc/sukazyo/cono/morny/util/OkHttpPublic.java @@ -0,0 +1,13 @@ +package cc.sukazyo.cono.morny.util; + +import okhttp3.MediaType; + +public class OkHttpPublic { + + public static class MediaTypes { + + public static final MediaType JSON = MediaType.get("application/json; charset=utf-8"); + + } + +} diff --git a/src/test/java/cc/sukazyo/cono/morny/MornyCLI.java b/src/test/java/cc/sukazyo/cono/morny/MornyCLI.java index ef788b2..e634582 100644 --- a/src/test/java/cc/sukazyo/cono/morny/MornyCLI.java +++ b/src/test/java/cc/sukazyo/cono/morny/MornyCLI.java @@ -8,9 +8,9 @@ public class MornyCLI { public static void main (String[] args) { - Scanner line = new Scanner(System.in); System.out.print("$ java -jar morny-coeur-"+MornySystem.VERSION_FULL+".jar " ); - String x = line.nextLine(); + String x; + try (Scanner line = new Scanner(System.in)) { x = line.nextLine(); } ServerMain.main(UniversalCommand.format(x)); } diff --git a/src/test/java/cc/sukazyo/cono/morny/util/TestCommonConvert.java b/src/test/java/cc/sukazyo/cono/morny/util/TestCommonConvert.java index 8fd9ed7..d35e55f 100644 --- a/src/test/java/cc/sukazyo/cono/morny/util/TestCommonConvert.java +++ b/src/test/java/cc/sukazyo/cono/morny/util/TestCommonConvert.java @@ -1,16 +1,12 @@ package cc.sukazyo.cono.morny.util; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.CsvSource; -import org.junit.jupiter.params.provider.MethodSource; - -import java.util.stream.Stream; +import org.junit.jupiter.params.provider.EnumSource; import static cc.sukazyo.cono.morny.util.CommonConvert.byteArrayToHex; import static cc.sukazyo.cono.morny.util.CommonConvert.byteToHex; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.params.provider.Arguments.arguments; public class TestCommonConvert { @@ -30,21 +26,22 @@ public class TestCommonConvert { assertEquals(expected, byteToHex(source)); } - public static Stream testByteArrayToHexProvider () { - return Stream.of( - arguments(new byte[]{0x00}, "00"), - arguments(new byte[]{(byte)0xff}, "ff"), - arguments(new byte[]{(byte)0xc3}, "c3"), - arguments(new byte[]{}, ""), - arguments(new byte[]{0x30,0x0a,0x00,0x04,(byte)0xb0,0x00}, "300a0004b000"), - arguments(new byte[]{0x00,0x00,0x0a,(byte)0xff,(byte)0xfc,(byte)0xab,(byte)0x00,0x04}, "00000afffcab0004"), - arguments(new byte[]{0x00,0x7c,0x11,0x28,(byte)0x88,(byte)0xa6,(byte)0xfc,0x30}, "007c112888a6fc30") - ); + public enum TestByteArrayToHexSource { + $1(new T(new byte[]{0x00}, "00")), + $2(new T(new byte[]{(byte)0xff}, "ff")), + $3(new T(new byte[]{(byte)0xc3}, "c3")), + $4(new T(new byte[]{}, "")), + $5(new T(new byte[]{0x30,0x0a,0x00,0x04,(byte)0xb0,0x00}, "300a0004b000")), + $6(new T(new byte[]{0x00,0x00,0x0a,(byte)0xff,(byte)0xfc,(byte)0xab,(byte)0x00,0x04}, "00000afffcab0004")), + $7(new T(new byte[]{0x00,0x7c,0x11,0x28,(byte)0x88,(byte)0xa6,(byte)0xfc,0x30}, "007c112888a6fc30")); + public record T (byte[] raw, String expected) {} + public final T value; + TestByteArrayToHexSource (T value) { this.value = value; } } @ParameterizedTest - @MethodSource("testByteArrayToHexProvider") - void testByteArrayToHex (byte[] raw, String expected) { - assertEquals(expected, byteArrayToHex(raw)); + @EnumSource + void testByteArrayToHex (TestByteArrayToHexSource source) { + assertEquals(source.value.expected, byteArrayToHex(source.value.raw)); } }