added --dinner-chat startup param, code quality optimization.

- added `--dinner-chat`/`-chd` startup param for setup dinner chat id
- fixed some unclosed resource
  - TrackerDataManager changed method to get FileChannel (I don't know if it works)
  - MornyCLI scanner uses try-with-resources
- declared directly used dependencies
  - okhttp: 4.11.0
  - gson: 2.10.1
- some minor refactors
  - MornyCoeur.main rename to .init
  - added package `internal` and move @BuildConfigField to it
  - added new util class OkHttpPublic with MediaTypes in use
  - added javadoc for TrackerDataManager
  - and so on...
This commit is contained in:
A.C.Sukazyo Eyre 2023-08-20 23:11:10 +08:00
parent 69a33933f5
commit 7589e8661d
Signed by: Eyre_S
GPG Key ID: C17CE40291207874
14 changed files with 133 additions and 69 deletions

View File

@ -4,7 +4,7 @@ plugins {
id 'application' id 'application'
id 'maven-publish' id 'maven-publish'
id 'com.github.johnrengelman.shadow' version '8.1.1' 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' id 'org.ajoberstar.grgit' version '5.2.0'
} }
@ -73,15 +73,16 @@ repositories {
dependencies { 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 platform("org.junit:junit-bom:${lib_junit_v}")
testImplementation "org.junit.jupiter:junit-jupiter-params:${libJunitVersion}" testImplementation "org.junit.jupiter:junit-jupiter"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${libJunitVersion}"
} }
@ -91,6 +92,9 @@ application {
test { test {
useJUnitPlatform() useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed"
}
} }
java { java {

View File

@ -5,7 +5,7 @@ MORNY_ARCHIVE_NAME = morny-coeur
MORNY_CODE_STORE = https://github.com/Eyre-S/Coeur-Morny-Cono MORNY_CODE_STORE = https://github.com/Eyre-S/Coeur-Morny-Cono
MORNY_COMMIT_PATH = https://github.com/Eyre-S/Coeur-Morny-Cono/commit/%s 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 USE_DELTA = false
VERSION_DELTA = VERSION_DELTA =
@ -14,10 +14,13 @@ CODENAME = beiping
# dependencies # 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

View File

@ -118,7 +118,7 @@ public class MornyCoeur {
* @see #MornyCoeur 程序初始化方法 * @see #MornyCoeur 程序初始化方法
* @param config morny 实例的配置选项数据 * @param config morny 实例的配置选项数据
*/ */
public static void main (MornyConfig config) { public static void init (MornyConfig config) {
if (INSTANCE == null) { if (INSTANCE == null) {
logger.info("Coeur Starting"); logger.info("Coeur Starting");

View File

@ -169,12 +169,12 @@ public class MornyConfig {
public long eventOutdatedTimestamp = -1; public long eventOutdatedTimestamp = -1;
public boolean commandLoginRefresh = false; public boolean commandLoginRefresh = false;
public boolean commandLogoutClear = false; public boolean commandLogoutClear = false;
@Nonnull public Set<Long> dinnerTrustedReaders = new HashSet<>(); @Nonnull public final Set<Long> dinnerTrustedReaders = new HashSet<>();
public long dinnerChatId = -1001707106392L; public long dinnerChatId = -1001707106392L;
public long reportToChat = -1001650050443L; public long reportToChat = -1001650050443L;
public long medicationNotifyToChat = -1001729016815L; public long medicationNotifyToChat = -1001729016815L;
@Nonnull public ZoneOffset medicationTimerUseTimezone = ZoneOffset.UTC; @Nonnull public ZoneOffset medicationTimerUseTimezone = ZoneOffset.UTC;
@Nonnull public Set<Integer> medicationNotifyAt = new HashSet<>(); @Nonnull public final Set<Integer> medicationNotifyAt = new HashSet<>();
} }

View File

@ -1,7 +1,7 @@
package cc.sukazyo.cono.morny; package cc.sukazyo.cono.morny;
import cc.sukazyo.cono.morny.daemon.MornyReport; 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 cc.sukazyo.cono.morny.util.FileUtils;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;

View File

@ -79,7 +79,7 @@ public class ServerMain {
* <b> {@code 0.5.0.4}旧的直接通过参数为 bot token & username 赋值的方式已被删除</b> * <b> {@code 0.5.0.4}旧的直接通过参数为 bot token & username 赋值的方式已被删除</b>
* 使用参数所进行取值的 token username 已被转移至 {@code --token} {@code --username} 参数<br> * 使用参数所进行取值的 token username 已被转移至 {@code --token} {@code --username} 参数<br>
* *
* @see MornyCoeur#main * @see MornyCoeur#init
* @since 0.4.0.0 * @since 0.4.0.0
* @param args 参数组 * @param args 参数组
*/ */
@ -143,12 +143,17 @@ public class ServerMain {
config.trustedChat = Long.parseLong(args[i]); config.trustedChat = Long.parseLong(args[i]);
continue; continue;
} }
//noinspection SpellCheckingInspection // noinspection SpellCheckingInspection
case "--trusted-reader-dinner", "-trsd" -> { case "--trusted-reader-dinner", "-trsd" -> {
i++; i++;
config.dinnerTrustedReaders.add(Long.parseLong(args[i])); config.dinnerTrustedReaders.add(Long.parseLong(args[i]));
continue; continue;
} }
case "--dinner-chat", "-chd" -> {
i++;
config.dinnerChatId = Long.parseLong(args[i]);
continue;
}
case "--auto-cmd", "-cmd", "-c" -> { case "--auto-cmd", "-cmd", "-c" -> {
config.commandLoginRefresh = true; config.commandLoginRefresh = true;
config.commandLogoutClear = true; config.commandLogoutClear = true;
@ -278,7 +283,7 @@ public class ServerMain {
Thread.currentThread().setName(THREAD_MORNY_INIT); Thread.currentThread().setName(THREAD_MORNY_INIT);
try { try {
MornyCoeur.main(new MornyConfig(config)); MornyCoeur.init(new MornyConfig(config));
} catch (MornyConfig.CheckFailure.NullTelegramBotKey ignore) { } catch (MornyConfig.CheckFailure.NullTelegramBotKey ignore) {
logger.info("Parameter required has no value:\n --token."); logger.info("Parameter required has no value:\n --token.");
} catch (MornyConfig.CheckFailure e) { } catch (MornyConfig.CheckFailure e) {

View File

@ -37,50 +37,57 @@ public class EventHack implements ITelegramCommand {
@Override @Override
public void execute (@Nonnull InputCommand command, @Nonnull Update event) { public void execute (@Nonnull InputCommand command, @Nonnull Update event) {
boolean isOk = false; enum Status {
OK,
FORBIDDEN_FOR_ANY
}
Status status;
String x_mode = ""; String x_mode = "";
if (command.hasArgs()) { if (command.hasArgs()) {
x_mode = command.getArgs()[0]; x_mode = command.getArgs()[0];
} }
switch (x_mode) { switch (x_mode) {
case "any": case "any" -> {
if (MornyCoeur.trustedInstance().isTrusted(event.message().from().id())) { if (MornyCoeur.trustedInstance().isTrusted(event.message().from().id())) {
OnEventHackHandle.registerHack( OnEventHackHandle.registerHack(
event.message().messageId(), event.message().messageId(),
event.message().from().id(), event.message().from().id(),
event.message().chat().id(), event.message().chat().id(),
OnEventHackHandle.HackType.ANY OnEventHackHandle.HackType.ANY
);isOk = true; );
status = Status.OK;
} else {
status = Status.FORBIDDEN_FOR_ANY;
} }
break; }
case "group": case "group" -> {
OnEventHackHandle.registerHack( OnEventHackHandle.registerHack(
event.message().messageId(), event.message().messageId(),
event.message().from().id(), event.message().from().id(),
event.message().chat().id(), event.message().chat().id(),
OnEventHackHandle.HackType.GROUP OnEventHackHandle.HackType.GROUP
);isOk = true; );
break; status = Status.OK;
default: }
default -> {
OnEventHackHandle.registerHack( OnEventHackHandle.registerHack(
event.message().messageId(), event.message().messageId(),
event.message().from().id(), event.message().from().id(),
event.message().chat().id(), event.message().chat().id(),
OnEventHackHandle.HackType.USER OnEventHackHandle.HackType.USER
);isOk = true; );
break; status = Status.OK;
}
} }
if (isOk) { switch (status) {
MornyCoeur.extra().exec(new SendSticker( case OK -> MornyCoeur.extra().exec(new SendSticker(
event.message().chat().id(), event.message().chat().id(),
TelegramStickers.ID_WAITING TelegramStickers.ID_WAITING
).replyToMessageId(event.message().messageId()) ).replyToMessageId(event.message().messageId())
); );
} else { case FORBIDDEN_FOR_ANY -> MornyCoeur.extra().exec(new SendSticker(
MornyCoeur.extra().exec(new SendSticker(
event.message().chat().id(), event.message().chat().id(),
TelegramStickers.ID_403 TelegramStickers.ID_403
).replyToMessageId(event.message().messageId()) ).replyToMessageId(event.message().messageId())

View File

@ -4,10 +4,10 @@ import cc.sukazyo.cono.morny.bot.api.EventListener;
public class OnRandomlyTriggered extends EventListener { public class OnRandomlyTriggered extends EventListener {
/** // /**
* function CODE_IK0XA1 // * function CODE_IK0XA1
*/ // */
// @Override // // @Override
// public boolean onMessage (@Nonnull Update update) { // public boolean onMessage (@Nonnull Update update) {
// //
// if (update.message().text() == null) return false; // if (update.message().text() == null) return false;

View File

@ -1,11 +1,11 @@
package cc.sukazyo.cono.morny.daemon; package cc.sukazyo.cono.morny.daemon;
import java.io.File; import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.StandardOpenOption;
import java.util.HashMap; import java.util.HashMap;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@ -15,9 +15,11 @@ import static cc.sukazyo.cono.morny.Log.logger;
public class TrackerDataManager { public class TrackerDataManager {
/** {@link TrackerDaemon} 的锁。保证在程序中只有一个 TrackerDaemon 会运行。 */
public static final ReentrantLock trackingLock = new ReentrantLock(); public static final ReentrantLock trackingLock = new ReentrantLock();
/** {@link #record Tracker 缓存}的锁 <p> 为保证对 Tracker 缓存的操作不会造成线程冲突,在操作缓存数据前应先取得此锁。 */
private static final ReentrantLock recordLock = new ReentrantLock(); private static final ReentrantLock recordLock = new ReentrantLock();
/** Tracker 数据的内存缓存 <p> 进行数据操作前请先取得对应的{@link #recordLock 锁} */
private static HashMap<Long, HashMap<Long, TreeSet<Long>>> record = new HashMap<>(); private static HashMap<Long, HashMap<Long, TreeSet<Long>>> record = new HashMap<>();
public static final TrackerDaemon DAEMON = new TrackerDaemon(); public static final TrackerDaemon DAEMON = new TrackerDaemon();
@ -55,6 +57,15 @@ public class TrackerDataManager {
} }
/**
* Tracker 缓存写入一条 tracker 数据.
* <p>
* <font color=green>这个方法对于 Tracker 缓存是原子化的</font>
*
* @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) { public static void record (long chat, long user, long timestamp) {
recordLock.lock(); recordLock.lock();
if (!record.containsKey(chat)) record.put(chat, new HashMap<>()); if (!record.containsKey(chat)) record.put(chat, new HashMap<>());
@ -65,16 +76,36 @@ public class TrackerDataManager {
recordLock.unlock(); recordLock.unlock();
} }
/**
* 开启 {@link TrackerDaemon}.
* <p>
* <font color=orange>由于 Tracker 已废弃这个方法已无作用</font>
*/
@SuppressWarnings("unused")
public static void init () { public static void init () {
DAEMON.start(); DAEMON.start();
} }
/**
* 执行 Tracker 的保存逻辑.
* @see #reset() 弹出 Tracker 缓存
* @see #save(HashMap) 执行硬盘写入操作
*/
public static void save () { public static void save () {
logger.info("start writing tracker data."); logger.info("start writing tracker data.");
save(reset()); save(reset());
logger.info("done writing tracker data."); logger.info("done writing tracker data.");
} }
/**
* Tracker 的缓存数据弹出.
* <p>
* 这个方法将返回现在 Tracker 的所有缓存数据然后清除缓存
* <p>
* <font color=green>这个方法对于 Tracker 缓存是原子化的</font>
*
* @return 当前 Tracker 所包含的内容
*/
private static HashMap<Long, HashMap<Long, TreeSet<Long>>> reset () { private static HashMap<Long, HashMap<Long, TreeSet<Long>>> reset () {
recordLock.lock(); recordLock.lock();
HashMap<Long, HashMap<Long, TreeSet<Long>>> recordOld = record; HashMap<Long, HashMap<Long, TreeSet<Long>>> recordOld = record;
@ -83,6 +114,11 @@ public class TrackerDataManager {
return recordOld; return recordOld;
} }
/**
* Tracker 数据写入到硬盘.
*
* @param record 需要保存的 Tracker 数据集
*/
private static void save (HashMap<Long, HashMap<Long, TreeSet<Long>>> record) { private static void save (HashMap<Long, HashMap<Long, TreeSet<Long>>> record) {
{ {
@ -109,10 +145,12 @@ public class TrackerDataManager {
} }
assert channelCurrent != null; assert channelCurrent != null;
channelCurrent.write(ByteBuffer.wrap( final int result = channelCurrent.write(ByteBuffer.wrap(
String.format("%d\n", timestamp).getBytes(StandardCharsets.UTF_8) 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) { } catch (Exception e) {
final String message = String.format("exception in write tracker data: %d/%d/%d", chat, user, timestamp); final String message = String.format("exception in write tracker data: %d/%d/%d", chat, user, timestamp);
logger.error(message); 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()); if (!data.isDirectory()) if (!data.mkdirs()) throw new IOException("Cannot create file directory " + data.getPath());
File file = new File(data, String.valueOf(day)); File file = new File(data, String.valueOf(day));
if (!file.isFile()) if (!file.createNewFile()) throw new IOException("Cannot create file " + file.getPath()); 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);
} }
} }

View File

@ -4,7 +4,7 @@ import java.io.IOException;
import com.google.gson.Gson; import com.google.gson.Gson;
import okhttp3.MediaType; import cc.sukazyo.cono.morny.util.OkHttpPublic.MediaTypes;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.RequestBody; import okhttp3.RequestBody;
@ -23,21 +23,18 @@ public class NbnhhshQuery {
public Word[] words; 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_URL = "https://lab.magiconch.com/api/nbnhhsh/";
public static final String API_GUESS_METHOD = "guess/"; 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(); 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 { public static GuessResult sendGuess (String text) throws IOException {
final String reqJsonText = new Gson().toJson(new GuessReq(text)); final String reqJsonText = new Gson().toJson(new GuessReq(text));
Request request = new Request.Builder() Request request = new Request.Builder()
.url(API_URL + API_GUESS_METHOD) .url(API_URL + API_GUESS_METHOD)
.post(RequestBody.create(JSON, reqJsonText)) .post(RequestBody.create(reqJsonText, MediaTypes.JSON))
.build(); .build();
try (Response response = httpClient.newCall(request).execute()) { try (Response response = httpClient.newCall(request).execute()) {
final ResponseBody body = response.body(); final ResponseBody body = response.body();

View File

@ -1,4 +1,4 @@
package cc.sukazyo.cono.morny.util; package cc.sukazyo.cono.morny.internal;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;

View File

@ -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");
}
}

View File

@ -8,9 +8,9 @@ public class MornyCLI {
public static void main (String[] args) { public static void main (String[] args) {
Scanner line = new Scanner(System.in);
System.out.print("$ java -jar morny-coeur-"+MornySystem.VERSION_FULL+".jar " ); 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)); ServerMain.main(UniversalCommand.format(x));
} }

View File

@ -1,16 +1,12 @@
package cc.sukazyo.cono.morny.util; package cc.sukazyo.cono.morny.util;
import org.junit.jupiter.params.ParameterizedTest; 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.CsvSource;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.EnumSource;
import java.util.stream.Stream;
import static cc.sukazyo.cono.morny.util.CommonConvert.byteArrayToHex; import static cc.sukazyo.cono.morny.util.CommonConvert.byteArrayToHex;
import static cc.sukazyo.cono.morny.util.CommonConvert.byteToHex; import static cc.sukazyo.cono.morny.util.CommonConvert.byteToHex;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.params.provider.Arguments.arguments;
public class TestCommonConvert { public class TestCommonConvert {
@ -30,21 +26,22 @@ public class TestCommonConvert {
assertEquals(expected, byteToHex(source)); assertEquals(expected, byteToHex(source));
} }
public static Stream<Arguments> testByteArrayToHexProvider () { public enum TestByteArrayToHexSource {
return Stream.of( $1(new T(new byte[]{0x00}, "00")),
arguments(new byte[]{0x00}, "00"), $2(new T(new byte[]{(byte)0xff}, "ff")),
arguments(new byte[]{(byte)0xff}, "ff"), $3(new T(new byte[]{(byte)0xc3}, "c3")),
arguments(new byte[]{(byte)0xc3}, "c3"), $4(new T(new byte[]{}, "")),
arguments(new byte[]{}, ""), $5(new T(new byte[]{0x30,0x0a,0x00,0x04,(byte)0xb0,0x00}, "300a0004b000")),
arguments(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")),
arguments(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"));
arguments(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 @ParameterizedTest
@MethodSource("testByteArrayToHexProvider") @EnumSource
void testByteArrayToHex (byte[] raw, String expected) { void testByteArrayToHex (TestByteArrayToHexSource source) {
assertEquals(expected, byteArrayToHex(raw)); assertEquals(source.value.expected, byteArrayToHex(source.value.raw));
} }
} }