Changed Autoanswer implementation (cooldown timer, message routing) and its documentation

This commit is contained in:
Marc Froehlich
2026-07-26 11:04:47 +02:00
parent ee974bbd73
commit 333063f350
8 changed files with 161 additions and 59 deletions
@@ -43,7 +43,7 @@ public class ApplicationConstants {
public static final String DISCONNECT_RDR_POISONPILL = "UNKNOWN: KST4C KILL POISONPILL_KILLTHREAD=: " + sessionRuntimeUniqueId; //whereever a (blocking) udp or tcp reader in an infinite loop gets this message, it will break this loop
public static final String AUTOANSWER_PREFIX = "[KST4C Automsg] "; // hard-coded marker (user can't remove it)
public static final String AUTOANSWER_PREFIX = "[KST4C Automsg]"; // hard-coded marker (user cannot remove it)
/**
* UI message retention limits.
@@ -47,13 +47,12 @@ public class MessageBusManagementThread extends Thread {
private final String PTRN_QRG_CAT3 = "(([0-9]{3,5}[\\.|,| ]?[0-9]{3})([\\.|,][\\d]{1,2})?)|(([a-zA-Z][0-4]{1}[\\d]{2}\\b)([\\.|,][\\d]{1,2}\\b)?)|((\\b[0-4]{1}[\\d]{2}\\b)([\\.|,][\\d]{1,2}\\b)?)";
// ==== Autoanswer Flood/Pingpong Protection ====
private static final String AUTOANSWER_PREFIX = ApplicationConstants.AUTOANSWER_PREFIX; // hard-coded marker (user can't remove it)
private static final long AUTOANSWER_COOLDOWN_MS = 45_000L; // 45_000L = 45s
// ==== Auto-answer flood/ping-pong protection ====
private static final String AUTOANSWER_PREFIX = ApplicationConstants.AUTOANSWER_PREFIX;
private static final long AUTOANSWER_COOLDOWN_MS = 120_000L; // two minutes
// Cooldown per opponent station (and ChatCategory) only setted if this client sends
// Cooldown per remote station and chat category; updated only after this client sends.
private final Hashtable<String, Long> lastLocalAutoAnswerPerRemoteMs = new Hashtable<>();
// BufferedWriter bufwrtrDBGMSGOut;
// private String text;
@@ -829,7 +828,11 @@ public class MessageBusManagementThread extends Thread {
versionInfo.setSender(itsMe);
versionInfo.setReceiver(newMessageArrived.getSender());
versionInfo.setMessageText("/CQ " + newMessageArrived.getSender().getCallSign() + " " + ApplicationConstants.AUTOANSWER_PREFIX + " " + "KST4Contest " + " v" + ApplicationConstants.APPLICATION_CURRENT_VERSION + " by DO5AMF");
versionInfo.setChatCategory(newMessageArrived.getChatCategory());
versionInfo.setMessageText("/CQ " + newMessageArrived.getSender().getCallSign()
+ " " + AUTOANSWER_PREFIX
+ " KST4Contest v" + ApplicationConstants.APPLICATION_CURRENT_VERSION
+ " by DO5AMF");
this.client.getMessageTXBus().add(versionInfo);
}
@@ -876,11 +879,11 @@ public class MessageBusManagementThread extends Thread {
// }
// }
// ==== Unified Autoanswer (Generic + QRG) with Pingpong-Guard + per-Remote Cooldown ====
// ==== Unified auto-answer (generic + QRG) with ping-pong guard and per-remote cooldown ====
final String incomingText = newMessageArrived.getMessageText();
final String incomingLower = (incomingText == null) ? "" : incomingText.toLowerCase(Locale.ROOT);
// 1) Pingpong-security: never ever react to auto generated messages
// Never answer another automatically generated message.
if (!isAutoMessage(newMessageArrived)) {
boolean qrgRequested = false;
@@ -896,24 +899,17 @@ public class MessageBusManagementThread extends Thread {
boolean genericEnabled = this.client.getChatPreferences().isMsgHandling_autoAnswerEnabled();
// 2) Entscheide, ob überhaupt geantwortet wird (QRG hat Vorrang vor Generic)
// A QRG reply takes precedence over the generic reply.
String payload = null;
if (qrgRequested) {
if (this.client.getChatPreferences().isLoginToSecondChatEnabled()) {
payload = "QRGs: " + this.client.getChatPreferences().getMYQRGFirstCat().getValue()
+ " / " + this.client.getChatPreferences().getMYQRGSecondCat().getValue();
} else {
payload = "QRG is: " + this.client.getChatPreferences().getMYQRGFirstCat().getValue();
}
payload = "QRG is: " + getAutoAnswerQrgForCategory(newMessageArrived.getChatCategory());
} else if (genericEnabled) {
payload = this.client.getChatPreferences().getMessageHandling_autoAnswerTextMainCat();
}
// 3) Cooldown pro Gegenstation: nur wenn DIESER Client jetzt wirklich sendet
// Apply the cooldown only when this client is about to send a reply.
if (payload != null && isAutoAnswerAllowedNow(newMessageArrived)) {
ChatMessage automaticAnswer = new ChatMessage();
@@ -922,15 +918,15 @@ public class MessageBusManagementThread extends Thread {
automaticAnswer.setSender(itsMe);
automaticAnswer.setReceiver(newMessageArrived.getSender());
automaticAnswer.setChatCategory(newMessageArrived.getChatCategory());
// Prefix fest + nicht entfernbar, damit Auto↔Auto nicht pingpongt
// The fixed prefix prevents automatic clients from answering each other.
automaticAnswer.setMessageText("/CQ " + newMessageArrived.getSender().getCallSign()
+ " " + AUTOANSWER_PREFIX + " " + payload);
this.client.getMessageTXBus().add(automaticAnswer);
// Cooldown wird NUR hier gesetzt (nicht bei 'message sent by me' Echo),
// damit nur lokale Auto-Sends zählen.
// Record only locally generated replies, not the later server echo.
markLocalAutoAnswerSent(newMessageArrived);
}
}
@@ -1544,9 +1540,7 @@ public class MessageBusManagementThread extends Thread {
/**
* check if message had been auto generated
* @param msg
* @return
* Returns whether a message carries the fixed marker used for automatic replies.
*/
private boolean isAutoMessage(ChatMessage msg) {
return msg != null
@@ -1554,6 +1548,22 @@ public class MessageBusManagementThread extends Thread {
&& msg.getMessageText().contains(AUTOANSWER_PREFIX);
}
/**
* Returns the configured QRG for the category in which the request was received.
* If the category cannot be resolved, the main category remains the safe fallback.
*/
private String getAutoAnswerQrgForCategory(ChatCategory incomingCategory) {
ChatCategory secondCategory = this.client.getChatCategorySecondChat();
if (incomingCategory != null
&& secondCategory != null
&& incomingCategory.getCategoryNumber() == secondCategory.getCategoryNumber()) {
return this.client.getChatPreferences().getMYQRGSecondCat().getValue();
}
return this.client.getChatPreferences().getMYQRGFirstCat().getValue();
}
private String autoAnswerCooldownKey(ChatMessage incoming) {
String remoteCall = "UNKNOWN";
@@ -1561,13 +1571,16 @@ public class MessageBusManagementThread extends Thread {
remoteCall = incoming.getSender().getCallSign().toUpperCase();
}
int cat = 0; // fallback
if (incoming != null && incoming.getSender() != null && incoming.getSender().getChatCategory() != null) {
cat = incoming.getSender().getChatCategory().getCategoryNumber();
int categoryNumber = 0;
if (incoming != null && incoming.getChatCategory() != null) {
categoryNumber = incoming.getChatCategory().getCategoryNumber();
} else if (incoming != null
&& incoming.getSender() != null
&& incoming.getSender().getChatCategory() != null) {
categoryNumber = incoming.getSender().getChatCategory().getCategoryNumber();
}
// pro Gegenstation + pro Chat-Kategorie (falls derselbe Call in Cat2/Cat3 PMs macht)
return remoteCall + "|" + cat;
return remoteCall + "|" + categoryNumber;
}
private boolean isAutoAnswerAllowedNow(ChatMessage incoming) {
@@ -2692,6 +2692,14 @@ public class ChatPreferences {
messageHandling_autoAnswerEnabledSecondCat,
"messageHandling_autoAnswerEnabledSecondCat",
"autoAnswerEnabledSecondCat");
/*
* The user interface intentionally exposes one shared generic auto-answer
* setting for both chat categories. Keep the legacy second-category XML
* fields synchronized so existing configuration files remain compatible.
*/
messageHandling_autoAnswerTextSecondCat = messageHandling_autoAnswerTextMainCat;
messageHandling_autoAnswerEnabledSecondCat = messageHandling_autoAnswerEnabled;
}
@@ -10472,38 +10472,37 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
// CheckBox chkBxEnableTRXMsgbyUCX = new CheckBox();
grdPnlMessageHandlingBeacon.add(generateLabeledSeparator(100,
"Set the unworked penetrator Beacons (intervalled PM to unworked stations)"), 0, 0, 2, 1);
grdPnlMessageHandlingBeacon.add(generateLabeledSeparator(100,
"Automatic answering options)"), 0, 1, 2, 1);
"Automatic answering options"), 0, 0, 2, 1);
// Label lbl_unwkd_autoAnswerDescriptor = new Label("Auto-answer Text:");
// grdPnlMessageHandlingBeacon.add(lbl_unwkd_autoAnswerDescriptor,0,3);
CheckBox chkbx_msgHandlingAutoAnswerEnabled = new CheckBox("Auto-reply (to all queries): ");
chkbx_msgHandlingAutoAnswerEnabled.setTooltip(new Tooltip("KST4Contest will answer for you with your pre-definied text to all PMs"));
CheckBox chkbx_msgHandlingAutoAnswerEnabled = new CheckBox("Enable automatic reply to all private messages");
chkbx_msgHandlingAutoAnswerEnabled.setTooltip(new Tooltip(
"KST4Contest replies with the configured text in the chat category from which the private message was received.\n"
+ "The same station can receive one automatic reply per chat category every two minutes."));
chkbx_msgHandlingAutoAnswerEnabled.setSelected(this.chatcontroller.getChatPreferences().isMsgHandling_autoAnswerEnabled());
chkbx_msgHandlingAutoAnswerEnabled.selectedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
chatcontroller.getChatPreferences().setMessageHandling_autoAnswerEnabled(chkbx_msgHandlingAutoAnswerEnabled.isSelected());
chatcontroller.getChatPreferences().setMessageHandling_autoAnswerEnabledSecondCat(chkbx_msgHandlingAutoAnswerEnabled.isSelected());
System.out.println("[Main.java, Info]: Autoreply turned on: " + newValue);
}
});
CheckBox chkbx_messageHandlingAutoQRGInfoEnabled = new CheckBox("Enable auto-reply with my QRG on QRG-request");
String changeMeToPreferences = "";
changeMeToPreferences += ("ur qrg?\n");
changeMeToPreferences += ("your qrg?\n");
changeMeToPreferences += ("qrg?\n");
changeMeToPreferences += ("freq?\n");
changeMeToPreferences += ("pse QRG\n");
chkbx_messageHandlingAutoQRGInfoEnabled.setTooltip(new Tooltip("KST4Contest can answer with your QRG automatically. Following Strings causing reaction: \n " + changeMeToPreferences));
CheckBox chkbx_messageHandlingAutoQRGInfoEnabled = new CheckBox("Enable automatic QRG replies");
String qrgRequestExamples = "ur qrg?\n"
+ "your qrg?\n"
+ "qrg?\n"
+ "freq?\n"
+ "pse qrg";
chkbx_messageHandlingAutoQRGInfoEnabled.setTooltip(new Tooltip(
"KST4Contest replies with the QRG configured for the chat category in which the request was received.\n"
+ "Recognized text:\n" + qrgRequestExamples));
chkbx_messageHandlingAutoQRGInfoEnabled.setSelected(this.chatcontroller.getChatPreferences().isMessageHandling_autoAnswerToQRGRequestEnabled());
chkbx_messageHandlingAutoQRGInfoEnabled.selectedProperty().addListener(new ChangeListener<Boolean>() {
@Override
@@ -10515,22 +10514,22 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
});
TextField txtFld_messageHandlingAutoAnswer = new TextField();
txtFld_messageHandlingAutoAnswer.setPrefWidth(400);
txtFld_messageHandlingAutoAnswer.setText(this.chatcontroller.getChatPreferences().getMessageHandling_autoAnswerTextMainCat());
txtFld_messageHandlingAutoAnswer.textProperty().addListener(new ChangeListener<String>() {
@Override
public void changed(ObservableValue<? extends String> observed, String oldString, String newString) {
System.out.println("[Main.java, Info]: Setted the autoanswer: " + txtFld_messageHandlingAutoAnswer.getText().toUpperCase());
chatcontroller.getChatPreferences().setMessageHandling_autoAnswerTextMainCat(txtFld_messageHandlingAutoAnswer.getText().toUpperCase());
chatcontroller.getChatPreferences().setMessageHandling_autoAnswerTextSecondCat(txtFld_messageHandlingAutoAnswer.getText().toUpperCase());
System.out.println("[Main.java, Info]: Set the auto-answer text: " + newString);
chatcontroller.getChatPreferences().setMessageHandling_autoAnswerTextMainCat(newString);
chatcontroller.getChatPreferences().setMessageHandling_autoAnswerTextSecondCat(newString);
}
});
grdPnlMessageHandlingBeacon.add(chkbx_msgHandlingAutoAnswerEnabled, 0, 1);
grdPnlMessageHandlingBeacon.add(txtFld_messageHandlingAutoAnswer, 1, 1);
grdPnlMessageHandlingBeacon.add(txtFld_messageHandlingAutoAnswer,1,2);
grdPnlMessageHandlingBeacon.add(chkbx_msgHandlingAutoAnswerEnabled,0,2);
grdPnlMessageHandlingBeacon.add(chkbx_messageHandlingAutoQRGInfoEnabled,0,4, 2,1);
grdPnlMessageHandlingBeacon.add(chkbx_messageHandlingAutoQRGInfoEnabled, 0, 2, 2, 1);
VBox vbxMsgHandlBeacon = new VBox();
vbxMsgHandlBeacon.setPadding(new Insets(10, 10, 10, 10));