diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index bc2da395..704aa9fb 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,6 +1,7 @@ + diff --git a/api/ctjs.api b/api/ctjs.api index d8afafd8..e464fa72 100644 --- a/api/ctjs.api +++ b/api/ctjs.api @@ -1936,21 +1936,10 @@ public class com/chattriggers/ctjs/api/triggers/CancellableEvent { public final class com/chattriggers/ctjs/api/triggers/ChatTrigger : com/chattriggers/ctjs/api/triggers/Trigger { public fun (Ljava/lang/Object;Lcom/chattriggers/ctjs/api/triggers/ITriggerType;)V - public final fun addParameter (Ljava/lang/String;)Lcom/chattriggers/ctjs/api/triggers/ChatTrigger; - public final fun addParameters ([Ljava/lang/String;)Lcom/chattriggers/ctjs/api/triggers/ChatTrigger; - public final fun setCaseInsensitive ()Lcom/chattriggers/ctjs/api/triggers/ChatTrigger; - public final fun setChatCriteria (Ljava/lang/Object;)Lcom/chattriggers/ctjs/api/triggers/ChatTrigger; - public final fun setContains ()Lcom/chattriggers/ctjs/api/triggers/ChatTrigger; + public final fun contains ([Ljava/lang/Object;)Lcom/chattriggers/ctjs/api/triggers/ChatTrigger; + public final fun endsWith (Ljava/lang/Object;)Lcom/chattriggers/ctjs/api/triggers/ChatTrigger; public final fun setCriteria (Ljava/lang/Object;)Lcom/chattriggers/ctjs/api/triggers/ChatTrigger; - public final fun setEnd ()Lcom/chattriggers/ctjs/api/triggers/ChatTrigger; - public final fun setExact ()Lcom/chattriggers/ctjs/api/triggers/ChatTrigger; - public final fun setFormatted ()V - public final fun setFormatted (Z)V - public static synthetic fun setFormatted$default (Lcom/chattriggers/ctjs/api/triggers/ChatTrigger;ZILjava/lang/Object;)V - public final fun setParameter (Ljava/lang/String;)Lcom/chattriggers/ctjs/api/triggers/ChatTrigger; - public final fun setParameters ([Ljava/lang/String;)Lcom/chattriggers/ctjs/api/triggers/ChatTrigger; - public final fun setStart ()Lcom/chattriggers/ctjs/api/triggers/ChatTrigger; - public final fun triggerIfCanceled (Z)Lcom/chattriggers/ctjs/api/triggers/ChatTrigger; + public final fun startsWith (Ljava/lang/Object;)Lcom/chattriggers/ctjs/api/triggers/ChatTrigger; } public final class com/chattriggers/ctjs/api/triggers/ChatTrigger$Event : com/chattriggers/ctjs/api/triggers/CancellableEvent { diff --git a/src/main/kotlin/com/chattriggers/ctjs/api/triggers/ChatTrigger.kt b/src/main/kotlin/com/chattriggers/ctjs/api/triggers/ChatTrigger.kt index 7f284d08..6a8ae20a 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/api/triggers/ChatTrigger.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/api/triggers/ChatTrigger.kt @@ -4,175 +4,122 @@ import com.chattriggers.ctjs.api.message.TextComponent import org.mozilla.javascript.regexp.NativeRegExp class ChatTrigger(method: Any, type: ITriggerType) : Trigger(method, type) { - private lateinit var chatCriteria: Any private var formatted: Boolean = false - private var formattedForced = false - private var caseInsensitive: Boolean = false private lateinit var criteriaPattern: Regex - private val parameters = mutableListOf() - private var triggerIfCanceled: Boolean = true + private var usingCriteria = false + private var global = false - /** - * Sets if the chat trigger should run if the chat event has already been canceled. - * True by default. - * @param bool Boolean to set - * @return the trigger object for method chaining - */ - fun triggerIfCanceled(bool: Boolean) = apply { triggerIfCanceled = bool } + private var startsWith: Regex? = null + private val contains = mutableSetOf() + private var endsWith: Regex? = null /** - * Sets the chat criteria for [matchesChatCriteria]. - * Arguments for the trigger's method can be passed in using ${variable}. - * Example: `setChatCriteria("<${name}> ${message}");` - * Use ${*} to match a chat message but ignore the pass through. - * @param chatCriteria the chat criteria to set - * @return the trigger object for method chaining + * Creates a regex string and flags according to the criteria */ - fun setChatCriteria(chatCriteria: Any) = apply { - this.chatCriteria = chatCriteria - val flags = mutableSetOf() + private fun createCriteria(criteria: Any): Pair> { var source = ".+" + val flags = mutableSetOf() - when (chatCriteria) { + when (criteria) { is CharSequence -> { - if (!formattedForced) - formatted = Regex("[&\u00a7]") in chatCriteria + val chatCriteria = criteria.toString() - val replacedCriteria = Regex.escape(chatCriteria.toString().replace("\n", "->newLine<-")) - .replace(Regex("\\\$\\{[^*]+?}"), "\\\\E(.+)\\\\Q") - .replace(Regex("\\$\\{\\*?}"), "\\\\E(?:.+)\\\\Q") + if ("\n" in chatCriteria) + flags.add(RegexOption.DOT_MATCHES_ALL) - if (caseInsensitive) - flags.add(RegexOption.IGNORE_CASE) + val replacedCriteria = chatCriteria.replace("\n", "\\n") + .replace(Regex("""\$\{[^*]+?}"""), "(.+?)") + .replace(Regex("""\$\{\*?}"""), "(?:.+?)") if ("" != chatCriteria) source = replacedCriteria } is NativeRegExp -> { - if (chatCriteria["ignoreCase"] as Boolean || caseInsensitive) + if (criteria["ignoreCase"] as Boolean) flags.add(RegexOption.IGNORE_CASE) - if (chatCriteria["multiline"] as Boolean) + if (criteria["multiline"] as Boolean) flags.add(RegexOption.MULTILINE) - source = (chatCriteria["source"] as String).let { + if (criteria["dotAll"] as Boolean) + flags.add(RegexOption.DOT_MATCHES_ALL) + + source = (criteria["source"] as String).let { if ("" == it) ".+" else it } - - if (!formattedForced) - formatted = Regex("[&\u00a7]") in source } else -> throw IllegalArgumentException("Expected String or Regexp Object") } - criteriaPattern = Regex(source, flags) + formatted = formatted or (Regex("[&\u00a7]") in source) + return source to flags } /** - * Alias for [setChatCriteria]. - * @param chatCriteria the chat criteria to set + * Sets the chat criteria for this trigger. + * Arguments for the trigger's method can be passed in using ${variable}. + * Example: `setCriteria("<${name}> ${message}");` + * Use ${*} to match a chat message but ignore the pass through. + * @param criteria the chat criteria to set * @return the trigger object for method chaining */ - fun setCriteria(chatCriteria: Any) = setChatCriteria(chatCriteria) + fun setCriteria(criteria: Any) = apply { + check(startsWith == null && contains.isEmpty() && endsWith == null) { + "Can not use setCriteria() with any of startsWith(), contains(), or endsWith()" + } - /** - * Sets the chat parameter for [Parameter]. - * Clears current parameter list. - * @param parameter the chat parameter to set - * @return the trigger object for method chaining - */ - fun setParameter(parameter: String) = apply { - parameters.clear() - addParameter(parameter) - } + usingCriteria = true + val (source, flags) = createCriteria(criteria) - /** - * Sets multiple chat parameters for [Parameter]. - * Clears current parameter list. - * @param parameters the chat parameters to set - * @return the trigger object for method chaining - */ - fun setParameters(vararg parameters: String) = apply { - this.parameters.clear() - addParameters(*parameters) - } + if (criteria is NativeRegExp && criteria["global"] as Boolean) + global = true - /** - * Adds chat parameter for [Parameter]. - * @param parameter the chat parameter to add - * @return the trigger object for method chaining - */ - fun addParameter(parameter: String) = apply { - parameters.add(Parameter.getParameterByName(parameter)) + criteriaPattern = Regex("^${source}\$", flags) } /** - * Adds multiple chat parameters for [Parameter]. - * @param parameters the chat parameters to add + * Sets the starting criteria for this trigger. In order for this trigger to run, the beginning of + * the chat message must match [criteria]. + * Like [setCriteria], arguments can be passed in using ${variable}. * @return the trigger object for method chaining */ - fun addParameters(vararg parameters: String) = apply { - parameters.forEach(::addParameter) - } + fun startsWith(criteria: Any) = apply { + check(!usingCriteria) { "Can not use both setCriteria() and startsWith()" } - /** - * Adds the "start" parameter - * @return the trigger object for method chaining - */ - fun setStart() = apply { - setParameter("start") + val (source, flags) = createCriteria(criteria) + startsWith = Regex("^${source}", flags) } /** - * Adds the "contains" parameter + * Sets criteria this trigger must contain. In order for this trigger to run, the beginning of + * the chat message must contain **all** [criteria]. + * Like [setCriteria], arguments can be passed in using ${variable}. * @return the trigger object for method chaining */ - fun setContains() = apply { - setParameter("contains") - } + fun contains(vararg criteria: Any) = apply { + check(!usingCriteria) { "Can not use both setCriteria() and contains()" } - /** - * Adds the "end" parameter - * @return the trigger object for method chaining - */ - fun setEnd() = apply { - setParameter("end") - } - - /** - * Forces this trigger to be formatted or unformatted. If no argument is - * provided, it will be set to formatted. This method overrides the - * behavior of inferring the formatted status from the criteria. - */ - @JvmOverloads - fun setFormatted(formatted: Boolean = true) { - this.formatted = formatted - this.formattedForced = true - } - - /** - * Makes the trigger match the entire chat message - * @return the trigger object for method chaining - */ - fun setExact() = apply { - parameters.clear() + for (criterion in criteria) { + val (source, flags) = createCriteria(criterion) + contains += Regex(source, flags) + } } /** - * Makes the chat criteria case insensitive + * Sets the ending criteria for this trigger. In order for this trigger to run, the end of + * the chat message must match [criteria]. + * Like [setCriteria], arguments can be passed in using ${variable}. * @return the trigger object for method chaining */ - fun setCaseInsensitive() = apply { - caseInsensitive = true + fun endsWith(criteria: Any) = apply { + check(!usingCriteria) { "Can not use both setCriteria() and endsWith()" } - // Reparse criteria if setCriteria has already been called - if (::chatCriteria.isInitialized) - setCriteria(chatCriteria) + val (source, flags) = createCriteria(criteria) + endsWith = Regex("${source}\$", flags) } /** - * Argument 1 (String) The chat message received - * Argument 2 (ClientChatReceivedEvent) the chat event fired + * Argument 0 (Event) The chat message event * @param args list of arguments as described */ override fun trigger(args: Array) { @@ -181,15 +128,12 @@ class ChatTrigger(method: Any, type: ITriggerType) : Trigger(method, type) { } val chatEvent = args[0] as Event - - if (!triggerIfCanceled && chatEvent.isCancelled()) return + if (chatEvent.isCancelled()) return val chatMessage = getChatMessage(chatEvent.message) val variables = getVariables(chatMessage) ?: return - variables.add(chatEvent) - - callMethod(variables.toTypedArray()) + callMethod((variables + chatEvent).toTypedArray()) } // helper method to get the proper chat message based on the presence of color codes @@ -198,12 +142,6 @@ class ChatTrigger(method: Any, type: ITriggerType) : Trigger(method, type) { chatMessage.formattedText.replace("\u00a7", "&") else chatMessage.unformattedText - // helper method to get the variables to pass through - private fun getVariables(chatMessage: String) = - if (::criteriaPattern.isInitialized) - matchesChatCriteria(chatMessage.replace("\n", "->newLine<-")) - else ArrayList() - /** * A method to check whether a received chat message * matches this trigger's definition criteria. @@ -211,51 +149,52 @@ class ChatTrigger(method: Any, type: ITriggerType) : Trigger(method, type) { * @param chat the chat message to compare against * @return a list of the variables, in order or null if it doesn't match */ - private fun matchesChatCriteria(chat: String): MutableList? { - val regex = criteriaPattern - - if (parameters.isEmpty()) { - if (!(regex matches chat)) return null - } else { - parameters.forEach { parameter -> - val first = try { - regex.find(chat)?.groups?.get(0) - } catch (e: IndexOutOfBoundsException) { - return null - } + private fun getVariables(chat: String): List? { + if (usingCriteria && ::criteriaPattern.isInitialized) { + if (global) + return criteriaPattern.findAll(chat).flatMap { + it.groupValues.drop(1) + }.toList().ifEmpty { null } - when (parameter) { - Parameter.CONTAINS -> if (first == null) return null - Parameter.START -> if (first == null || first.range.first != 0) return null - Parameter.END -> if (first?.range?.last != chat.length) return null - null -> if (!(regex matches chat)) return null - } - } + return criteriaPattern.find(chat)?.groupValues?.drop(1) } - return regex.find(chat)?.groupValues?.drop(1)?.toMutableList() - } + val variables = mutableListOf() + var start = 0 + var end = chat.length - /** - * The parameter to match chat criteria to. - * Location parameters - * - contains - * - start - * - end - */ - private enum class Parameter(vararg names: String) { - CONTAINS("", "", "c", "contains"), - START("", "", "s", "start"), - END("", "", "e", "end"); + if (startsWith != null) { + val matcher = startsWith!!.find(chat) ?: return null + start = matcher.range.last + 1 + matcher.groupValues.drop(1).let { variables += it } + } - var names: List = names.asList() + if (endsWith != null) { + var subStart = chat.length - 1 + var matcher: MatchResult? = null - companion object { - fun getParameterByName(name: String) = - entries.find { param -> - param.names.any { it.lowercase() == name } + while (subStart >= 0) { + matcher = endsWith!!.find(chat.substring(subStart)) + if (matcher == null) { + subStart-- + } else { + break } + } + + if (matcher == null) return null + + end = subStart + matcher.groupValues.drop(1).let { variables += it } } + + for (contain in contains) { + contain.find(chat.substring(start, end))?.groupValues?.drop(1)?.let { + variables += it + } ?: return null + } + + return variables } class Event(@JvmField val message: TextComponent) : CancellableEvent() diff --git a/src/main/kotlin/com/chattriggers/ctjs/engine/Register.kt b/src/main/kotlin/com/chattriggers/ctjs/engine/Register.kt index 85f1c517..a6896eb0 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/engine/Register.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/engine/Register.kt @@ -56,9 +56,10 @@ object Register { * - The chat event, which can be cancelled * * Available modifications: - * - [ChatTrigger.triggerIfCanceled] Sets if triggered if event is already cancelled - * - [ChatTrigger.setChatCriteria] Sets the chat criteria - * - [ChatTrigger.setParameter] Sets the chat parameter + * - [ChatTrigger.setCriteria] Sets the exact chat criteria + * - [ChatTrigger.startsWith] Sets the starting criteria + * - [ChatTrigger.contains] Sets the containing criteria + * - [ChatTrigger.endsWith] Sets the ending criteria * - [Trigger.setPriority] Sets the priority * * @param method The method to call when the event is fired @@ -77,9 +78,10 @@ object Register { * - The chat event, which can be cancelled * * Available modifications: - * - [ChatTrigger.triggerIfCanceled] Sets if triggered if event is already cancelled - * - [ChatTrigger.setChatCriteria] Sets the chat criteria - * - [ChatTrigger.setParameter] Sets the chat parameter + * - [ChatTrigger.setCriteria] Sets the exact chat criteria + * - [ChatTrigger.startsWith] Sets the starting criteria + * - [ChatTrigger.contains] Sets the containing criteria + * - [ChatTrigger.endsWith] Sets the ending criteria * - [Trigger.setPriority] Sets the priority * * @param method The method to call when the event is fired