From 201852deef27008e48535f50d15fa9907f48f5fb Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Sun, 11 Jun 2017 14:07:38 +0100 Subject: [PATCH 01/36] Next development version. --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 4390ee82..ecc70850 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ allprojects { - version = '0.8.1' + version = '0.8.2-SNAPSHOT' group = 'com.gatehill.corebot' ext.mavenSnapshotRepository = 's3://gatehillsoftware-maven/snapshots' From c7da59cab8e174a83d7d433c742d4bb3b68eb683 Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Mon, 15 May 2017 00:01:26 +0100 Subject: [PATCH 02/36] Adds bot to allow simple borrow/return of items. Also includes evict and status actions for items. --- backends/items/build.gradle | 33 +++++ .../corebot/driver/items/ItemsDriverModule.kt | 11 ++ .../items/action/ItemsActionDriverImpl.kt | 35 +++++ .../items/action/model/ItemsActionType.kt | 13 ++ .../chat/model/template/BaseItemTemplate.kt | 32 +++++ .../chat/model/template/BorrowItemTemplate.kt | 22 +++ .../chat/model/template/EvictItemTemplate.kt | 17 +++ .../chat/model/template/ReturnItemTemplate.kt | 17 +++ .../chat/model/template/StatusAllTemplate.kt | 39 ++++++ .../chat/model/template/StatusItemTemplate.kt | 17 +++ .../driver/items/service/ClaimService.kt | 126 ++++++++++++++++++ bots/slack-items/build.gradle | 42 ++++++ .../kotlin/com/gatehill/corebot/Bootstrap.kt | 27 ++++ .../main/kotlin/com/gatehill/corebot/Main.kt | 20 +++ .../src/main/resources/default-security.yml | 23 ++++ .../corebot/config/model/ActionConfig.kt | 30 ++--- .../gatehill/corebot/chat/SessionService.kt | 1 + .../gatehill/corebot/chat/TemplateService.kt | 7 +- examples/config/ownable-items.yml | 10 ++ .../corebot/chat/SlackChatServiceImpl.kt | 5 +- .../corebot/chat/SlackSessionServiceImpl.kt | 3 + settings.gradle | 2 + 22 files changed, 509 insertions(+), 23 deletions(-) create mode 100644 backends/items/build.gradle create mode 100644 backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/ItemsDriverModule.kt create mode 100644 backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt create mode 100644 backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/model/ItemsActionType.kt create mode 100644 backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BaseItemTemplate.kt create mode 100644 backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt create mode 100644 backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictItemTemplate.kt create mode 100644 backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/ReturnItemTemplate.kt create mode 100644 backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusAllTemplate.kt create mode 100644 backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusItemTemplate.kt create mode 100644 backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt create mode 100644 bots/slack-items/build.gradle create mode 100644 bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt create mode 100644 bots/slack-items/src/main/kotlin/com/gatehill/corebot/Main.kt create mode 100644 bots/slack-items/src/main/resources/default-security.yml create mode 100644 examples/config/ownable-items.yml diff --git a/backends/items/build.gradle b/backends/items/build.gradle new file mode 100644 index 00000000..e348af3f --- /dev/null +++ b/backends/items/build.gradle @@ -0,0 +1,33 @@ +buildscript { + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$version_kotlin" + } +} + +apply plugin: "kotlin" + +dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib:$version_kotlin" + + compile project(':core:engine') + + testCompile "junit:junit:$version_junit" + testCompile "org.jetbrains.kotlin:kotlin-test-junit:$version_kotlin" +} + +publishing { + publications { + maven(MavenPublication) { + from components.java + repositories { + maven { + url mavenSnapshotRepository + credentials(AwsCredentials) { + accessKey awsAccessKey + secretKey awsSecretKey + } + } + } + } + } +} diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/ItemsDriverModule.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/ItemsDriverModule.kt new file mode 100644 index 00000000..cbf59af6 --- /dev/null +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/ItemsDriverModule.kt @@ -0,0 +1,11 @@ +package com.gatehill.corebot.driver.items + +import com.gatehill.corebot.asSingleton +import com.gatehill.corebot.driver.items.service.ClaimService +import com.google.inject.AbstractModule + +class ItemsDriverModule : AbstractModule() { + override fun configure() { + bind(ClaimService::class.java).asSingleton() + } +} diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt new file mode 100644 index 00000000..55396e52 --- /dev/null +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt @@ -0,0 +1,35 @@ +package com.gatehill.corebot.driver.items.action + +import com.gatehill.corebot.action.driver.ActionDriver +import com.gatehill.corebot.action.model.PerformActionResult +import com.gatehill.corebot.action.model.TriggerContext +import com.gatehill.corebot.chat.model.action.ActionType +import com.gatehill.corebot.config.model.ActionConfig +import com.gatehill.corebot.driver.items.action.model.ItemsActionType +import com.gatehill.corebot.driver.items.service.ClaimService +import java.util.concurrent.CompletableFuture +import javax.inject.Inject + +/** + * @author Pete Cornish {@literal } + */ +class ItemsActionDriverImpl @Inject constructor(private val claimService: ClaimService) : ActionDriver { + override fun perform(trigger: TriggerContext, actionType: ActionType, action: ActionConfig, args: Map): CompletableFuture { + val future = CompletableFuture() + try { + when (actionType) { + ItemsActionType.ITEM_BORROW -> claimService.claimItem(future, action, args, trigger.userId) + ItemsActionType.ITEM_RETURN -> claimService.releaseItem(future, action, trigger.userId) + ItemsActionType.ITEM_EVICT -> claimService.evictItemClaims(future, action, trigger.userId) + ItemsActionType.ITEM_STATUS -> claimService.checkItemStatus(future, action) + else -> { + throw UnsupportedOperationException( + "Action type $actionType is not supported by ${javaClass.canonicalName}") + } + } + } catch(e: Exception) { + future.completeExceptionally(e) + } + return future + } +} diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/model/ItemsActionType.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/model/ItemsActionType.kt new file mode 100644 index 00000000..15c6597f --- /dev/null +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/model/ItemsActionType.kt @@ -0,0 +1,13 @@ +package com.gatehill.corebot.driver.items.action.model + +import com.gatehill.corebot.chat.model.action.CoreActionType + +class ItemsActionType(name: String, description: String) : CoreActionType(name, description) { + companion object { + val ITEM_BORROW = ItemsActionType("ITEM_BORROW", "Borrow an item") + val ITEM_RETURN = ItemsActionType("ITEM_RETURN", "Return an item") + val ITEM_EVICT = ItemsActionType("ITEM_EVICT", "Evict all borrowers from an item") + val ITEM_STATUS = ItemsActionType("ITEM_STATUS", "Check status of an item") + val ALL_STATUS = ItemsActionType("ALL_STATUS", "Check status of all items") + } +} diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BaseItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BaseItemTemplate.kt new file mode 100644 index 00000000..0412a014 --- /dev/null +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BaseItemTemplate.kt @@ -0,0 +1,32 @@ +package com.gatehill.corebot.driver.items.chat.model.template + +import com.gatehill.corebot.chat.model.template.ActionMessageMode +import com.gatehill.corebot.chat.model.template.CustomActionTemplate +import com.gatehill.corebot.config.ConfigService +import com.gatehill.corebot.config.model.ActionConfig +import javax.inject.Inject + +/** + * Common item functionality. + */ +abstract class BaseItemTemplate @Inject constructor(private val configService: ConfigService) : CustomActionTemplate() { + override val builtIn = false + override val showInUsage = true + override val actionMessageMode = ActionMessageMode.INDIVIDUAL + override val actionConfigs = mutableListOf() + + protected val itemName: String + get() = placeholderValues[itemPlaceholder]!! + + override fun onTemplateSatisfied(): Boolean { + actionConfigs += configService.actions() + .filterKeys { it.equals(itemName, ignoreCase = true) } + .values + + return actionConfigs.isNotEmpty() + } + + companion object { + val itemPlaceholder = "item name" + } +} diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt new file mode 100644 index 00000000..e02db23c --- /dev/null +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt @@ -0,0 +1,22 @@ +package com.gatehill.corebot.driver.items.chat.model.template + +import com.gatehill.corebot.chat.model.action.ActionType +import com.gatehill.corebot.config.ConfigService +import com.gatehill.corebot.config.model.ActionConfig +import com.gatehill.corebot.driver.items.action.model.ItemsActionType +import java.util.* +import javax.inject.Inject + +/** + * Borrow an item. + */ +class BorrowItemTemplate @Inject constructor(configService: ConfigService) : BaseItemTemplate(configService) { + override val actionType: ActionType = ItemsActionType.ITEM_BORROW + override val tokens = LinkedList(listOf("borrow", "{$itemPlaceholder}", "for", "{$reasonPlaceholder}")) + + override fun buildStartMessage(options: Map, actionConfig: ActionConfig?) = "" + + companion object { + val reasonPlaceholder = "reason" + } +} diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictItemTemplate.kt new file mode 100644 index 00000000..e8c0e9c9 --- /dev/null +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictItemTemplate.kt @@ -0,0 +1,17 @@ +package com.gatehill.corebot.driver.items.chat.model.template + +import com.gatehill.corebot.chat.model.action.ActionType +import com.gatehill.corebot.config.ConfigService +import com.gatehill.corebot.config.model.ActionConfig +import com.gatehill.corebot.driver.items.action.model.ItemsActionType +import java.util.* +import javax.inject.Inject + +/** + * Evict all borrowers from an item. + */ +class EvictItemTemplate @Inject constructor(configService: ConfigService) : BaseItemTemplate(configService) { + override val actionType: ActionType = ItemsActionType.ITEM_EVICT + override val tokens = LinkedList(listOf("evict", "{$itemPlaceholder}")) + override fun buildStartMessage(options: Map, actionConfig: ActionConfig?) = "" +} diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/ReturnItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/ReturnItemTemplate.kt new file mode 100644 index 00000000..44e8818a --- /dev/null +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/ReturnItemTemplate.kt @@ -0,0 +1,17 @@ +package com.gatehill.corebot.driver.items.chat.model.template + +import com.gatehill.corebot.chat.model.action.ActionType +import com.gatehill.corebot.config.ConfigService +import com.gatehill.corebot.config.model.ActionConfig +import com.gatehill.corebot.driver.items.action.model.ItemsActionType +import java.util.* +import javax.inject.Inject + +/** + * Return a borrowed item. + */ +class ReturnItemTemplate @Inject constructor(configService: ConfigService) : BaseItemTemplate(configService) { + override val actionType: ActionType = ItemsActionType.ITEM_RETURN + override val tokens = LinkedList(listOf("return", "{$itemPlaceholder}")) + override fun buildStartMessage(options: Map, actionConfig: ActionConfig?) = "" +} diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusAllTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusAllTemplate.kt new file mode 100644 index 00000000..c25c520b --- /dev/null +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusAllTemplate.kt @@ -0,0 +1,39 @@ +package com.gatehill.corebot.driver.items.chat.model.template + +import com.gatehill.corebot.chat.model.action.ActionType +import com.gatehill.corebot.chat.model.template.ActionMessageMode +import com.gatehill.corebot.chat.model.template.SystemActionTemplate +import com.gatehill.corebot.config.ConfigService +import com.gatehill.corebot.config.model.ActionConfig +import com.gatehill.corebot.driver.items.action.model.ItemsActionType +import com.gatehill.corebot.driver.items.service.ClaimService +import java.util.* +import javax.inject.Inject + +/** + * Show status and claims for all items. + */ +class StatusAllTemplate @Inject constructor(val configService: ConfigService, + val claimService: ClaimService) : SystemActionTemplate() { + + override val builtIn = false + override val showInUsage = true + override val actionMessageMode = ActionMessageMode.INDIVIDUAL + override val actionType: ActionType = ItemsActionType.ALL_STATUS + override val tokens = LinkedList(listOf("status")) + + override fun buildStartMessage(options: Map, actionConfig: ActionConfig?): String { + val status = StringBuilder() + + configService.actions().keys.forEach { itemName -> + claimService.describeItem(itemName) { + if (status.isNotEmpty()) { + status.append("\n") + } + status.append(it) + } + } + + return "Here's the latest:\n$status" + } +} diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusItemTemplate.kt new file mode 100644 index 00000000..346a737a --- /dev/null +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusItemTemplate.kt @@ -0,0 +1,17 @@ +package com.gatehill.corebot.driver.items.chat.model.template + +import com.gatehill.corebot.chat.model.action.ActionType +import com.gatehill.corebot.config.ConfigService +import com.gatehill.corebot.config.model.ActionConfig +import com.gatehill.corebot.driver.items.action.model.ItemsActionType +import java.util.* +import javax.inject.Inject + +/** + * Show status and claims for an item. + */ +class StatusItemTemplate @Inject constructor(configService: ConfigService) : BaseItemTemplate(configService) { + override val actionType: ActionType = ItemsActionType.ITEM_STATUS + override val tokens = LinkedList(listOf("status", "{$itemPlaceholder}")) + override fun buildStartMessage(options: Map, actionConfig: ActionConfig?) = "" +} diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt new file mode 100644 index 00000000..88a20c06 --- /dev/null +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt @@ -0,0 +1,126 @@ +package com.gatehill.corebot.driver.items.service + +import com.gatehill.corebot.action.model.PerformActionResult +import com.gatehill.corebot.chat.SessionService +import com.gatehill.corebot.config.model.ActionConfig +import com.gatehill.corebot.driver.items.chat.model.template.BorrowItemTemplate +import java.util.concurrent.CompletableFuture +import javax.inject.Inject + +/** + * Allows an item to be locked or unlocked by a user. + * + * @author Pete Cornish {@literal } + */ +class ClaimService @Inject constructor(private val sessionService: SessionService) { + /** + * A claim held on an item. + */ + data class ItemClaim(val owner: String, val reason: String) + + private val itemClaims = mutableMapOf>() + + fun claimItem(future: CompletableFuture, action: ActionConfig, args: Map, + triggerMessageSenderId: String) { + + val itemName = action.name + synchronized(itemName) { + checkItemClaims(action) { claims -> + val reason = args[BorrowItemTemplate.reasonPlaceholder]!! + + itemClaims[itemName] = claims.toMutableList().apply { + + // any existing claim will be replaced + claims.firstOrNull { it.owner == triggerMessageSenderId }?.let { existing -> + remove(existing) + } + + add(ItemClaim(triggerMessageSenderId, reason)) + } + + future.complete(PerformActionResult("You've borrowed :lock: *$itemName* for _${reason}_.")) + } + } + } + + fun releaseItem(future: CompletableFuture, action: ActionConfig, + triggerMessageSenderId: String) { + + val itemName = action.name + synchronized(itemName) { + checkItemClaims(action) { claims -> + + // check for existing claim for current user + claims.firstOrNull { it.owner == triggerMessageSenderId }?.let { claim -> + itemClaims[itemName] = claims.toMutableList().apply { + remove(claim) + } + + future.complete(PerformActionResult("You've returned *$itemName*.")) + + } ?: run { + future.complete(PerformActionResult("BTW, you weren't borrowing *$itemName* :wink:")) + } + } + } + } + + fun evictItemClaims(future: CompletableFuture, action: ActionConfig, + triggerMessageSenderId: String) { + + val itemName = action.name + synchronized(itemName) { + checkItemClaims(action) { claims -> + itemClaims[itemName] = emptyList() + + val nonSelfBorrowers = claims + .map{ it.owner } + .filter { it != triggerMessageSenderId } + + val previousBorrowers = if (nonSelfBorrowers.isEmpty()) { + "" + } else { + "\n_(FYI ${nonSelfBorrowers.map { "<@$it>" }.joinToString()})_" + } + + val message = "I've :unlock: evicted all borrowers (${claims.size}) from *$itemName*.$previousBorrowers" + future.complete(PerformActionResult(message)) + } + } + } + + fun checkItemClaims(itemName: String, callback: (List) -> Unit) = + callback(itemClaims[itemName] ?: emptyList()) + + fun checkItemClaims(action: ActionConfig, callback: (List) -> Unit) = + checkItemClaims(action.name, callback) + + fun checkItemStatus(future: CompletableFuture, action: ActionConfig) { + describeItem(action.name) { + future.complete(PerformActionResult(it)) + } + } + + fun describeItem(itemName: String, callback: (String) -> Unit) { + checkItemClaims(itemName) { claims -> + val message = when { + claims.isEmpty() -> "No one is borrowing *$itemName*." + claims.size == 1 -> { + val claim = claims.first() + val ownerUsername = sessionService.lookupUser(claim.owner) + "There is a single borrower of *$itemName*: $ownerUsername - ${claim.reason}" + } + else -> { + val claimsList = StringBuilder() + claims.forEach { (owner, reason) -> + val ownerUsername = sessionService.lookupUser(owner) + claimsList.append("\n• $ownerUsername - $reason") + } + "There are ${claims.size} borrowers of *$itemName*:$claimsList" + } + } + + callback(message) + } + } +} diff --git a/bots/slack-items/build.gradle b/bots/slack-items/build.gradle new file mode 100644 index 00000000..d512f6a0 --- /dev/null +++ b/bots/slack-items/build.gradle @@ -0,0 +1,42 @@ +buildscript { + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$version_kotlin" + } +} + +apply plugin: "kotlin" +apply plugin: "application" + +dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib:$version_kotlin" + + compile project(':bots:common') + compile project(':backends:items') + compile project(':frontends:slack') + + testCompile "junit:junit:$version_junit" + testCompile "org.jetbrains.kotlin:kotlin-test-junit:$version_kotlin" +} + +mainClassName = 'com.gatehill.corebot.MainKt' + +task createStartScripts(type: CreateStartScripts) { + applicationName = 'corebot' +} + +publishing { + publications { + maven(MavenPublication) { + from components.java + repositories { + maven { + url mavenSnapshotRepository + credentials(AwsCredentials) { + accessKey awsAccessKey + secretKey awsSecretKey + } + } + } + } + } +} diff --git a/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt b/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt new file mode 100644 index 00000000..64da5a55 --- /dev/null +++ b/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt @@ -0,0 +1,27 @@ +package com.gatehill.corebot + +import com.gatehill.corebot.action.driver.ActionDriverFactory +import com.gatehill.corebot.chat.TemplateService +import com.gatehill.corebot.chat.model.template.ShowHelpTemplate +import com.gatehill.corebot.driver.items.action.ItemsActionDriverImpl +import com.gatehill.corebot.driver.items.chat.model.template.* +import javax.inject.Inject + +/** + * @author Pete Cornish {@literal } + */ +class Bootstrap @Inject constructor(actionDriverFactory: ActionDriverFactory, + templateService: TemplateService) { + init { + // drivers + actionDriverFactory.registerDriver("items", ItemsActionDriverImpl::class.java) + + // templates + templateService.registerTemplate(ShowHelpTemplate::class.java) + templateService.registerTemplate(BorrowItemTemplate::class.java) + templateService.registerTemplate(ReturnItemTemplate::class.java) + templateService.registerTemplate(EvictItemTemplate::class.java) + templateService.registerTemplate(StatusItemTemplate::class.java) + templateService.registerTemplate(StatusAllTemplate::class.java) + } +} diff --git a/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Main.kt b/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Main.kt new file mode 100644 index 00000000..a1559038 --- /dev/null +++ b/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Main.kt @@ -0,0 +1,20 @@ +package com.gatehill.corebot + +import com.gatehill.corebot.chat.ActionTemplateConverter +import com.gatehill.corebot.chat.NoOpActionTemplateConverter +import com.gatehill.corebot.driver.items.ItemsDriverModule +import com.google.inject.AbstractModule + +fun main(args: Array) { + Bot.build(ItemsBotModule(), SlackModule()).start() +} + +private class ItemsBotModule : AbstractModule() { + override fun configure() { + bind(Bootstrap::class.java).asEagerSingleton() + bind(ActionTemplateConverter::class.java).to(NoOpActionTemplateConverter::class.java).asSingleton() + + // drivers + install(ItemsDriverModule()) + } +} diff --git a/bots/slack-items/src/main/resources/default-security.yml b/bots/slack-items/src/main/resources/default-security.yml new file mode 100644 index 00000000..779040e3 --- /dev/null +++ b/bots/slack-items/src/main/resources/default-security.yml @@ -0,0 +1,23 @@ +# Default authz rules. +--- +version: '1' + +security: + roles: + # With great power, comes great responsibility. + admin: + permissions: + - help + - item_borrow + - item_return + - item_evict + - item_status + - all_status + tags: + - all + + users: + # Permits everyone to do everything. + "*": + roles: + - admin diff --git a/core/api/src/main/kotlin/com/gatehill/corebot/config/model/ActionConfig.kt b/core/api/src/main/kotlin/com/gatehill/corebot/config/model/ActionConfig.kt index 4550b378..2b07ae69 100644 --- a/core/api/src/main/kotlin/com/gatehill/corebot/config/model/ActionConfig.kt +++ b/core/api/src/main/kotlin/com/gatehill/corebot/config/model/ActionConfig.kt @@ -7,8 +7,8 @@ private val defaultDriver = "rundeck" * * @author Pete Cornish {@literal } */ -class ActionConfig(val template: String, - val jobId: String, +class ActionConfig(template: String?, + jobId: String?, name: String?, options: Map?, tags: List?, @@ -17,23 +17,15 @@ class ActionConfig(val template: String, showJobOutcome: Boolean?, runAsTriggerUser: Boolean?) { - val name: String - val options: Map - val tags: List - val driver: String - val showJobOutput: Boolean - val showJobOutcome: Boolean - val runAsTriggerUser: Boolean - - init { - this.name = name ?: "" - this.options = options ?: emptyMap() - this.tags = tags ?: emptyList() - this.driver = driver ?: defaultDriver - this.showJobOutput = showJobOutput ?: false - this.showJobOutcome = showJobOutcome ?: true - this.runAsTriggerUser = runAsTriggerUser ?: false - } + val template: String = template ?: "" + val jobId: String = jobId ?: "" + val name: String = name ?: "" + val options: Map = options ?: emptyMap() + val tags: List = tags ?: emptyList() + val driver: String = driver ?: defaultDriver + val showJobOutput: Boolean = showJobOutput ?: false + val showJobOutcome: Boolean = showJobOutcome ?: true + val runAsTriggerUser: Boolean = runAsTriggerUser ?: false override fun toString(): String { return "ActionConfig(name='$name', jobId='$jobId', options=$options, template='$template', tags=$tags, " + diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/SessionService.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/SessionService.kt index 149f6b6c..ad4b15bf 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/SessionService.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/SessionService.kt @@ -9,4 +9,5 @@ interface SessionService { val botUsername: String fun sendMessage(triggerContext: TriggerContext, message: String) fun addReaction(triggerContext: TriggerContext, emojiCode: String) + fun lookupUser(userId: String): String } diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt index a07ef126..14526e38 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt @@ -60,8 +60,11 @@ class TemplateService @Inject constructor(private val injector: Injector, usage.appendln(); usage.appendln() } - usage.append("*Built-in actions*") - candidates.filter(ActionTemplate::showInUsage).filter(ActionTemplate::builtIn).forEach(printTemplate) + val builtInActions = candidates.filter(ActionTemplate::showInUsage).filter(ActionTemplate::builtIn) + if (builtInActions.isNotEmpty()) { + usage.append("*Built-in actions*") + builtInActions.forEach(printTemplate) + } return usage } } diff --git a/examples/config/ownable-items.yml b/examples/config/ownable-items.yml new file mode 100644 index 00000000..7865fbc7 --- /dev/null +++ b/examples/config/ownable-items.yml @@ -0,0 +1,10 @@ +# Example for use with the ownable items driver. +--- +version: '1' + +actions: + sit: + driver: items + + prod: + driver: items diff --git a/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt b/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt index 2e2db4e2..4511a4ac 100644 --- a/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt +++ b/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt @@ -93,9 +93,10 @@ open class SlackChatServiceImpl @Inject constructor(private val sessionService: } // remove unsatisfied candidates - when (context.candidates.filter { candidate -> candidate.tokens.isEmpty() }.size) { + val satisfied = context.candidates.filter { candidate -> candidate.tokens.isEmpty() } + when (satisfied.size) { 1 -> { - val candidate = context.candidates[0] + val candidate = satisfied.first() return ActionWrapper(candidate.buildActions(), if (candidate.actionMessageMode == ActionMessageMode.GROUP) candidate.buildStartMessage() else null, if (candidate.actionMessageMode == ActionMessageMode.GROUP) candidate.buildCompleteMessage() else null) diff --git a/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackSessionServiceImpl.kt b/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackSessionServiceImpl.kt index 119cb6b2..97da7821 100644 --- a/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackSessionServiceImpl.kt +++ b/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackSessionServiceImpl.kt @@ -84,4 +84,7 @@ open class SlackSessionServiceImpl @Inject constructor(configService: ConfigServ override fun addReaction(triggerContext: TriggerContext, emojiCode: String) { session.addReactionToMessage(session.findChannelById(triggerContext.channelId), triggerContext.messageTimestamp, emojiCode) } + + override fun lookupUser(userId: String): String = + session.findUserById(userId).userName } diff --git a/settings.gradle b/settings.gradle index ee7ddf8d..1898fe97 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,8 +3,10 @@ rootProject.name = 'corebot' include ':backends:deployment:jobs' include ':backends:deployment:jenkins' include ':backends:deployment:rundeck' +include ':backends:items' include ':bots:common' include ':bots:slack-deploy' +include ':bots:slack-items' include ':core:api' include ':core:engine' include ':core:rest' From fc47b811394a251d8305f1226d70ddc5605debdb Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Mon, 22 May 2017 00:58:02 +0100 Subject: [PATCH 03/36] Build multiple Docker images. --- Dockerfile => bots/slack-deploy/Dockerfile | 2 +- bots/slack-items/Dockerfile | 11 ++++++ docker-compose.yml | 2 +- examples/compose/docker-compose.yml | 2 +- scripts/docker-build.sh | 46 ++++++++++++++++------ 5 files changed, 47 insertions(+), 16 deletions(-) rename Dockerfile => bots/slack-deploy/Dockerfile (74%) create mode 100644 bots/slack-items/Dockerfile diff --git a/Dockerfile b/bots/slack-deploy/Dockerfile similarity index 74% rename from Dockerfile rename to bots/slack-deploy/Dockerfile index b3358427..051de0d1 100644 --- a/Dockerfile +++ b/bots/slack-deploy/Dockerfile @@ -4,7 +4,7 @@ MAINTAINER Pete Cornish RUN mkdir -p /opt/corebot /opt/corebot/config -ADD bots/slack-deploy/build/install/slack-deploy /opt/corebot +ADD build/install/slack-deploy /opt/corebot WORKDIR "/opt/corebot" diff --git a/bots/slack-items/Dockerfile b/bots/slack-items/Dockerfile new file mode 100644 index 00000000..c107f006 --- /dev/null +++ b/bots/slack-items/Dockerfile @@ -0,0 +1,11 @@ +FROM openjdk:8-jdk + +MAINTAINER Pete Cornish + +RUN mkdir -p /opt/corebot /opt/corebot/config + +ADD build/install/slack-items /opt/corebot + +WORKDIR "/opt/corebot" + +ENTRYPOINT [ "./bin/slack-items" ] diff --git a/docker-compose.yml b/docker-compose.yml index 776cb10a..b44fad92 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,7 @@ version: '2' services: corebot: image: outofcoffee/corebot - build: . + build: ./bots/slack-deploy environment: SLACK_AUTH_TOKEN: "CHANGEME" SLACK_CHANNELS: "corebot" diff --git a/examples/compose/docker-compose.yml b/examples/compose/docker-compose.yml index adb908d5..bd323804 100644 --- a/examples/compose/docker-compose.yml +++ b/examples/compose/docker-compose.yml @@ -7,7 +7,7 @@ version: '2' services: corebot: image: outofcoffee/corebot - build: . + build: ./bots/slack-deploy environment: SLACK_AUTH_TOKEN: "CHANGEME" SLACK_CHANNELS: "general" diff --git a/scripts/docker-build.sh b/scripts/docker-build.sh index 6c97477a..28112d71 100755 --- a/scripts/docker-build.sh +++ b/scripts/docker-build.sh @@ -1,36 +1,56 @@ #!/usr/bin/env bash set -e +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +DOCKERFILE_PATH="${SCRIPT_DIR}/../bots/slack-" IMAGE_BASE_NAME="outofcoffee/corebot" IMAGE_TAG="${1-dev}" -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +IMAGES=( + "deploy" + "items" +) function buildImage() { - IMAGE_NAME="${IMAGE_BASE_NAME}:${IMAGE_TAG}" - echo -e "\nBuilding Docker image: ${IMAGE_NAME}" + IMAGE_SUFFIX="$2" + IMAGE_NAME="${IMAGE_BASE_NAME}${IMAGE_SUFFIX}:${IMAGE_TAG}" + echo -e "\nBuilding Docker image: ${IMAGE_NAME}" cd $1 docker build --tag ${IMAGE_NAME} . } - function pushImage() { + IMAGE_SUFFIX="$1" + IMAGE_NAME="${IMAGE_BASE_NAME}${IMAGE_SUFFIX}:${IMAGE_TAG}" + echo -e "\nLogging in to Docker registry..." docker login --username "${DOCKER_USERNAME}" --password "${DOCKER_PASSWORD}" --email deprecated@example.com - IMAGE_NAME="${IMAGE_BASE_NAME}:${IMAGE_TAG}" echo -e "\nPushing Docker image: ${IMAGE_NAME}" - docker push ${IMAGE_NAME} } -echo -e "\nBuilding image from local source" -buildImage "${SCRIPT_DIR}/../" +function buildPushImage() +{ + IMAGE_DIR="$1" + echo -e "\nBuilding '${IMAGE_DIR}' image" + + if [[ "deploy" == "${IMAGE_DIR}" ]]; then + IMAGE_SUFFIX="" + else + IMAGE_SUFFIX="-${IMAGE_DIR}" + fi + buildImage "${DOCKERFILE_PATH}${IMAGE_DIR}" ${IMAGE_SUFFIX} + + if [[ "dev" == "${IMAGE_TAG}" ]]; then + echo -e "\nSkipped pushing dev image" + else + pushImage ${IMAGE_SUFFIX} + fi +} -if [[ "dev" == "${IMAGE_TAG}" ]]; then - echo -e "\nSkipped pushing dev image" -else - pushImage -fi +for IMAGE_DIR in "${IMAGES[@]}"; do + buildPushImage ${IMAGE_DIR} +done From bca9fd8c957e27c6cc3375b9d9ec77096ef2cd17 Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Mon, 22 May 2017 01:13:10 +0100 Subject: [PATCH 04/36] Adds 'edge' deployment. --- circle.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/circle.yml b/circle.yml index 4abdf419..de264f9c 100644 --- a/circle.yml +++ b/circle.yml @@ -25,3 +25,9 @@ deployment: commands: - ./gradlew publish --stacktrace - /bin/bash ./scripts/docker-build.sh beta + + edge: + branch: feature/ownable-items + commands: + - ./gradlew publish --stacktrace + - /bin/bash ./scripts/docker-build.sh edge From acddcd8932fdbf92b677163a144455534a32e8e2 Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Sun, 28 May 2017 13:06:56 +0100 Subject: [PATCH 05/36] Adds Redis data store support to Items bot. --- .../driver/items/service/ClaimService.kt | 23 ++++++++++++------- bots/slack-items/build.gradle | 1 + .../main/kotlin/com/gatehill/corebot/Main.kt | 4 ++++ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt index 88a20c06..e6bea0db 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt @@ -4,21 +4,28 @@ import com.gatehill.corebot.action.model.PerformActionResult import com.gatehill.corebot.chat.SessionService import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.items.chat.model.template.BorrowItemTemplate +import com.gatehill.corebot.store.DataStore import java.util.concurrent.CompletableFuture import javax.inject.Inject +import javax.inject.Named /** * Allows an item to be locked or unlocked by a user. * * @author Pete Cornish {@literal } */ -class ClaimService @Inject constructor(private val sessionService: SessionService) { +class ClaimService @Inject constructor(private val sessionService: SessionService, + @Named("itemStore") private val dataStore: DataStore) { + /** * A claim held on an item. */ data class ItemClaim(val owner: String, val reason: String) - private val itemClaims = mutableMapOf>() + data class ItemClaims(val claims: List) + + private val itemClaims = + dataStore.partition("items", ItemClaims::class.java) fun claimItem(future: CompletableFuture, action: ActionConfig, args: Map, triggerMessageSenderId: String) { @@ -28,7 +35,7 @@ class ClaimService @Inject constructor(private val sessionService: SessionServic checkItemClaims(action) { claims -> val reason = args[BorrowItemTemplate.reasonPlaceholder]!! - itemClaims[itemName] = claims.toMutableList().apply { + itemClaims[itemName] = ItemClaims(claims.toMutableList().apply { // any existing claim will be replaced claims.firstOrNull { it.owner == triggerMessageSenderId }?.let { existing -> @@ -36,7 +43,7 @@ class ClaimService @Inject constructor(private val sessionService: SessionServic } add(ItemClaim(triggerMessageSenderId, reason)) - } + }) future.complete(PerformActionResult("You've borrowed :lock: *$itemName* for _${reason}_.")) } @@ -52,9 +59,9 @@ class ClaimService @Inject constructor(private val sessionService: SessionServic // check for existing claim for current user claims.firstOrNull { it.owner == triggerMessageSenderId }?.let { claim -> - itemClaims[itemName] = claims.toMutableList().apply { + itemClaims[itemName] = ItemClaims(claims.toMutableList().apply { remove(claim) - } + }) future.complete(PerformActionResult("You've returned *$itemName*.")) @@ -71,7 +78,7 @@ class ClaimService @Inject constructor(private val sessionService: SessionServic val itemName = action.name synchronized(itemName) { checkItemClaims(action) { claims -> - itemClaims[itemName] = emptyList() + itemClaims.remove(itemName) val nonSelfBorrowers = claims .map{ it.owner } @@ -90,7 +97,7 @@ class ClaimService @Inject constructor(private val sessionService: SessionServic } fun checkItemClaims(itemName: String, callback: (List) -> Unit) = - callback(itemClaims[itemName] ?: emptyList()) + callback(itemClaims[itemName]?.claims ?: emptyList()) fun checkItemClaims(action: ActionConfig, callback: (List) -> Unit) = checkItemClaims(action.name, callback) diff --git a/bots/slack-items/build.gradle b/bots/slack-items/build.gradle index d512f6a0..b341546f 100644 --- a/bots/slack-items/build.gradle +++ b/bots/slack-items/build.gradle @@ -12,6 +12,7 @@ dependencies { compile project(':bots:common') compile project(':backends:items') + compile project(':stores:redis') compile project(':frontends:slack') testCompile "junit:junit:$version_junit" diff --git a/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Main.kt b/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Main.kt index a1559038..623f741c 100644 --- a/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Main.kt +++ b/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Main.kt @@ -3,6 +3,7 @@ package com.gatehill.corebot import com.gatehill.corebot.chat.ActionTemplateConverter import com.gatehill.corebot.chat.NoOpActionTemplateConverter import com.gatehill.corebot.driver.items.ItemsDriverModule +import com.gatehill.corebot.store.DataStoreModule import com.google.inject.AbstractModule fun main(args: Array) { @@ -14,6 +15,9 @@ private class ItemsBotModule : AbstractModule() { bind(Bootstrap::class.java).asEagerSingleton() bind(ActionTemplateConverter::class.java).to(NoOpActionTemplateConverter::class.java).asSingleton() + install(DataStoreModule("lockStore")) + install(DataStoreModule("itemStore")) + // drivers install(ItemsDriverModule()) } From 99d49ef106e728ded69e7b26066a18307772d71f Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Tue, 30 May 2017 14:55:32 +0100 Subject: [PATCH 06/36] Moves template parsing logic into template service. Adds regex template support. --- .../chat/model/template/BaseItemTemplate.kt | 2 +- .../chat/model/template/BorrowItemTemplate.kt | 5 +- .../gatehill/corebot/chat/TemplateService.kt | 100 +++++++++++++----- .../chat/model/template/ActionTemplate.kt | 8 ++ .../chat/model/template/BaseActionTemplate.kt | 20 ++-- .../corebot/chat/SlackChatServiceImpl.kt | 65 +++++------- 6 files changed, 130 insertions(+), 70 deletions(-) diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BaseItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BaseItemTemplate.kt index 0412a014..cf1e01f6 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BaseItemTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BaseItemTemplate.kt @@ -27,6 +27,6 @@ abstract class BaseItemTemplate @Inject constructor(private val configService: C } companion object { - val itemPlaceholder = "item name" + val itemPlaceholder = "itemName" } } diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt index e02db23c..37d24fad 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt @@ -5,6 +5,7 @@ import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.items.action.model.ItemsActionType import java.util.* +import java.util.regex.Pattern import javax.inject.Inject /** @@ -12,7 +13,9 @@ import javax.inject.Inject */ class BorrowItemTemplate @Inject constructor(configService: ConfigService) : BaseItemTemplate(configService) { override val actionType: ActionType = ItemsActionType.ITEM_BORROW - override val tokens = LinkedList(listOf("borrow", "{$itemPlaceholder}", "for", "{$reasonPlaceholder}")) + override val tokens = LinkedList(listOf("{$itemPlaceholder}", "{$reasonPlaceholder}")) + override val templateRegex: Pattern? + get() = "borrow (?[a-zA-Z0-9]+) for (?.+)".toPattern() override fun buildStartMessage(options: Map, actionConfig: ActionConfig?) = "" diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt index 14526e38..459a050d 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt @@ -3,6 +3,8 @@ package com.gatehill.corebot.chat import com.gatehill.corebot.chat.model.template.ActionTemplate import com.gatehill.corebot.config.ConfigService import com.google.inject.Injector +import org.apache.logging.log4j.LogManager +import java.util.regex.Matcher import javax.inject.Inject /** @@ -12,10 +14,8 @@ class TemplateService @Inject constructor(private val injector: Injector, private val configService: ConfigService, private val sessionService: SessionService, private val actionTemplateConverter: ActionTemplateConverter) { - /** - * Holds candidate templates. - */ - data class TemplateContext(var candidates: MutableList) + + private val logger = LogManager.getLogger(TemplateService::class.java) /** * Unique set of templates. @@ -26,45 +26,97 @@ class TemplateService @Inject constructor(private val injector: Injector, actionTemplates.add(template) } - fun fetchCandidates(): TemplateContext = TemplateContext(mutableListOf().apply { + /** + * Find the templates that match the specified command. + */ + fun findSatisfiedTemplates(splitCmd: List): Collection { + val candidates = fetchCandidates() + + // skip element 0, which contains the bot's username + val commandOnly = splitCmd.subList(1, splitCmd.size) + + // include those satisfying templates + return mutableSetOf().apply { + addAll(filterSimpleTemplates(commandOnly, candidates.filter { null == it.templateRegex })) + addAll(filterRegexTemplates(commandOnly, candidates.filterNot { null == it.templateRegex })) + } + } + + /** + * Returns a new `Set` of candidates. + */ + private fun fetchCandidates(): Set = mutableSetOf().apply { addAll(actionTemplateConverter.convertConfigToTemplate(configService.actions().values)) addAll(actionTemplates.map({ actionTemplate -> injector.getInstance(actionTemplate) })) - }) + } - fun process(context: TemplateContext, token: String) { - // iterate over a copy to prevent concurrent modification issues - context.candidates.toList().forEach { candidate -> - if (!candidate.accept(token)) { - context.candidates.remove(candidate) + /** + * Filter candidates based on their template (or `tokens` property). + */ + private fun filterSimpleTemplates(commandOnly: List, candidates: Collection) = + candidates.filter { candidate -> + commandOnly.forEach { token -> + // push command elements into the candidate templates + if (!candidate.accept(token)) { + return@filter false + } + } + return@filter true + + }.filter { candidate -> + // include only satisfied candidates + candidate.tokens.isEmpty() } - } + + /** + * Filter candidates based on their `templateRegex` property. + */ + private fun filterRegexTemplates(commandOnly: List, candidates: Collection) = + candidates.map { it to it.templateRegex!!.matcher(commandOnly.joinToString(" ")) } + .filter { (_, matcher) -> matcher.matches() } + .map { (template, matcher) -> injectPlaceholderValues(template, matcher) } + .filter { it.onTemplateSatisfied() } + + /** + * Fetch the placeholder names from the template, then populate the placeholder values. + * Return the template. + */ + private fun injectPlaceholderValues(template: ActionTemplate, matcher: Matcher): ActionTemplate { + template.tokens.filter { "\\{(.*)}".toRegex().matches(it) } + .map { it.substring(1, it.length - 1) } + .map { it to matcher.group(it) }.toMap() + .let { placeholderValues -> + logger.trace("Placeholder values for ${template.actionType}: $placeholderValues") + template.placeholderValues += placeholderValues + } + + return template } - fun usage(): StringBuilder { - val usage = StringBuilder() - val candidates = fetchCandidates().candidates - candidates.sortBy { candidate -> candidate.tokens.joinToString(" ") } + fun usage() = StringBuilder().apply { + val sortedCandidates = fetchCandidates().toMutableList().apply { + sortBy { candidate -> candidate.tokens.joinToString(" ") } + } val printTemplate: (ActionTemplate) -> Unit = { candidate -> val template = candidate.tokens.joinToString(" ") - usage.appendln(); usage.append("_@${sessionService.botUsername} ${template}_") + appendln(); append("_@${sessionService.botUsername} ${template}_") } - val customActions = candidates.filter(ActionTemplate::showInUsage).filterNot(ActionTemplate::builtIn) + val customActions = sortedCandidates.filter(ActionTemplate::showInUsage).filterNot(ActionTemplate::builtIn) if (customActions.isNotEmpty()) { - usage.append("*Custom actions*") + append("*Custom actions*") customActions.forEach(printTemplate) } - if (usage.isNotEmpty()) { - usage.appendln(); usage.appendln() + if (isNotEmpty()) { + appendln(); appendln() } - val builtInActions = candidates.filter(ActionTemplate::showInUsage).filter(ActionTemplate::builtIn) + val builtInActions = sortedCandidates.filter(ActionTemplate::showInUsage).filter(ActionTemplate::builtIn) if (builtInActions.isNotEmpty()) { - usage.append("*Built-in actions*") + append("*Built-in actions*") builtInActions.forEach(printTemplate) } - return usage } } diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt index 7cc8ca5f..0c554cd0 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt @@ -4,6 +4,7 @@ import com.gatehill.corebot.chat.model.action.Action import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.model.ActionConfig import java.util.* +import java.util.regex.Pattern /** * An abstract representation of a templated action. @@ -13,7 +14,9 @@ interface ActionTemplate { val showInUsage: Boolean val actionType: ActionType val tokens: Queue + val templateRegex: Pattern? val actionMessageMode: ActionMessageMode + val placeholderValues: MutableMap /** * Convert the action templates to a human-readable String. @@ -25,6 +28,11 @@ interface ActionTemplate { */ fun accept(input: String): Boolean + /** + * Hook for subclasses. + */ + fun onTemplateSatisfied() = true + /** * List the actions from this template. */ diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/BaseActionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/BaseActionTemplate.kt index 3e3fd281..f3c0d468 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/BaseActionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/BaseActionTemplate.kt @@ -2,13 +2,15 @@ package com.gatehill.corebot.chat.model.template import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.config.model.readActionConfigAttribute +import java.util.regex.Pattern /** * Parses tokens into placeholder values. */ abstract class BaseActionTemplate : ActionTemplate { - protected val placeholderValues = mutableMapOf() protected abstract val actionConfigs: List + override val placeholderValues = mutableMapOf() + override val templateRegex: Pattern? = null override val actionTemplates: String get() = readActionConfigAttribute(actionConfigs, ActionConfig::template) @@ -33,11 +35,6 @@ abstract class BaseActionTemplate : ActionTemplate { return if (accepted && tokens.isEmpty()) onTemplateSatisfied() else accepted } - /** - * Hook for subclasses. - */ - open fun onTemplateSatisfied() = true - /** * A short, human readable description. */ @@ -49,4 +46,15 @@ abstract class BaseActionTemplate : ActionTemplate { */ override fun buildStartMessage(options: Map, actionConfig: ActionConfig?) = actionConfig?.let { "I'm working on *${actionConfig.name}*..." } ?: run { "I'm working on it..." } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is BaseActionTemplate) return false + + if (javaClass != other.javaClass) return false + + return true + } + + override fun hashCode(): Int = javaClass.hashCode() } diff --git a/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt b/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt index 4511a4ac..01d214c8 100644 --- a/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt +++ b/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt @@ -49,25 +49,24 @@ open class SlackChatServiceImpl @Inject constructor(private val sessionService: if (session.sessionPersona().id == event.sender.id) return@SlackMessagePostedListener try { - event.messageContent?.trim()?.let { messageContent -> - val splitCmd = messageContent.split("\"?( |$)(?=(([^\"]*\"){2})*[^\"]*$)\"?".toRegex()).filterNot(String::isBlank) - - if (splitCmd.isNotEmpty() && isAddressedToBot(session.sessionPersona(), splitCmd[0])) { - // indicate busy... - session.addReactionToMessage(event.channel, event.timeStamp, "hourglass_flowing_sand") - - parseMessage(splitCmd)?.let { parsed -> - logger.info("Handling command '$messageContent' from ${event.sender.userName}") - parsed.groupStartMessage?.let { sessionService.sendMessage(event, it) } - parsed.actions.forEach { action -> handleAction(session, event, action, parsed) } - - } ?: run { - logger.warn("Ignored command '$messageContent' from ${event.sender.userName}") - session.addReactionToMessage(event.channel, event.timeStamp, "question") - printUsage(event) - } + val messageContent = event.messageContent.trim() + val splitCmd = messageContent.split("\"?( |$)(?=(([^\"]*\"){2})*[^\"]*$)\"?".toRegex()).filterNot(String::isBlank) + + if (splitCmd.isNotEmpty() && isAddressedToBot(session.sessionPersona(), splitCmd[0])) { + // indicate busy... + session.addReactionToMessage(event.channel, event.timeStamp, "hourglass_flowing_sand") + + parseMessage(splitCmd)?.let { parsed -> + logger.info("Handling command '$messageContent' from ${event.sender.userName}") + parsed.groupStartMessage?.let { session.sendMessage(event.channel, it) } + parsed.actions.forEach { action -> handleAction(session, event, action, parsed) } + + } ?: run { + logger.warn("Ignored command '$messageContent' from ${event.sender.userName}") + session.addReactionToMessage(event.channel, event.timeStamp, "question") + printUsage(event) } - } ?: logger.trace("Ignoring event with null message: $event") + } } catch(e: Exception) { logger.error("Error parsing message event: $event", e) @@ -81,31 +80,21 @@ open class SlackChatServiceImpl @Inject constructor(private val sessionService: * Determine the Action to perform based on the provided command. */ private fun parseMessage(splitCmd: List): ActionWrapper? { - val joinedMessage = splitCmd.joinToString() - try { - val context = templateService.fetchCandidates() - - // skip element 0, which contains the bot's username - splitCmd.subList(1, splitCmd.size).forEach { - token -> - templateService.process(context, token) - } - - // remove unsatisfied candidates - val satisfied = context.candidates.filter { candidate -> candidate.tokens.isEmpty() } - when (satisfied.size) { - 1 -> { - val candidate = satisfied.first() - return ActionWrapper(candidate.buildActions(), - if (candidate.actionMessageMode == ActionMessageMode.GROUP) candidate.buildStartMessage() else null, - if (candidate.actionMessageMode == ActionMessageMode.GROUP) candidate.buildCompleteMessage() else null) + templateService.findSatisfiedTemplates(splitCmd).let { satisfied -> + if (satisfied.size == 1) { + return with(satisfied.first()) { + ActionWrapper(buildActions(), + if (actionMessageMode == ActionMessageMode.GROUP) buildStartMessage() else null, + if (actionMessageMode == ActionMessageMode.GROUP) buildCompleteMessage() else null) + } + } else { + throw IllegalStateException("Could not find a unique matching action for command: $splitCmd") } - else -> throw IllegalStateException("Could not find a unique matching action for command: $joinedMessage") } } catch(e: IllegalStateException) { - logger.warn("Unable to parse message: $joinedMessage - ${e.message}") + logger.warn("Unable to parse message: $splitCmd - ${e.message}") return null } } From 15d0fe2c3f1fa7fd54ceb91e5944ab71329601f4 Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Wed, 31 May 2017 18:37:36 +0100 Subject: [PATCH 07/36] Allow a partition to be retrieved using a reified generic type for the value class. --- .../corebot/driver/items/service/ClaimService.kt | 7 ++++--- .../kotlin/com/gatehill/corebot/action/LockService.kt | 5 +++-- .../kotlin/com/gatehill/corebot/store/DataStore.kt | 10 +++++++++- .../gatehill/corebot/store/InMemoryDataStoreImpl.kt | 2 +- .../gatehill/corebot/store/redis/RedisDataStoreImpl.kt | 4 ++-- 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt index e6bea0db..d267f318 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt @@ -5,6 +5,7 @@ import com.gatehill.corebot.chat.SessionService import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.items.chat.model.template.BorrowItemTemplate import com.gatehill.corebot.store.DataStore +import com.gatehill.corebot.store.partition import java.util.concurrent.CompletableFuture import javax.inject.Inject import javax.inject.Named @@ -24,8 +25,8 @@ class ClaimService @Inject constructor(private val sessionService: SessionServic data class ItemClaims(val claims: List) - private val itemClaims = - dataStore.partition("items", ItemClaims::class.java) + private val itemClaims + get() = dataStore.partition("itemClaims") fun claimItem(future: CompletableFuture, action: ActionConfig, args: Map, triggerMessageSenderId: String) { @@ -81,7 +82,7 @@ class ClaimService @Inject constructor(private val sessionService: SessionServic itemClaims.remove(itemName) val nonSelfBorrowers = claims - .map{ it.owner } + .map { it.owner } .filter { it != triggerMessageSenderId } val previousBorrowers = if (nonSelfBorrowers.isEmpty()) { diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/LockService.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/LockService.kt index 2a1f4576..6b207444 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/action/LockService.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/LockService.kt @@ -4,6 +4,7 @@ import com.gatehill.corebot.action.model.PerformActionResult import com.gatehill.corebot.chat.model.template.BaseLockOptionTemplate import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.store.DataStore +import com.gatehill.corebot.store.partition import java.util.concurrent.CompletableFuture import javax.inject.Inject import javax.inject.Named @@ -27,10 +28,10 @@ class LockService @Inject constructor(@Named("lockStore") private val lockStore: val optionValue: String) private val actionLocks - get() = lockStore.partition("actionLocks", ActionLock::class.java) + get() = lockStore.partition("actionLocks") private val optionLocks - get() = lockStore.partition("optionLocks", OptionLock::class.java) + get() = lockStore.partition("optionLocks") fun lockAction(future: CompletableFuture, action: ActionConfig, triggerMessageSenderId: String) { diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/store/DataStore.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/store/DataStore.kt index 2aa71c25..9d629ac2 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/store/DataStore.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/store/DataStore.kt @@ -6,9 +6,16 @@ package com.gatehill.corebot.store * @author Pete Cornish {@literal } */ interface DataStore { - fun partition(partitionId: String, clazz: Class): DataStorePartition + fun partitionForClass(partitionId: String, valueClass: Class): DataStorePartition } +/** + * Syntactic sugar, which calls `DataStore.partitionForClass()` using the reified + * type as the value class. + */ +inline fun DataStore.partition(partitionId: String) = + this.partitionForClass(partitionId, V::class.java) + /** * Stores a particular type of data in a store. * @@ -18,4 +25,5 @@ interface DataStorePartition { operator fun set(key: K, value: V) operator fun get(key: K): V? fun remove(key: K) + } diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/store/InMemoryDataStoreImpl.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/store/InMemoryDataStoreImpl.kt index 67ddf99a..913eb03d 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/store/InMemoryDataStoreImpl.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/store/InMemoryDataStoreImpl.kt @@ -9,7 +9,7 @@ class InMemoryDataStoreImpl : DataStore { private val partitions = mutableMapOf>() @Suppress("UNCHECKED_CAST") - override fun partition(partitionId: String, clazz: Class): DataStorePartition = + override fun partitionForClass(partitionId: String, valueClass: Class): DataStorePartition = partitions[partitionId] as DataStorePartition? ?: InMemoryDataStorePartitionImpl().apply { partitions[partitionId] = this } } diff --git a/stores/redis/src/main/kotlin/com/gatehill/corebot/store/redis/RedisDataStoreImpl.kt b/stores/redis/src/main/kotlin/com/gatehill/corebot/store/redis/RedisDataStoreImpl.kt index 21e922b8..93efb183 100644 --- a/stores/redis/src/main/kotlin/com/gatehill/corebot/store/redis/RedisDataStoreImpl.kt +++ b/stores/redis/src/main/kotlin/com/gatehill/corebot/store/redis/RedisDataStoreImpl.kt @@ -16,9 +16,9 @@ class RedisDataStoreImpl : DataStore { private val partitions = mutableMapOf>() @Suppress("UNCHECKED_CAST") - override fun partition(partitionId: String, clazz: Class): DataStorePartition = + override fun partitionForClass(partitionId: String, valueClass: Class): DataStorePartition = partitions[partitionId] as DataStorePartition? ?: - RedisDataStorePartitionImpl(jsonMapper, clazz, partitionId).apply { partitions[partitionId] = this } + RedisDataStorePartitionImpl(jsonMapper, valueClass, partitionId).apply { partitions[partitionId] = this } } /** From be7cdd525270bda77408cb28c5be25c3f5892dc3 Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Wed, 31 May 2017 19:57:00 +0100 Subject: [PATCH 08/36] Bumps to next development version. Changes subproject names to include parent module names. Removes system-property based injection extension point. --- backends/deployment/jenkins/build.gradle | 4 +-- backends/deployment/jobs/build.gradle | 2 +- backends/deployment/rundeck/build.gradle | 4 +-- backends/items/build.gradle | 2 +- bots/common/build.gradle | 2 +- .../main/kotlin/com/gatehill/corebot/Bot.kt | 12 +++------ bots/slack-deploy/Dockerfile | 4 +-- bots/slack-deploy/build.gradle | 10 +++---- bots/slack-items/Dockerfile | 4 +-- bots/slack-items/build.gradle | 8 +++--- core/engine/build.gradle | 2 +- core/rest/build.gradle | 2 +- frontends/slack/build.gradle | 4 +-- settings.gradle | 27 +++++++++++++++++-- stores/redis/build.gradle | 2 +- 15 files changed, 53 insertions(+), 36 deletions(-) diff --git a/backends/deployment/jenkins/build.gradle b/backends/deployment/jenkins/build.gradle index 2e539c64..54bed23c 100644 --- a/backends/deployment/jenkins/build.gradle +++ b/backends/deployment/jenkins/build.gradle @@ -9,8 +9,8 @@ apply plugin: 'kotlin' dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$version_kotlin" - compile project(':backends:deployment:jobs') - compile project(':core:rest') + compile project(':backends:deployment:backends-deployment-jobs') + compile project(':core:core-rest') } publishing { diff --git a/backends/deployment/jobs/build.gradle b/backends/deployment/jobs/build.gradle index c691e7bc..1624a433 100644 --- a/backends/deployment/jobs/build.gradle +++ b/backends/deployment/jobs/build.gradle @@ -7,7 +7,7 @@ buildscript { apply plugin: 'kotlin' dependencies { - compile project(':core:engine') + compile project(':core:core-engine') } publishing { diff --git a/backends/deployment/rundeck/build.gradle b/backends/deployment/rundeck/build.gradle index 2e539c64..54bed23c 100644 --- a/backends/deployment/rundeck/build.gradle +++ b/backends/deployment/rundeck/build.gradle @@ -9,8 +9,8 @@ apply plugin: 'kotlin' dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$version_kotlin" - compile project(':backends:deployment:jobs') - compile project(':core:rest') + compile project(':backends:deployment:backends-deployment-jobs') + compile project(':core:core-rest') } publishing { diff --git a/backends/items/build.gradle b/backends/items/build.gradle index e348af3f..4fd4def4 100644 --- a/backends/items/build.gradle +++ b/backends/items/build.gradle @@ -9,7 +9,7 @@ apply plugin: "kotlin" dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$version_kotlin" - compile project(':core:engine') + compile project(':core:core-engine') testCompile "junit:junit:$version_junit" testCompile "org.jetbrains.kotlin:kotlin-test-junit:$version_kotlin" diff --git a/bots/common/build.gradle b/bots/common/build.gradle index 97bf0ec9..d259a8f5 100644 --- a/bots/common/build.gradle +++ b/bots/common/build.gradle @@ -19,7 +19,7 @@ dependencies { compile "com.fasterxml.jackson.module:jackson-module-kotlin:$version_jackson" compile "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:$version_jackson" - compile project(':core:engine') + compile project(':core:core-engine') } publishing { diff --git a/bots/common/src/main/kotlin/com/gatehill/corebot/Bot.kt b/bots/common/src/main/kotlin/com/gatehill/corebot/Bot.kt index 009acad9..f3589781 100644 --- a/bots/common/src/main/kotlin/com/gatehill/corebot/Bot.kt +++ b/bots/common/src/main/kotlin/com/gatehill/corebot/Bot.kt @@ -30,7 +30,6 @@ class Bot @Inject constructor(private val chatService: ChatService) { * Constructs `Bot` instances. */ companion object Builder { - private val injectionModuleSystemProperty = "com.gatehill.corebot.InjectionModule" private val logger: Logger = LogManager.getLogger(Builder::class.java) /** @@ -59,14 +58,9 @@ class Bot @Inject constructor(private val chatService: ChatService) { bind(ActionOutcomeService::class.java).to(ActionOutcomeServiceImpl::class.java) // extension point - with(extensionModules.toMutableList()) { - System.getProperty(injectionModuleSystemProperty)?.let { - add(Class.forName(it).newInstance() as Module) - } - forEach { - logger.debug("Installing injection module: ${it.javaClass.canonicalName}") - install(it) - } + extensionModules.forEach { + logger.debug("Installing injection module: ${it.javaClass.canonicalName}") + install(it) } } }) diff --git a/bots/slack-deploy/Dockerfile b/bots/slack-deploy/Dockerfile index 051de0d1..581a84d5 100644 --- a/bots/slack-deploy/Dockerfile +++ b/bots/slack-deploy/Dockerfile @@ -4,8 +4,8 @@ MAINTAINER Pete Cornish RUN mkdir -p /opt/corebot /opt/corebot/config -ADD build/install/slack-deploy /opt/corebot +ADD build/install/bots-slack-deploy /opt/corebot WORKDIR "/opt/corebot" -ENTRYPOINT [ "./bin/slack-deploy" ] +ENTRYPOINT [ "./bin/bots-slack-deploy" ] diff --git a/bots/slack-deploy/build.gradle b/bots/slack-deploy/build.gradle index a13a0e27..22e88257 100644 --- a/bots/slack-deploy/build.gradle +++ b/bots/slack-deploy/build.gradle @@ -10,11 +10,11 @@ apply plugin: "application" dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$version_kotlin" - compile project(':bots:common') - compile project(':backends:deployment:jenkins') - compile project(':backends:deployment:rundeck') - compile project(':stores:redis') - compile project(':frontends:slack') + compile project(':bots:bots-common') + compile project(':backends:deployment:backends-deployment-jenkins') + compile project(':backends:deployment:backends-deployment-rundeck') + compile project(':stores:stores-redis') + compile project(':frontends:frontends-slack') } mainClassName = 'com.gatehill.corebot.MainKt' diff --git a/bots/slack-items/Dockerfile b/bots/slack-items/Dockerfile index c107f006..bab26ee4 100644 --- a/bots/slack-items/Dockerfile +++ b/bots/slack-items/Dockerfile @@ -4,8 +4,8 @@ MAINTAINER Pete Cornish RUN mkdir -p /opt/corebot /opt/corebot/config -ADD build/install/slack-items /opt/corebot +ADD build/install/bots-slack-items /opt/corebot WORKDIR "/opt/corebot" -ENTRYPOINT [ "./bin/slack-items" ] +ENTRYPOINT [ "./bin/bots-slack-items" ] diff --git a/bots/slack-items/build.gradle b/bots/slack-items/build.gradle index b341546f..4a8ed8b1 100644 --- a/bots/slack-items/build.gradle +++ b/bots/slack-items/build.gradle @@ -10,10 +10,10 @@ apply plugin: "application" dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$version_kotlin" - compile project(':bots:common') - compile project(':backends:items') - compile project(':stores:redis') - compile project(':frontends:slack') + compile project(':bots:bots-common') + compile project(':backends:backends-items') + compile project(':stores:stores-redis') + compile project(':frontends:frontends-slack') testCompile "junit:junit:$version_junit" testCompile "org.jetbrains.kotlin:kotlin-test-junit:$version_kotlin" diff --git a/core/engine/build.gradle b/core/engine/build.gradle index 3ceafa19..ba533bcf 100644 --- a/core/engine/build.gradle +++ b/core/engine/build.gradle @@ -33,7 +33,7 @@ dependencies { compile "com.google.guava:guava:$version_guava" compile "com.google.inject:guice:$version_guice" - compile project(':core:api') + compile project(':core:core-api') testCompile "org.amshove.kluent:kluent:$version_kluent" testCompile "org.jetbrains.spek:spek-api:$version_spek" diff --git a/core/rest/build.gradle b/core/rest/build.gradle index aaea550f..416bf82d 100644 --- a/core/rest/build.gradle +++ b/core/rest/build.gradle @@ -14,7 +14,7 @@ dependencies { compile "com.squareup.retrofit2:retrofit:$version_retrofit" compile "com.squareup.retrofit2:converter-jackson:$version_retrofit" - compile project(':core:engine') + compile project(':core:core-engine') } publishing { diff --git a/frontends/slack/build.gradle b/frontends/slack/build.gradle index 59558b68..47df03cc 100644 --- a/frontends/slack/build.gradle +++ b/frontends/slack/build.gradle @@ -14,8 +14,8 @@ dependencies { } compile "org.glassfish.tyrus.bundles:tyrus-standalone-client:$version_tyrus" - compile project(':core:engine') - compile project(':bots:common') + compile project(':core:core-engine') + compile project(':bots:bots-common') } publishing { diff --git a/settings.gradle b/settings.gradle index 1898fe97..e267cab5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,14 +1,37 @@ rootProject.name = 'corebot' -include ':backends:deployment:jobs' include ':backends:deployment:jenkins' +project(':backends:deployment:jenkins').name = 'backends-deployment-jenkins' + +include ':backends:deployment:jobs' +project(':backends:deployment:jobs').name = 'backends-deployment-jobs' + include ':backends:deployment:rundeck' +project(':backends:deployment:rundeck').name = 'backends-deployment-rundeck' + include ':backends:items' +project(':backends:items').name = 'backends-items' + include ':bots:common' +project(':bots:common').name = 'bots-common' + include ':bots:slack-deploy' +project(':bots:slack-deploy').name = 'bots-slack-deploy' + include ':bots:slack-items' +project(':bots:slack-items').name = 'bots-slack-items' + include ':core:api' +project(':core:api').name = 'core-api' + include ':core:engine' +project(':core:engine').name = 'core-engine' + include ':core:rest' -include ':stores:redis' +project(':core:rest').name = 'core-rest' + include ':frontends:slack' +project(':frontends:slack').name = 'frontends-slack' + +include ':stores:redis' +project(':stores:redis').name = 'stores-redis' diff --git a/stores/redis/build.gradle b/stores/redis/build.gradle index a6a08c1f..d870e30d 100644 --- a/stores/redis/build.gradle +++ b/stores/redis/build.gradle @@ -7,7 +7,7 @@ buildscript { apply plugin: 'kotlin' dependencies { - compile project(':core:engine') + compile project(':core:core-engine') compile('redis.clients:jedis:2.9.0') } From 714439784a949711d76ae95d3cfadf1cd6d8b016 Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Thu, 1 Jun 2017 21:26:10 +0100 Subject: [PATCH 09/36] Adds optional sub-item support to borrow action. --- .../chat/model/template/BorrowItemTemplate.kt | 5 +++-- .../driver/items/service/ClaimService.kt | 21 +++++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt index 37d24fad..507c3ca4 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt @@ -13,13 +13,14 @@ import javax.inject.Inject */ class BorrowItemTemplate @Inject constructor(configService: ConfigService) : BaseItemTemplate(configService) { override val actionType: ActionType = ItemsActionType.ITEM_BORROW - override val tokens = LinkedList(listOf("{$itemPlaceholder}", "{$reasonPlaceholder}")) + override val tokens = LinkedList(listOf("borrow", "{$itemPlaceholder}", "{$subItemPlaceholder}", "for", "{$reasonPlaceholder}")) override val templateRegex: Pattern? - get() = "borrow (?[a-zA-Z0-9]+) for (?.+)".toPattern() + get() = "borrow\\s+(?[a-zA-Z0-9]+)\\s*(?.*)\\s+for\\s+(?.+)".toPattern() override fun buildStartMessage(options: Map, actionConfig: ActionConfig?) = "" companion object { + val subItemPlaceholder = "optionalSubItemName" val reasonPlaceholder = "reason" } } diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt index d267f318..e4966059 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt @@ -1,5 +1,6 @@ package com.gatehill.corebot.driver.items.service +import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.gatehill.corebot.action.model.PerformActionResult import com.gatehill.corebot.chat.SessionService import com.gatehill.corebot.config.model.ActionConfig @@ -21,7 +22,10 @@ class ClaimService @Inject constructor(private val sessionService: SessionServic /** * A claim held on an item. */ - data class ItemClaim(val owner: String, val reason: String) + @JsonIgnoreProperties(ignoreUnknown = true) + data class ItemClaim(val owner: String, + val reason: String, + val subItem: String?) data class ItemClaims(val claims: List) @@ -34,7 +38,8 @@ class ClaimService @Inject constructor(private val sessionService: SessionServic val itemName = action.name synchronized(itemName) { checkItemClaims(action) { claims -> - val reason = args[BorrowItemTemplate.reasonPlaceholder]!! + val reason : String = args[BorrowItemTemplate.reasonPlaceholder]!! + val subItem : String? = args[BorrowItemTemplate.subItemPlaceholder] itemClaims[itemName] = ItemClaims(claims.toMutableList().apply { @@ -43,7 +48,7 @@ class ClaimService @Inject constructor(private val sessionService: SessionServic remove(existing) } - add(ItemClaim(triggerMessageSenderId, reason)) + add(ItemClaim(triggerMessageSenderId, reason, subItem)) }) future.complete(PerformActionResult("You've borrowed :lock: *$itemName* for _${reason}_.")) @@ -116,13 +121,17 @@ class ClaimService @Inject constructor(private val sessionService: SessionServic claims.size == 1 -> { val claim = claims.first() val ownerUsername = sessionService.lookupUser(claim.owner) - "There is a single borrower of *$itemName*: $ownerUsername - ${claim.reason}" + val subItemDescription = claim.subItem?.let { " (${claim.subItem})" } ?: "" + + "There is a single borrower of *$itemName*$subItemDescription: $ownerUsername - ${claim.reason}" } else -> { val claimsList = StringBuilder() - claims.forEach { (owner, reason) -> + claims.forEach { (owner, reason, subItem) -> val ownerUsername = sessionService.lookupUser(owner) - claimsList.append("\n• $ownerUsername - $reason") + val subItemDescription = subItem?.let { "$subItem: " } ?: "" + + claimsList.append("\n• $subItemDescription$ownerUsername - $reason") } "There are ${claims.size} borrowers of *$itemName*:$claimsList" } From 99d302122e06cb612e087e184458ccc069852c4f Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Fri, 2 Jun 2017 12:15:57 +0100 Subject: [PATCH 10/36] Optionally print status after change. --- .../items/action/ItemsActionDriverImpl.kt | 6 +-- .../driver/items/config/ItemSettings.kt | 8 ++++ .../driver/items/service/ClaimService.kt | 41 +++++++++++++------ .../action/ActionOutcomeServiceImpl.kt | 1 - 4 files changed, 39 insertions(+), 17 deletions(-) create mode 100644 backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/config/ItemSettings.kt diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt index 55396e52..b74009b1 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt @@ -18,9 +18,9 @@ class ItemsActionDriverImpl @Inject constructor(private val claimService: ClaimS val future = CompletableFuture() try { when (actionType) { - ItemsActionType.ITEM_BORROW -> claimService.claimItem(future, action, args, trigger.userId) - ItemsActionType.ITEM_RETURN -> claimService.releaseItem(future, action, trigger.userId) - ItemsActionType.ITEM_EVICT -> claimService.evictItemClaims(future, action, trigger.userId) + ItemsActionType.ITEM_BORROW -> claimService.claimItem(trigger, future, action, args, trigger.userId) + ItemsActionType.ITEM_RETURN -> claimService.releaseItem(trigger, future, action, trigger.userId) + ItemsActionType.ITEM_EVICT -> claimService.evictItemClaims(trigger, future, action, trigger.userId) ItemsActionType.ITEM_STATUS -> claimService.checkItemStatus(future, action) else -> { throw UnsupportedOperationException( diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/config/ItemSettings.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/config/ItemSettings.kt new file mode 100644 index 00000000..95c45e79 --- /dev/null +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/config/ItemSettings.kt @@ -0,0 +1,8 @@ +package com.gatehill.corebot.driver.items.config + +/** + * Item driver settings. + */ +object ItemSettings { + val showStatusOnChange by lazy { System.getenv("ITEMS_SHOW_STATUS_ON_CHANGE")?.toBoolean() ?: false } +} diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt index e4966059..c40f4fb5 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt @@ -2,9 +2,11 @@ package com.gatehill.corebot.driver.items.service import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.gatehill.corebot.action.model.PerformActionResult +import com.gatehill.corebot.action.model.TriggerContext import com.gatehill.corebot.chat.SessionService import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.items.chat.model.template.BorrowItemTemplate +import com.gatehill.corebot.driver.items.config.ItemSettings import com.gatehill.corebot.store.DataStore import com.gatehill.corebot.store.partition import java.util.concurrent.CompletableFuture @@ -32,14 +34,14 @@ class ClaimService @Inject constructor(private val sessionService: SessionServic private val itemClaims get() = dataStore.partition("itemClaims") - fun claimItem(future: CompletableFuture, action: ActionConfig, args: Map, + fun claimItem(trigger: TriggerContext, future: CompletableFuture, action: ActionConfig, args: Map, triggerMessageSenderId: String) { val itemName = action.name synchronized(itemName) { checkItemClaims(action) { claims -> - val reason : String = args[BorrowItemTemplate.reasonPlaceholder]!! - val subItem : String? = args[BorrowItemTemplate.subItemPlaceholder] + val reason: String = args[BorrowItemTemplate.reasonPlaceholder]!! + val subItem: String? = args[BorrowItemTemplate.subItemPlaceholder] itemClaims[itemName] = ItemClaims(claims.toMutableList().apply { @@ -51,15 +53,17 @@ class ClaimService @Inject constructor(private val sessionService: SessionServic add(ItemClaim(triggerMessageSenderId, reason, subItem)) }) - future.complete(PerformActionResult("You've borrowed :lock: *$itemName* for _${reason}_.")) + completeWithStatusCheck(trigger, future, itemName, + "You've borrowed :lock: *$itemName* for _${reason}_.") } } } - fun releaseItem(future: CompletableFuture, action: ActionConfig, + fun releaseItem(trigger: TriggerContext, future: CompletableFuture, action: ActionConfig, triggerMessageSenderId: String) { val itemName = action.name + synchronized(itemName) { checkItemClaims(action) { claims -> @@ -68,17 +72,16 @@ class ClaimService @Inject constructor(private val sessionService: SessionServic itemClaims[itemName] = ItemClaims(claims.toMutableList().apply { remove(claim) }) - - future.complete(PerformActionResult("You've returned *$itemName*.")) + completeWithStatusCheck(trigger, future, itemName, "You've returned *$itemName*.") } ?: run { - future.complete(PerformActionResult("BTW, you weren't borrowing *$itemName* :wink:")) + completeWithStatusCheck(trigger, future, itemName, "BTW, you weren't borrowing *$itemName* :wink:") } } } } - fun evictItemClaims(future: CompletableFuture, action: ActionConfig, + fun evictItemClaims(trigger: TriggerContext, future: CompletableFuture, action: ActionConfig, triggerMessageSenderId: String) { val itemName = action.name @@ -96,12 +99,24 @@ class ClaimService @Inject constructor(private val sessionService: SessionServic "\n_(FYI ${nonSelfBorrowers.map { "<@$it>" }.joinToString()})_" } - val message = "I've :unlock: evicted all borrowers (${claims.size}) from *$itemName*.$previousBorrowers" - future.complete(PerformActionResult(message)) + completeWithStatusCheck(trigger, future, itemName, + "I've :unlock: evicted all borrowers (${claims.size}) from *$itemName*.$previousBorrowers") } } } + /** + * Follow up with status. + */ + private fun completeWithStatusCheck(trigger: TriggerContext, future: CompletableFuture, + itemName: String, message: String) { + + future.complete(PerformActionResult(message)) + if (ItemSettings.showStatusOnChange) { + describeItem(itemName) { sessionService.sendMessage(trigger.channelId, it) } + } + } + fun checkItemClaims(itemName: String, callback: (List) -> Unit) = callback(itemClaims[itemName]?.claims ?: emptyList()) @@ -121,7 +136,7 @@ class ClaimService @Inject constructor(private val sessionService: SessionServic claims.size == 1 -> { val claim = claims.first() val ownerUsername = sessionService.lookupUser(claim.owner) - val subItemDescription = claim.subItem?.let { " (${claim.subItem})" } ?: "" + val subItemDescription = claim.subItem?.let { if (it.isNotBlank()) " ($it)" else null } ?: "" "There is a single borrower of *$itemName*$subItemDescription: $ownerUsername - ${claim.reason}" } @@ -129,7 +144,7 @@ class ClaimService @Inject constructor(private val sessionService: SessionServic val claimsList = StringBuilder() claims.forEach { (owner, reason, subItem) -> val ownerUsername = sessionService.lookupUser(owner) - val subItemDescription = subItem?.let { "$subItem: " } ?: "" + val subItemDescription = subItem?.let { if (it.isNotBlank()) "$it: " else null } ?: "" claimsList.append("\n• $subItemDescription$ownerUsername - $reason") } diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/ActionOutcomeServiceImpl.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/ActionOutcomeServiceImpl.kt index bbafe9b9..af7e743d 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/action/ActionOutcomeServiceImpl.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/ActionOutcomeServiceImpl.kt @@ -48,7 +48,6 @@ open class ActionOutcomeServiceImpl @Inject constructor(private val sessionServi override fun handleTimeout(trigger: TriggerContext, action: ActionConfig, blockDescription: String) { logger.error("Timed out '$blockDescription' after ${Settings.execution.executionTimeout}ms") - sessionService.addReaction(trigger, "x") val timeoutSecs = TimeUnit.MILLISECONDS.toSeconds(Settings.execution.executionTimeout.toLong()) From 9f2824e032df9bdcacc59e6022ff13ac3b8d223a Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Fri, 2 Jun 2017 13:00:20 +0100 Subject: [PATCH 11/36] Allow borrowing as another user. --- .../items/action/ItemsActionDriverImpl.kt | 1 + .../items/action/model/ItemsActionType.kt | 1 + .../template/BorrowItemAsUserTemplate.kt | 23 ++++++++++ .../chat/model/template/BorrowItemTemplate.kt | 2 +- .../chat/model/template/StatusAllTemplate.kt | 21 ++------- .../driver/items/service/ClaimService.kt | 45 +++++++++++++------ .../kotlin/com/gatehill/corebot/Bootstrap.kt | 1 + .../src/main/resources/default-security.yml | 1 + 8 files changed, 63 insertions(+), 32 deletions(-) create mode 100644 backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemAsUserTemplate.kt diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt index b74009b1..695e360d 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt @@ -19,6 +19,7 @@ class ItemsActionDriverImpl @Inject constructor(private val claimService: ClaimS try { when (actionType) { ItemsActionType.ITEM_BORROW -> claimService.claimItem(trigger, future, action, args, trigger.userId) + ItemsActionType.ITEM_BORROW_AS_USER -> claimService.claimItem(trigger, future, action, args, trigger.userId) ItemsActionType.ITEM_RETURN -> claimService.releaseItem(trigger, future, action, trigger.userId) ItemsActionType.ITEM_EVICT -> claimService.evictItemClaims(trigger, future, action, trigger.userId) ItemsActionType.ITEM_STATUS -> claimService.checkItemStatus(future, action) diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/model/ItemsActionType.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/model/ItemsActionType.kt index 15c6597f..e95f7c9f 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/model/ItemsActionType.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/model/ItemsActionType.kt @@ -5,6 +5,7 @@ import com.gatehill.corebot.chat.model.action.CoreActionType class ItemsActionType(name: String, description: String) : CoreActionType(name, description) { companion object { val ITEM_BORROW = ItemsActionType("ITEM_BORROW", "Borrow an item") + val ITEM_BORROW_AS_USER = ItemsActionType("ITEM_BORROW_AS_USER", "Borrow an item as another user") val ITEM_RETURN = ItemsActionType("ITEM_RETURN", "Return an item") val ITEM_EVICT = ItemsActionType("ITEM_EVICT", "Evict all borrowers from an item") val ITEM_STATUS = ItemsActionType("ITEM_STATUS", "Check status of an item") diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemAsUserTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemAsUserTemplate.kt new file mode 100644 index 00000000..38ea45c4 --- /dev/null +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemAsUserTemplate.kt @@ -0,0 +1,23 @@ +package com.gatehill.corebot.driver.items.chat.model.template + +import com.gatehill.corebot.chat.model.action.ActionType +import com.gatehill.corebot.config.ConfigService +import com.gatehill.corebot.config.model.ActionConfig +import com.gatehill.corebot.driver.items.action.model.ItemsActionType +import java.util.* +import java.util.regex.Pattern +import javax.inject.Inject + +/** + * Borrow an item as another user. + */ +class BorrowItemAsUserTemplate @Inject constructor(configService: ConfigService) : BorrowItemTemplate(configService) { + override val actionType: ActionType = ItemsActionType.ITEM_BORROW_AS_USER + override val tokens = LinkedList(listOf("as", "{$borrower}", "borrow", "{$itemPlaceholder}", "{$subItemPlaceholder}", "for", "{$reasonPlaceholder}")) + override val templateRegex: Pattern? + get() = "as\\s\\<@(?[a-zA-Z0-9]+)\\>\\sborrow\\s+(?[a-zA-Z0-9]+)\\s*(?.*)\\s+for\\s+(?.+)".toPattern() + + companion object { + val borrower = "borrower" + } +} diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt index 507c3ca4..292b5492 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt @@ -11,7 +11,7 @@ import javax.inject.Inject /** * Borrow an item. */ -class BorrowItemTemplate @Inject constructor(configService: ConfigService) : BaseItemTemplate(configService) { +open class BorrowItemTemplate @Inject constructor(configService: ConfigService) : BaseItemTemplate(configService) { override val actionType: ActionType = ItemsActionType.ITEM_BORROW override val tokens = LinkedList(listOf("borrow", "{$itemPlaceholder}", "{$subItemPlaceholder}", "for", "{$reasonPlaceholder}")) override val templateRegex: Pattern? diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusAllTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusAllTemplate.kt index c25c520b..283b64bd 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusAllTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusAllTemplate.kt @@ -3,7 +3,6 @@ package com.gatehill.corebot.driver.items.chat.model.template import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.chat.model.template.ActionMessageMode import com.gatehill.corebot.chat.model.template.SystemActionTemplate -import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.items.action.model.ItemsActionType import com.gatehill.corebot.driver.items.service.ClaimService @@ -13,27 +12,13 @@ import javax.inject.Inject /** * Show status and claims for all items. */ -class StatusAllTemplate @Inject constructor(val configService: ConfigService, - val claimService: ClaimService) : SystemActionTemplate() { - +class StatusAllTemplate @Inject constructor(val claimService: ClaimService) : SystemActionTemplate() { override val builtIn = false override val showInUsage = true override val actionMessageMode = ActionMessageMode.INDIVIDUAL override val actionType: ActionType = ItemsActionType.ALL_STATUS override val tokens = LinkedList(listOf("status")) - override fun buildStartMessage(options: Map, actionConfig: ActionConfig?): String { - val status = StringBuilder() - - configService.actions().keys.forEach { itemName -> - claimService.describeItem(itemName) { - if (status.isNotEmpty()) { - status.append("\n") - } - status.append(it) - } - } - - return "Here's the latest:\n$status" - } + override fun buildStartMessage(options: Map, actionConfig: ActionConfig?) = + claimService.describeAllItemStatus() } diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt index c40f4fb5..79cf8943 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt @@ -4,7 +4,9 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.gatehill.corebot.action.model.PerformActionResult import com.gatehill.corebot.action.model.TriggerContext import com.gatehill.corebot.chat.SessionService +import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig +import com.gatehill.corebot.driver.items.chat.model.template.BorrowItemAsUserTemplate import com.gatehill.corebot.driver.items.chat.model.template.BorrowItemTemplate import com.gatehill.corebot.driver.items.config.ItemSettings import com.gatehill.corebot.store.DataStore @@ -18,7 +20,8 @@ import javax.inject.Named * * @author Pete Cornish {@literal } */ -class ClaimService @Inject constructor(private val sessionService: SessionService, +class ClaimService @Inject constructor(private val configService: ConfigService, + private val sessionService: SessionService, @Named("itemStore") private val dataStore: DataStore) { /** @@ -43,18 +46,21 @@ class ClaimService @Inject constructor(private val sessionService: SessionServic val reason: String = args[BorrowItemTemplate.reasonPlaceholder]!! val subItem: String? = args[BorrowItemTemplate.subItemPlaceholder] + // check if on behalf of another user + val borrower = args[BorrowItemAsUserTemplate.borrower] ?: triggerMessageSenderId + itemClaims[itemName] = ItemClaims(claims.toMutableList().apply { // any existing claim will be replaced - claims.firstOrNull { it.owner == triggerMessageSenderId }?.let { existing -> + claims.firstOrNull { it.owner == borrower }?.let { existing -> remove(existing) } - add(ItemClaim(triggerMessageSenderId, reason, subItem)) + add(ItemClaim(borrower, reason, subItem)) }) - completeWithStatusCheck(trigger, future, itemName, - "You've borrowed :lock: *$itemName* for _${reason}_.") + val borrowerDescription = if (borrower == triggerMessageSenderId) " <@$borrower>, you've" else ", <@$borrower> has" + completeWithStatusCheck(trigger, future, "OK$borrowerDescription borrowed :lock: *$itemName* for _${reason}_.") } } } @@ -72,10 +78,10 @@ class ClaimService @Inject constructor(private val sessionService: SessionServic itemClaims[itemName] = ItemClaims(claims.toMutableList().apply { remove(claim) }) - completeWithStatusCheck(trigger, future, itemName, "You've returned *$itemName*.") + completeWithStatusCheck(trigger, future, "OK <@$triggerMessageSenderId>, you've returned *$itemName*.") } ?: run { - completeWithStatusCheck(trigger, future, itemName, "BTW, you weren't borrowing *$itemName* :wink:") + completeWithStatusCheck(trigger, future, "BTW <@$triggerMessageSenderId>, you weren't borrowing *$itemName* :wink:") } } } @@ -99,8 +105,7 @@ class ClaimService @Inject constructor(private val sessionService: SessionServic "\n_(FYI ${nonSelfBorrowers.map { "<@$it>" }.joinToString()})_" } - completeWithStatusCheck(trigger, future, itemName, - "I've :unlock: evicted all borrowers (${claims.size}) from *$itemName*.$previousBorrowers") + completeWithStatusCheck(trigger, future, "OK <@$triggerMessageSenderId>, I've :unlock: evicted all borrowers (${claims.size}) from *$itemName*.$previousBorrowers") } } } @@ -108,12 +113,11 @@ class ClaimService @Inject constructor(private val sessionService: SessionServic /** * Follow up with status. */ - private fun completeWithStatusCheck(trigger: TriggerContext, future: CompletableFuture, - itemName: String, message: String) { - + private fun completeWithStatusCheck(trigger: TriggerContext, future: CompletableFuture, message: String) { future.complete(PerformActionResult(message)) + if (ItemSettings.showStatusOnChange) { - describeItem(itemName) { sessionService.sendMessage(trigger.channelId, it) } + sessionService.sendMessage(trigger.channelId, describeAllItemStatus()) } } @@ -155,4 +159,19 @@ class ClaimService @Inject constructor(private val sessionService: SessionServic callback(message) } } + + fun describeAllItemStatus(): String { + val status = StringBuilder() + + configService.actions().keys.forEach { itemName -> + describeItem(itemName) { + if (status.isNotEmpty()) { + status.append("\n") + } + status.append(it) + } + } + + return "Here's the latest:\n$status" + } } diff --git a/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt b/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt index 64da5a55..9acbd612 100644 --- a/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt +++ b/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt @@ -19,6 +19,7 @@ class Bootstrap @Inject constructor(actionDriverFactory: ActionDriverFactory, // templates templateService.registerTemplate(ShowHelpTemplate::class.java) templateService.registerTemplate(BorrowItemTemplate::class.java) + templateService.registerTemplate(BorrowItemAsUserTemplate::class.java) templateService.registerTemplate(ReturnItemTemplate::class.java) templateService.registerTemplate(EvictItemTemplate::class.java) templateService.registerTemplate(StatusItemTemplate::class.java) diff --git a/bots/slack-items/src/main/resources/default-security.yml b/bots/slack-items/src/main/resources/default-security.yml index 779040e3..b259d6f2 100644 --- a/bots/slack-items/src/main/resources/default-security.yml +++ b/bots/slack-items/src/main/resources/default-security.yml @@ -9,6 +9,7 @@ security: permissions: - help - item_borrow + - item_borrow_as_user - item_return - item_evict - item_status From c8f79f8d571cca7361fbcf983f7307971cd8037f Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Fri, 2 Jun 2017 23:05:39 +0100 Subject: [PATCH 12/36] Applies ktlint code style. --- .../driver/jenkins/action/JenkinsActionDriver.kt | 2 +- .../corebot/driver/jenkins/action/JenkinsApi.kt | 9 ++++++++- .../driver/jenkins/action/JenkinsJobTriggerService.kt | 2 +- .../gatehill/corebot/action/BaseJobTriggerService.kt | 3 ++- .../corebot/action/driver/JobBaseActionDriver.kt | 2 +- .../corebot/chat/model/template/DisableJobTemplate.kt | 2 +- .../corebot/chat/model/template/EnableJobTemplate.kt | 2 +- .../corebot/chat/model/template/TriggerJobTemplate.kt | 3 ++- .../driver/rundeck/action/ExecutionStatusService.kt | 2 +- .../driver/rundeck/action/RundeckActionDriver.kt | 4 ++-- .../corebot/driver/rundeck/action/RundeckApi.kt | 10 +++++++--- .../driver/rundeck/action/RundeckJobTriggerService.kt | 9 +++++---- .../driver/items/action/ItemsActionDriverImpl.kt | 2 +- .../chat/model/template/BorrowItemAsUserTemplate.kt | 3 +-- .../items/chat/model/template/BorrowItemTemplate.kt | 2 +- .../items/chat/model/template/EvictItemTemplate.kt | 2 +- .../items/chat/model/template/ReturnItemTemplate.kt | 2 +- .../items/chat/model/template/StatusAllTemplate.kt | 2 +- .../items/chat/model/template/StatusItemTemplate.kt | 2 +- .../common/src/main/kotlin/com/gatehill/corebot/Bot.kt | 6 +++++- .../src/main/kotlin/com/gatehill/corebot/Bootstrap.kt | 9 ++++++++- .../src/main/kotlin/com/gatehill/corebot/Bootstrap.kt | 7 ++++++- .../corebot/action/driver/ActionDriverFactory.kt | 2 +- .../gatehill/corebot/action/driver/BaseActionDriver.kt | 2 +- .../corebot/chat/model/template/ActionTemplate.kt | 2 +- .../chat/model/template/CustomActionTemplate.kt | 2 +- .../corebot/chat/model/template/LockActionTemplate.kt | 2 +- .../corebot/chat/model/template/LockOptionTemplate.kt | 2 +- .../corebot/chat/model/template/ShowHelpTemplate.kt | 2 +- .../corebot/chat/model/template/StatusJobTemplate.kt | 2 +- .../chat/model/template/UnlockActionTemplate.kt | 2 +- .../chat/model/template/UnlockOptionTemplate.kt | 2 +- .../com/gatehill/corebot/config/ConfigServiceImpl.kt | 2 +- .../kotlin/com/gatehill/corebot/util/Extensions.kt | 2 +- .../main/kotlin/com/gatehill/corebot/SlackModule.kt | 6 +++++- .../com/gatehill/corebot/chat/SlackChatServiceImpl.kt | 4 ++-- 36 files changed, 77 insertions(+), 44 deletions(-) diff --git a/backends/deployment/jenkins/src/main/kotlin/com/gatehill/corebot/driver/jenkins/action/JenkinsActionDriver.kt b/backends/deployment/jenkins/src/main/kotlin/com/gatehill/corebot/driver/jenkins/action/JenkinsActionDriver.kt index 4a7125e1..8b3a0552 100644 --- a/backends/deployment/jenkins/src/main/kotlin/com/gatehill/corebot/driver/jenkins/action/JenkinsActionDriver.kt +++ b/backends/deployment/jenkins/src/main/kotlin/com/gatehill/corebot/driver/jenkins/action/JenkinsActionDriver.kt @@ -6,7 +6,7 @@ import com.gatehill.corebot.action.driver.JobBaseActionDriver import com.gatehill.corebot.driver.base.action.ApiClientBuilder import com.gatehill.corebot.driver.jenkins.config.DriverSettings import okhttp3.Credentials -import java.util.* +import java.util.HashMap import javax.inject.Inject /** diff --git a/backends/deployment/jenkins/src/main/kotlin/com/gatehill/corebot/driver/jenkins/action/JenkinsApi.kt b/backends/deployment/jenkins/src/main/kotlin/com/gatehill/corebot/driver/jenkins/action/JenkinsApi.kt index 87e20e4c..34dfba0a 100644 --- a/backends/deployment/jenkins/src/main/kotlin/com/gatehill/corebot/driver/jenkins/action/JenkinsApi.kt +++ b/backends/deployment/jenkins/src/main/kotlin/com/gatehill/corebot/driver/jenkins/action/JenkinsApi.kt @@ -4,7 +4,14 @@ import com.gatehill.corebot.driver.jenkins.model.BuildDetails import com.gatehill.corebot.driver.jenkins.model.QueuedItem import okhttp3.ResponseBody import retrofit2.Call -import retrofit2.http.* +import retrofit2.http.Field +import retrofit2.http.FieldMap +import retrofit2.http.FormUrlEncoded +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.POST +import retrofit2.http.Path +import retrofit2.http.Query /** * Models the Jenkins API. diff --git a/backends/deployment/jenkins/src/main/kotlin/com/gatehill/corebot/driver/jenkins/action/JenkinsJobTriggerService.kt b/backends/deployment/jenkins/src/main/kotlin/com/gatehill/corebot/driver/jenkins/action/JenkinsJobTriggerService.kt index 3c42655d..63ea88f3 100644 --- a/backends/deployment/jenkins/src/main/kotlin/com/gatehill/corebot/driver/jenkins/action/JenkinsJobTriggerService.kt +++ b/backends/deployment/jenkins/src/main/kotlin/com/gatehill/corebot/driver/jenkins/action/JenkinsJobTriggerService.kt @@ -46,7 +46,7 @@ class JenkinsJobTriggerService @Inject constructor(private val actionDriver: Jen parameters = args ) - } catch(e: Exception) { + } catch (e: Exception) { future.completeExceptionally(RuntimeException("Error building API client or obtaining CSRF token", e)) return } diff --git a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/action/BaseJobTriggerService.kt b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/action/BaseJobTriggerService.kt index 8b53cb8e..29eef3ab 100644 --- a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/action/BaseJobTriggerService.kt +++ b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/action/BaseJobTriggerService.kt @@ -8,7 +8,8 @@ import com.gatehill.corebot.config.Settings import com.gatehill.corebot.config.model.ActionConfig import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.Logger -import java.util.* +import java.util.Timer +import java.util.TimerTask import java.util.concurrent.CompletableFuture /** diff --git a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/action/driver/JobBaseActionDriver.kt b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/action/driver/JobBaseActionDriver.kt index 198956c2..e58458ce 100644 --- a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/action/driver/JobBaseActionDriver.kt +++ b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/action/driver/JobBaseActionDriver.kt @@ -26,7 +26,7 @@ abstract class JobBaseActionDriver @Inject constructor(private val jobTriggerSer } return true - } catch(e: Exception) { + } catch (e: Exception) { future.completeExceptionally(e) return false } diff --git a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/DisableJobTemplate.kt b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/DisableJobTemplate.kt index cbbf15cf..fc385f6a 100644 --- a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/DisableJobTemplate.kt +++ b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/DisableJobTemplate.kt @@ -2,7 +2,7 @@ package com.gatehill.corebot.chat.model.template import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.ConfigService -import java.util.* +import java.util.LinkedList import javax.inject.Inject /** diff --git a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/EnableJobTemplate.kt b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/EnableJobTemplate.kt index 2c1b9a91..dedbbee5 100644 --- a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/EnableJobTemplate.kt +++ b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/EnableJobTemplate.kt @@ -2,7 +2,7 @@ package com.gatehill.corebot.chat.model.template import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.ConfigService -import java.util.* +import java.util.LinkedList import javax.inject.Inject /** diff --git a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/TriggerJobTemplate.kt b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/TriggerJobTemplate.kt index 1680fbd6..51ac662f 100644 --- a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/TriggerJobTemplate.kt +++ b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/TriggerJobTemplate.kt @@ -3,7 +3,8 @@ package com.gatehill.corebot.chat.model.template import com.gatehill.corebot.chat.ChatGenerator import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.model.ActionConfig -import java.util.* +import java.util.LinkedList +import java.util.Queue /** * Triggers job execution. diff --git a/backends/deployment/rundeck/src/main/kotlin/com/gatehill/corebot/driver/rundeck/action/ExecutionStatusService.kt b/backends/deployment/rundeck/src/main/kotlin/com/gatehill/corebot/driver/rundeck/action/ExecutionStatusService.kt index 5718e131..80083797 100644 --- a/backends/deployment/rundeck/src/main/kotlin/com/gatehill/corebot/driver/rundeck/action/ExecutionStatusService.kt +++ b/backends/deployment/rundeck/src/main/kotlin/com/gatehill/corebot/driver/rundeck/action/ExecutionStatusService.kt @@ -8,7 +8,7 @@ import org.apache.logging.log4j.Logger import retrofit2.Call import retrofit2.Callback import retrofit2.Response -import java.util.* +import java.util.HashMap import java.util.concurrent.CompletableFuture import javax.inject.Inject diff --git a/backends/deployment/rundeck/src/main/kotlin/com/gatehill/corebot/driver/rundeck/action/RundeckActionDriver.kt b/backends/deployment/rundeck/src/main/kotlin/com/gatehill/corebot/driver/rundeck/action/RundeckActionDriver.kt index 88fa5396..f28a8531 100644 --- a/backends/deployment/rundeck/src/main/kotlin/com/gatehill/corebot/driver/rundeck/action/RundeckActionDriver.kt +++ b/backends/deployment/rundeck/src/main/kotlin/com/gatehill/corebot/driver/rundeck/action/RundeckActionDriver.kt @@ -10,7 +10,7 @@ import com.gatehill.corebot.chat.model.template.JobActionType import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.base.action.ApiClientBuilder import com.gatehill.corebot.driver.rundeck.config.DriverSettings -import java.util.* +import java.util.HashMap import java.util.concurrent.CompletableFuture import javax.inject.Inject @@ -42,7 +42,7 @@ class RundeckActionDriverImpl @Inject constructor(triggerJobService: RundeckJobT } return true - } catch(e: Exception) { + } catch (e: Exception) { future.completeExceptionally(e) return false } diff --git a/backends/deployment/rundeck/src/main/kotlin/com/gatehill/corebot/driver/rundeck/action/RundeckApi.kt b/backends/deployment/rundeck/src/main/kotlin/com/gatehill/corebot/driver/rundeck/action/RundeckApi.kt index 603ea215..1a0191bf 100644 --- a/backends/deployment/rundeck/src/main/kotlin/com/gatehill/corebot/driver/rundeck/action/RundeckApi.kt +++ b/backends/deployment/rundeck/src/main/kotlin/com/gatehill/corebot/driver/rundeck/action/RundeckApi.kt @@ -5,8 +5,12 @@ import com.gatehill.corebot.driver.rundeck.model.ExecutionInfo import com.gatehill.corebot.driver.rundeck.model.ExecutionOptions import com.gatehill.corebot.driver.rundeck.model.ExecutionOutput import retrofit2.Call -import retrofit2.http.* -import java.util.* +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.POST +import retrofit2.http.Path +import java.util.HashMap /** * Models the Rundeck REST API. @@ -31,5 +35,5 @@ interface RundeckApi { @GET("/api/14/execution/{executionId}/output") fun fetchExecutionOutput(@Header("Accept") accept: String = "application/json", - @Path("executionId") executionId: String): Call + @Path("executionId") executionId: String): Call } diff --git a/backends/deployment/rundeck/src/main/kotlin/com/gatehill/corebot/driver/rundeck/action/RundeckJobTriggerService.kt b/backends/deployment/rundeck/src/main/kotlin/com/gatehill/corebot/driver/rundeck/action/RundeckJobTriggerService.kt index e8cfc22c..32988cc6 100644 --- a/backends/deployment/rundeck/src/main/kotlin/com/gatehill/corebot/driver/rundeck/action/RundeckJobTriggerService.kt +++ b/backends/deployment/rundeck/src/main/kotlin/com/gatehill/corebot/driver/rundeck/action/RundeckJobTriggerService.kt @@ -38,10 +38,11 @@ class RundeckJobTriggerService @Inject constructor(private val actionDriver: Run try { call = actionDriver.buildApiClient().runJob( jobId = action.jobId, - executionOptions = ExecutionOptions(argString = buildArgString(args), - asUser = if (action.runAsTriggerUser) trigger.username else "") + executionOptions = ExecutionOptions( + argString = buildArgString(args), + asUser = if (action.runAsTriggerUser) trigger.username else "") ) - } catch(e: Exception) { + } catch (e: Exception) { future.completeExceptionally(e) return } @@ -84,7 +85,7 @@ class RundeckJobTriggerService @Inject constructor(private val actionDriver: Run argString.append(" ") if (it.value.contains(" ")) { argString.append("\"").append(it.value).append("\"") - }else{ + } else { argString.append(it.value) } } diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt index 695e360d..c1fdaf33 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt @@ -28,7 +28,7 @@ class ItemsActionDriverImpl @Inject constructor(private val claimService: ClaimS "Action type $actionType is not supported by ${javaClass.canonicalName}") } } - } catch(e: Exception) { + } catch (e: Exception) { future.completeExceptionally(e) } return future diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemAsUserTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemAsUserTemplate.kt index 38ea45c4..0004626c 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemAsUserTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemAsUserTemplate.kt @@ -2,9 +2,8 @@ package com.gatehill.corebot.driver.items.chat.model.template import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.ConfigService -import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.items.action.model.ItemsActionType -import java.util.* +import java.util.LinkedList import java.util.regex.Pattern import javax.inject.Inject diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt index 292b5492..dc9dc8e0 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt @@ -4,7 +4,7 @@ import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.items.action.model.ItemsActionType -import java.util.* +import java.util.LinkedList import java.util.regex.Pattern import javax.inject.Inject diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictItemTemplate.kt index e8c0e9c9..a2407597 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictItemTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictItemTemplate.kt @@ -4,7 +4,7 @@ import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.items.action.model.ItemsActionType -import java.util.* +import java.util.LinkedList import javax.inject.Inject /** diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/ReturnItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/ReturnItemTemplate.kt index 44e8818a..ee689602 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/ReturnItemTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/ReturnItemTemplate.kt @@ -4,7 +4,7 @@ import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.items.action.model.ItemsActionType -import java.util.* +import java.util.LinkedList import javax.inject.Inject /** diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusAllTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusAllTemplate.kt index 283b64bd..94186d21 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusAllTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusAllTemplate.kt @@ -6,7 +6,7 @@ import com.gatehill.corebot.chat.model.template.SystemActionTemplate import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.items.action.model.ItemsActionType import com.gatehill.corebot.driver.items.service.ClaimService -import java.util.* +import java.util.LinkedList import javax.inject.Inject /** diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusItemTemplate.kt index 346a737a..1f117827 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusItemTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusItemTemplate.kt @@ -4,7 +4,7 @@ import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.items.action.model.ItemsActionType -import java.util.* +import java.util.LinkedList import javax.inject.Inject /** diff --git a/bots/common/src/main/kotlin/com/gatehill/corebot/Bot.kt b/bots/common/src/main/kotlin/com/gatehill/corebot/Bot.kt index f3589781..81647ff5 100644 --- a/bots/common/src/main/kotlin/com/gatehill/corebot/Bot.kt +++ b/bots/common/src/main/kotlin/com/gatehill/corebot/Bot.kt @@ -1,6 +1,10 @@ package com.gatehill.corebot -import com.gatehill.corebot.action.* +import com.gatehill.corebot.action.ActionOutcomeService +import com.gatehill.corebot.action.ActionOutcomeServiceImpl +import com.gatehill.corebot.action.ActionPerformService +import com.gatehill.corebot.action.DirectActionPerformServiceImpl +import com.gatehill.corebot.action.LockService import com.gatehill.corebot.action.driver.ActionDriverFactory import com.gatehill.corebot.chat.ChatGenerator import com.gatehill.corebot.chat.ChatService diff --git a/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt b/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt index b91066a9..26937b35 100644 --- a/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt +++ b/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt @@ -2,7 +2,14 @@ package com.gatehill.corebot import com.gatehill.corebot.action.driver.ActionDriverFactory import com.gatehill.corebot.chat.TemplateService -import com.gatehill.corebot.chat.model.template.* +import com.gatehill.corebot.chat.model.template.DisableJobTemplate +import com.gatehill.corebot.chat.model.template.EnableJobTemplate +import com.gatehill.corebot.chat.model.template.LockActionTemplate +import com.gatehill.corebot.chat.model.template.LockOptionTemplate +import com.gatehill.corebot.chat.model.template.ShowHelpTemplate +import com.gatehill.corebot.chat.model.template.StatusJobTemplate +import com.gatehill.corebot.chat.model.template.UnlockActionTemplate +import com.gatehill.corebot.chat.model.template.UnlockOptionTemplate import com.gatehill.corebot.driver.jenkins.action.JenkinsActionDriver import com.gatehill.corebot.driver.rundeck.action.RundeckActionDriver import javax.inject.Inject diff --git a/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt b/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt index 9acbd612..300d799f 100644 --- a/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt +++ b/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt @@ -4,7 +4,12 @@ import com.gatehill.corebot.action.driver.ActionDriverFactory import com.gatehill.corebot.chat.TemplateService import com.gatehill.corebot.chat.model.template.ShowHelpTemplate import com.gatehill.corebot.driver.items.action.ItemsActionDriverImpl -import com.gatehill.corebot.driver.items.chat.model.template.* +import com.gatehill.corebot.driver.items.chat.model.template.BorrowItemAsUserTemplate +import com.gatehill.corebot.driver.items.chat.model.template.BorrowItemTemplate +import com.gatehill.corebot.driver.items.chat.model.template.EvictItemTemplate +import com.gatehill.corebot.driver.items.chat.model.template.ReturnItemTemplate +import com.gatehill.corebot.driver.items.chat.model.template.StatusAllTemplate +import com.gatehill.corebot.driver.items.chat.model.template.StatusItemTemplate import javax.inject.Inject /** diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/driver/ActionDriverFactory.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/driver/ActionDriverFactory.kt index 674e0f9a..00e240c8 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/action/driver/ActionDriverFactory.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/driver/ActionDriverFactory.kt @@ -22,7 +22,7 @@ class ActionDriverFactory @Inject constructor(private val injector: Injector) { logger.trace("Loading driver $driverClass for action driver: $actionDriverName") return injector.getInstance(driverClass) - } catch(e: Exception) { + } catch (e: Exception) { throw UnsupportedOperationException("Action driver '$actionDriverName' not supported") } } diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/driver/BaseActionDriver.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/driver/BaseActionDriver.kt index 3cb7eb0a..4f4eef71 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/action/driver/BaseActionDriver.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/driver/BaseActionDriver.kt @@ -33,7 +33,7 @@ abstract class BaseActionDriver @Inject constructor(private val lockService: Loc "Action type $actionType is not supported by ${javaClass.canonicalName}") } } - } catch(e: Exception) { + } catch (e: Exception) { future.completeExceptionally(e) } return future diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt index 0c554cd0..9efdeac5 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt @@ -3,7 +3,7 @@ package com.gatehill.corebot.chat.model.template import com.gatehill.corebot.chat.model.action.Action import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.model.ActionConfig -import java.util.* +import java.util.Queue import java.util.regex.Pattern /** diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/CustomActionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/CustomActionTemplate.kt index c4240e1f..37eca2aa 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/CustomActionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/CustomActionTemplate.kt @@ -4,7 +4,7 @@ import com.gatehill.corebot.chat.model.action.Action import com.gatehill.corebot.chat.model.action.CustomAction import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.config.model.TransformType -import java.util.* +import java.util.HashMap /** * Represents a custom action. diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/LockActionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/LockActionTemplate.kt index ba847076..97c9781f 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/LockActionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/LockActionTemplate.kt @@ -3,7 +3,7 @@ package com.gatehill.corebot.chat.model.template import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.chat.model.action.CoreActionType import com.gatehill.corebot.config.ConfigService -import java.util.* +import java.util.LinkedList import javax.inject.Inject /** diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/LockOptionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/LockOptionTemplate.kt index d4f91425..e0198c5a 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/LockOptionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/LockOptionTemplate.kt @@ -4,7 +4,7 @@ import com.gatehill.corebot.chat.ChatGenerator import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.chat.model.action.CoreActionType import com.gatehill.corebot.config.ConfigService -import java.util.* +import java.util.LinkedList import javax.inject.Inject /** diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ShowHelpTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ShowHelpTemplate.kt index 09302869..f8755cdb 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ShowHelpTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ShowHelpTemplate.kt @@ -4,7 +4,7 @@ import com.gatehill.corebot.chat.ChatGenerator import com.gatehill.corebot.chat.TemplateService import com.gatehill.corebot.chat.model.action.CoreActionType import com.gatehill.corebot.config.model.ActionConfig -import java.util.* +import java.util.LinkedList import javax.inject.Inject /** diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/StatusJobTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/StatusJobTemplate.kt index ad7ce0b6..b7940348 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/StatusJobTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/StatusJobTemplate.kt @@ -3,7 +3,7 @@ package com.gatehill.corebot.chat.model.template import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.chat.model.action.CoreActionType import com.gatehill.corebot.config.ConfigService -import java.util.* +import java.util.LinkedList import javax.inject.Inject /** diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/UnlockActionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/UnlockActionTemplate.kt index 34f3ba1b..71209eb6 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/UnlockActionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/UnlockActionTemplate.kt @@ -3,7 +3,7 @@ package com.gatehill.corebot.chat.model.template import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.chat.model.action.CoreActionType import com.gatehill.corebot.config.ConfigService -import java.util.* +import java.util.LinkedList import javax.inject.Inject /** diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/UnlockOptionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/UnlockOptionTemplate.kt index 1f3d8285..41bb9d80 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/UnlockOptionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/UnlockOptionTemplate.kt @@ -4,7 +4,7 @@ import com.gatehill.corebot.chat.ChatGenerator import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.chat.model.action.CoreActionType import com.gatehill.corebot.config.ConfigService -import java.util.* +import java.util.LinkedList import javax.inject.Inject /** diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/config/ConfigServiceImpl.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/config/ConfigServiceImpl.kt index d6186d8a..66ef4be7 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/config/ConfigServiceImpl.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/config/ConfigServiceImpl.kt @@ -9,7 +9,7 @@ import com.google.common.cache.CacheBuilder import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.Logger import java.io.InputStream -import java.util.* +import java.util.HashMap import java.util.concurrent.TimeUnit /** diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/util/Extensions.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/util/Extensions.kt index b4791c59..fabb6fef 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/util/Extensions.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/util/Extensions.kt @@ -5,7 +5,7 @@ import java.util.concurrent.CompletableFuture /** * Syntactic sugar for handling `exceptionally()` flow. */ -fun CompletableFuture<*>.onException(handler: (Throwable) -> Unit): Unit { +fun CompletableFuture<*>.onException(handler: (Throwable) -> Unit) { this.exceptionally { handler(it) null diff --git a/frontends/slack/src/main/kotlin/com/gatehill/corebot/SlackModule.kt b/frontends/slack/src/main/kotlin/com/gatehill/corebot/SlackModule.kt index f3d5ac58..4f0011b4 100644 --- a/frontends/slack/src/main/kotlin/com/gatehill/corebot/SlackModule.kt +++ b/frontends/slack/src/main/kotlin/com/gatehill/corebot/SlackModule.kt @@ -1,6 +1,10 @@ package com.gatehill.corebot -import com.gatehill.corebot.chat.* +import com.gatehill.corebot.chat.ChatService +import com.gatehill.corebot.chat.SessionService +import com.gatehill.corebot.chat.SlackChatServiceImpl +import com.gatehill.corebot.chat.SlackSessionService +import com.gatehill.corebot.chat.SlackSessionServiceImpl import com.google.inject.AbstractModule /** diff --git a/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt b/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt index 01d214c8..771fc28c 100644 --- a/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt +++ b/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt @@ -68,7 +68,7 @@ open class SlackChatServiceImpl @Inject constructor(private val sessionService: } } - } catch(e: Exception) { + } catch (e: Exception) { logger.error("Error parsing message event: $event", e) session.addReactionToMessage(event.channel, event.timeStamp, "x") printUsage(event) @@ -93,7 +93,7 @@ open class SlackChatServiceImpl @Inject constructor(private val sessionService: } } - } catch(e: IllegalStateException) { + } catch (e: IllegalStateException) { logger.warn("Unable to parse message: $splitCmd - ${e.message}") return null } From 0004476a02be7b6d0fddfa8585040d33aa06bedf Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Sat, 3 Jun 2017 22:09:38 +0100 Subject: [PATCH 13/36] Allows borrower to be displayed using their real name instead of their username. --- .../driver/items/config/ItemSettings.kt | 18 ++++++++++++++++++ .../driver/items/service/ClaimService.kt | 17 ++++++++++++----- .../gatehill/corebot/chat/SessionService.kt | 3 ++- .../corebot/chat/SlackSessionServiceImpl.kt | 5 ++++- 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/config/ItemSettings.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/config/ItemSettings.kt index 95c45e79..3cf01351 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/config/ItemSettings.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/config/ItemSettings.kt @@ -4,5 +4,23 @@ package com.gatehill.corebot.driver.items.config * Item driver settings. */ object ItemSettings { + /** + * Whether to show the status on every change. + */ val showStatusOnChange by lazy { System.getenv("ITEMS_SHOW_STATUS_ON_CHANGE")?.toBoolean() ?: false } + + /** + * How to display the owner. + */ + val ownerDisplayMode: OwnerDisplayMode by lazy { + System.getenv("ITEMS_OWNER_DISPLAY")?.let { OwnerDisplayMode.valueOf(it) } ?: OwnerDisplayMode.USERNAME + } +} + +/** + * How to display the owner. + */ +enum class OwnerDisplayMode { + USERNAME, + REAL_NAME } diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt index 79cf8943..8a1a6d07 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt @@ -8,6 +8,7 @@ import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.items.chat.model.template.BorrowItemAsUserTemplate import com.gatehill.corebot.driver.items.chat.model.template.BorrowItemTemplate +import com.gatehill.corebot.driver.items.config.OwnerDisplayMode import com.gatehill.corebot.driver.items.config.ItemSettings import com.gatehill.corebot.store.DataStore import com.gatehill.corebot.store.partition @@ -139,19 +140,17 @@ class ClaimService @Inject constructor(private val configService: ConfigService, claims.isEmpty() -> "No one is borrowing *$itemName*." claims.size == 1 -> { val claim = claims.first() - val ownerUsername = sessionService.lookupUser(claim.owner) val subItemDescription = claim.subItem?.let { if (it.isNotBlank()) " ($it)" else null } ?: "" - "There is a single borrower of *$itemName*$subItemDescription: $ownerUsername - ${claim.reason}" + "There is a single borrower of *$itemName*$subItemDescription: ${describeOwner(claim.owner)} - ${claim.reason}" } else -> { val claimsList = StringBuilder() claims.forEach { (owner, reason, subItem) -> - val ownerUsername = sessionService.lookupUser(owner) val subItemDescription = subItem?.let { if (it.isNotBlank()) "$it: " else null } ?: "" - - claimsList.append("\n• $subItemDescription$ownerUsername - $reason") + claimsList.append("\n• $subItemDescription${describeOwner(owner)} - $reason") } + "There are ${claims.size} borrowers of *$itemName*:$claimsList" } } @@ -160,6 +159,14 @@ class ClaimService @Inject constructor(private val configService: ConfigService, } } + /** + * Describe the owner, based on the display mode. + */ + private fun describeOwner(owner: String) = when (ItemSettings.ownerDisplayMode) { + OwnerDisplayMode.USERNAME -> sessionService.lookupUsername(owner) + OwnerDisplayMode.REAL_NAME -> sessionService.lookupUserRealName(owner) + } + fun describeAllItemStatus(): String { val status = StringBuilder() diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/SessionService.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/SessionService.kt index ad4b15bf..9228e816 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/SessionService.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/SessionService.kt @@ -9,5 +9,6 @@ interface SessionService { val botUsername: String fun sendMessage(triggerContext: TriggerContext, message: String) fun addReaction(triggerContext: TriggerContext, emojiCode: String) - fun lookupUser(userId: String): String + fun lookupUsername(userId: String): String + fun lookupUserRealName(userId: String): String } diff --git a/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackSessionServiceImpl.kt b/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackSessionServiceImpl.kt index 97da7821..e0d2a227 100644 --- a/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackSessionServiceImpl.kt +++ b/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackSessionServiceImpl.kt @@ -85,6 +85,9 @@ open class SlackSessionServiceImpl @Inject constructor(configService: ConfigServ session.addReactionToMessage(session.findChannelById(triggerContext.channelId), triggerContext.messageTimestamp, emojiCode) } - override fun lookupUser(userId: String): String = + override fun lookupUsername(userId: String): String = session.findUserById(userId).userName + + override fun lookupUserRealName(userId: String): String = + session.findUserById(userId).realName } From baba57663fd919e305e5a9476b3325d26ba0d2a8 Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Sat, 3 Jun 2017 22:59:46 +0100 Subject: [PATCH 14/36] Moves ChatService interface into engine. --- .../src/main/kotlin/com/gatehill/corebot/chat/ChatService.kt | 0 frontends/slack/build.gradle | 1 - 2 files changed, 1 deletion(-) rename {bots/common => core/engine}/src/main/kotlin/com/gatehill/corebot/chat/ChatService.kt (100%) diff --git a/bots/common/src/main/kotlin/com/gatehill/corebot/chat/ChatService.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/ChatService.kt similarity index 100% rename from bots/common/src/main/kotlin/com/gatehill/corebot/chat/ChatService.kt rename to core/engine/src/main/kotlin/com/gatehill/corebot/chat/ChatService.kt diff --git a/frontends/slack/build.gradle b/frontends/slack/build.gradle index 47df03cc..105ef7f1 100644 --- a/frontends/slack/build.gradle +++ b/frontends/slack/build.gradle @@ -15,7 +15,6 @@ dependencies { compile "org.glassfish.tyrus.bundles:tyrus-standalone-client:$version_tyrus" compile project(':core:core-engine') - compile project(':bots:bots-common') } publishing { From f300f0fa6b3a10435330c9ab035a0da7f34d1a4f Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Sat, 3 Jun 2017 23:29:38 +0100 Subject: [PATCH 15/36] Moves LockService binding into bot. Adds function to stop chat service from listening. --- .../src/main/kotlin/com/gatehill/corebot/Bot.kt | 6 ++++-- .../main/kotlin/com/gatehill/corebot/Main.kt | 3 +++ .../main/kotlin/com/gatehill/corebot/Main.kt | 2 +- .../com/gatehill/corebot/chat/ChatService.kt | 1 + .../gatehill/corebot/chat/TemplateService.kt | 7 +++---- .../corebot/chat/SlackChatServiceImpl.kt | 17 ++++++++++++----- 6 files changed, 24 insertions(+), 12 deletions(-) diff --git a/bots/common/src/main/kotlin/com/gatehill/corebot/Bot.kt b/bots/common/src/main/kotlin/com/gatehill/corebot/Bot.kt index 81647ff5..e80d3c74 100644 --- a/bots/common/src/main/kotlin/com/gatehill/corebot/Bot.kt +++ b/bots/common/src/main/kotlin/com/gatehill/corebot/Bot.kt @@ -4,7 +4,6 @@ import com.gatehill.corebot.action.ActionOutcomeService import com.gatehill.corebot.action.ActionOutcomeServiceImpl import com.gatehill.corebot.action.ActionPerformService import com.gatehill.corebot.action.DirectActionPerformServiceImpl -import com.gatehill.corebot.action.LockService import com.gatehill.corebot.action.driver.ActionDriverFactory import com.gatehill.corebot.chat.ChatGenerator import com.gatehill.corebot.chat.ChatService @@ -30,6 +29,10 @@ class Bot @Inject constructor(private val chatService: ChatService) { chatService.listenForEvents() } + fun stop() { + chatService.stopListening() + } + /** * Constructs `Bot` instances. */ @@ -49,7 +52,6 @@ class Bot @Inject constructor(private val chatService: ChatService) { override fun configure() { // utility bind(ConfigService::class.java).to(ConfigServiceImpl::class.java).asSingleton() - bind(LockService::class.java).asSingleton() bind(AuthorisationService::class.java).asSingleton() // chat diff --git a/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Main.kt b/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Main.kt index 610b5378..a158257f 100644 --- a/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Main.kt +++ b/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Main.kt @@ -1,5 +1,6 @@ package com.gatehill.corebot +import com.gatehill.corebot.action.LockService import com.gatehill.corebot.chat.ActionTemplateConverter import com.gatehill.corebot.deploy.TriggerActionTemplateConverter import com.gatehill.corebot.driver.jenkins.JenkinsDriverModule @@ -14,8 +15,10 @@ fun main(args: Array) { private class DeployBotModule : AbstractModule() { override fun configure() { bind(Bootstrap::class.java).asEagerSingleton() + bind(LockService::class.java).asSingleton() bind(ActionTemplateConverter::class.java).to(TriggerActionTemplateConverter::class.java).asSingleton() + // data stores install(DataStoreModule("lockStore")) // drivers diff --git a/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Main.kt b/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Main.kt index 623f741c..1b8344b3 100644 --- a/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Main.kt +++ b/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Main.kt @@ -15,7 +15,7 @@ private class ItemsBotModule : AbstractModule() { bind(Bootstrap::class.java).asEagerSingleton() bind(ActionTemplateConverter::class.java).to(NoOpActionTemplateConverter::class.java).asSingleton() - install(DataStoreModule("lockStore")) + // data stores install(DataStoreModule("itemStore")) // drivers diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/ChatService.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/ChatService.kt index 88e6080d..a00f4122 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/ChatService.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/ChatService.kt @@ -7,4 +7,5 @@ package com.gatehill.corebot.chat */ interface ChatService { fun listenForEvents() + fun stopListening() } diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt index 459a050d..3bd8cb1b 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt @@ -28,13 +28,12 @@ class TemplateService @Inject constructor(private val injector: Injector, /** * Find the templates that match the specified command. + * + * @param commandOnly - the command, excluding any initial bot reference */ - fun findSatisfiedTemplates(splitCmd: List): Collection { + fun findSatisfiedTemplates(commandOnly: List): Collection { val candidates = fetchCandidates() - // skip element 0, which contains the bot's username - val commandOnly = splitCmd.subList(1, splitCmd.size) - // include those satisfying templates return mutableSetOf().apply { addAll(filterSimpleTemplates(commandOnly, candidates.filter { null == it.templateRegex })) diff --git a/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt b/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt index 771fc28c..71cf451e 100644 --- a/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt +++ b/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt @@ -37,6 +37,10 @@ open class SlackChatServiceImpl @Inject constructor(private val sessionService: messagePostedListeners.forEach { sessionService.session.addMessagePostedListener(it) } } + override fun stopListening() { + sessionService.session.disconnect() + } + /** * Allow subclasses to hook into Slack events. */ @@ -56,7 +60,10 @@ open class SlackChatServiceImpl @Inject constructor(private val sessionService: // indicate busy... session.addReactionToMessage(event.channel, event.timeStamp, "hourglass_flowing_sand") - parseMessage(splitCmd)?.let { parsed -> + // skip element 0, which contains the bot's username + val commandOnly = splitCmd.subList(1, splitCmd.size) + + parseMessage(commandOnly)?.let { parsed -> logger.info("Handling command '$messageContent' from ${event.sender.userName}") parsed.groupStartMessage?.let { session.sendMessage(event.channel, it) } parsed.actions.forEach { action -> handleAction(session, event, action, parsed) } @@ -79,9 +86,9 @@ open class SlackChatServiceImpl @Inject constructor(private val sessionService: /** * Determine the Action to perform based on the provided command. */ - private fun parseMessage(splitCmd: List): ActionWrapper? { + private fun parseMessage(commandOnly: List): ActionWrapper? { try { - templateService.findSatisfiedTemplates(splitCmd).let { satisfied -> + templateService.findSatisfiedTemplates(commandOnly).let { satisfied -> if (satisfied.size == 1) { return with(satisfied.first()) { ActionWrapper(buildActions(), @@ -89,12 +96,12 @@ open class SlackChatServiceImpl @Inject constructor(private val sessionService: if (actionMessageMode == ActionMessageMode.GROUP) buildCompleteMessage() else null) } } else { - throw IllegalStateException("Could not find a unique matching action for command: $splitCmd") + throw IllegalStateException("Could not find a unique matching action for command: $commandOnly") } } } catch (e: IllegalStateException) { - logger.warn("Unable to parse message: $splitCmd - ${e.message}") + logger.warn("Unable to parse message: $commandOnly - ${e.message}") return null } } From 7a1970e47f054e45b7dbf1b19240f2834ed60267 Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Sat, 3 Jun 2017 23:56:46 +0100 Subject: [PATCH 16/36] Fixes status update to use trigger. --- .../com/gatehill/corebot/driver/items/service/ClaimService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt index 8a1a6d07..defe47ce 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt @@ -118,7 +118,7 @@ class ClaimService @Inject constructor(private val configService: ConfigService, future.complete(PerformActionResult(message)) if (ItemSettings.showStatusOnChange) { - sessionService.sendMessage(trigger.channelId, describeAllItemStatus()) + sessionService.sendMessage(trigger, describeAllItemStatus()) } } From 93cf0869c10c1b0a9969f202f1cce4883b2c86d9 Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Sun, 4 Jun 2017 00:20:37 +0100 Subject: [PATCH 17/36] Factors out common chat functionality into separate service. --- frontends/common/build.gradle | 33 +++++ .../gatehill/corebot/chat/MessageService.kt | 138 ++++++++++++++++++ frontends/slack/build.gradle | 2 + .../corebot/chat/SlackChatServiceImpl.kt | 134 +---------------- settings.gradle | 3 + 5 files changed, 183 insertions(+), 127 deletions(-) create mode 100644 frontends/common/build.gradle create mode 100644 frontends/common/src/main/kotlin/com/gatehill/corebot/chat/MessageService.kt diff --git a/frontends/common/build.gradle b/frontends/common/build.gradle new file mode 100644 index 00000000..4fd4def4 --- /dev/null +++ b/frontends/common/build.gradle @@ -0,0 +1,33 @@ +buildscript { + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$version_kotlin" + } +} + +apply plugin: "kotlin" + +dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib:$version_kotlin" + + compile project(':core:core-engine') + + testCompile "junit:junit:$version_junit" + testCompile "org.jetbrains.kotlin:kotlin-test-junit:$version_kotlin" +} + +publishing { + publications { + maven(MavenPublication) { + from components.java + repositories { + maven { + url mavenSnapshotRepository + credentials(AwsCredentials) { + accessKey awsAccessKey + secretKey awsSecretKey + } + } + } + } + } +} diff --git a/frontends/common/src/main/kotlin/com/gatehill/corebot/chat/MessageService.kt b/frontends/common/src/main/kotlin/com/gatehill/corebot/chat/MessageService.kt new file mode 100644 index 00000000..bd231de6 --- /dev/null +++ b/frontends/common/src/main/kotlin/com/gatehill/corebot/chat/MessageService.kt @@ -0,0 +1,138 @@ +package com.gatehill.corebot.chat + +import com.gatehill.corebot.action.ActionPerformService +import com.gatehill.corebot.action.model.PerformActionRequest +import com.gatehill.corebot.action.model.TriggerContext +import com.gatehill.corebot.chat.model.action.Action +import com.gatehill.corebot.chat.model.action.ActionWrapper +import com.gatehill.corebot.chat.model.action.CustomAction +import com.gatehill.corebot.chat.model.template.ActionMessageMode +import com.gatehill.corebot.config.ConfigService +import com.gatehill.corebot.config.Settings +import com.gatehill.corebot.security.AuthorisationService +import com.gatehill.corebot.util.onException +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.Logger +import javax.inject.Inject + +/** + * Handles conversations. + * + * @author Pete Cornish {@literal } + */ +class MessageService @Inject constructor(private val sessionService: SessionService, + private val templateService: TemplateService, + private val configService: ConfigService, + private val authorisationService: AuthorisationService, + private val actionPerformService: ActionPerformService) { + + private val logger: Logger = LogManager.getLogger(MessageService::class.java) + + /** + * Allow subclasses to hook into Slack events. + */ + fun handleMessage(commandOnly: List, trigger: TriggerContext) { + // indicate busy... + sessionService.addReaction(trigger, "hourglass_flowing_sand") + + parseMessage(commandOnly)?.let { parsed -> + logger.info("Handling command '$commandOnly' from ${trigger.username}") + parsed.groupStartMessage?.let { sessionService.sendMessage(trigger, it) } + parsed.actions.forEach { action -> handleAction(trigger, action, parsed) } + + } ?: run { + logger.warn("Ignored command '$commandOnly' from ${trigger.username}") + sessionService.addReaction(trigger, "question") + printUsage(trigger) + } + } + + /** + * Determine the Action to perform based on the provided command. + */ + private fun parseMessage(commandOnly: List): ActionWrapper? { + try { + templateService.findSatisfiedTemplates(commandOnly).let { satisfied -> + if (satisfied.size == 1) { + return with(satisfied.first()) { + ActionWrapper(buildActions(), + if (actionMessageMode == ActionMessageMode.GROUP) buildStartMessage() else null, + if (actionMessageMode == ActionMessageMode.GROUP) buildCompleteMessage() else null) + } + } else { + throw IllegalStateException("Could not find a unique matching action for command: $commandOnly") + } + } + + } catch (e: IllegalStateException) { + logger.warn("Unable to parse message: $commandOnly - ${e.message}") + return null + } + } + + /** + * Post a message with usage information. + */ + fun printUsage(trigger: TriggerContext) { + val msg = StringBuilder() + + if (configService.actions().isEmpty()) { + msg.append("Oops :broken_heart: you don't have any actions configured - add some to _${Settings.actionConfigFile}_") + } else { + msg.append("Sorry, I didn't understand :confused: Try typing _@${sessionService.botUsername} help_ for examples.") + } + + sessionService.sendMessage(trigger, msg.toString()) + } + + /** + * Check if the action is permitted, and if so, carry it out. + */ + private fun handleAction(trigger: TriggerContext, action: Action, actionWrapper: ActionWrapper) { + logger.info("Handling action: $action") + + authorisationService.checkPermission(action, { permitted -> + if (permitted) { + // respond with acknowledgement + action.startMessage?.let { sessionService.sendMessage(trigger, it) } + + when (action) { + is CustomAction -> performCustomAction(trigger, action, actionWrapper) + else -> postSuccessfulReaction(trigger, true, actionWrapper) + } + + } else { + sessionService.addReaction(trigger, "no_entry") + sessionService.sendMessage(trigger, + "Sorry, <@${trigger.userId}>, you're not allowed to perform ${action.shortDescription}.") + } + }, trigger.username) + } + + /** + * Perform the custom action and add a reaction with the outcome. + */ + private fun performCustomAction(trigger: TriggerContext, action: CustomAction, actionWrapper: ActionWrapper) { + // schedule action execution + val request = PerformActionRequest.Builder.build(trigger, action.actionType, action.actionConfig, action.args) + + actionPerformService.perform(request).thenAccept { (message, finalResult) -> + postSuccessfulReaction(trigger, finalResult, actionWrapper) + message?.let { sessionService.sendMessage(trigger, it) } + + }.onException { ex -> + logger.error("Error performing custom action $action", ex) + + sessionService.addReaction(trigger, "x") + sessionService.sendMessage(trigger, + "Hmm, something went wrong :face_with_head_bandage:\r\n```${ex.message}```") + } + } + + private fun postSuccessfulReaction(trigger: TriggerContext, finalResult: Boolean, actionWrapper: ActionWrapper) { + sessionService.addReaction(trigger, if (finalResult) "white_check_mark" else "ok") + + if (++actionWrapper.successful == actionWrapper.actions.size) + actionWrapper.groupCompleteMessage?.let { sessionService.sendMessage(trigger, it) } + } +} diff --git a/frontends/slack/build.gradle b/frontends/slack/build.gradle index 105ef7f1..d7346a8b 100644 --- a/frontends/slack/build.gradle +++ b/frontends/slack/build.gradle @@ -9,6 +9,8 @@ apply plugin: 'kotlin' dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$version_kotlin" + compile project(':frontends:frontends-common') + compile("com.github.ullink:simple-slack-api:$version_slackapi") { exclude module: 'logback-classic' } diff --git a/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt b/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt index 71cf451e..ab4c0e40 100644 --- a/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt +++ b/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt @@ -1,20 +1,8 @@ package com.gatehill.corebot.chat -import com.gatehill.corebot.action.ActionPerformService -import com.gatehill.corebot.action.model.PerformActionRequest import com.gatehill.corebot.action.model.TriggerContext -import com.gatehill.corebot.chat.model.action.Action -import com.gatehill.corebot.chat.model.action.ActionWrapper -import com.gatehill.corebot.chat.model.action.CustomAction -import com.gatehill.corebot.chat.model.template.ActionMessageMode import com.gatehill.corebot.config.ChatSettings -import com.gatehill.corebot.config.ConfigService -import com.gatehill.corebot.config.Settings -import com.gatehill.corebot.security.AuthorisationService -import com.gatehill.corebot.util.onException import com.ullink.slack.simpleslackapi.SlackPersona -import com.ullink.slack.simpleslackapi.SlackSession -import com.ullink.slack.simpleslackapi.events.SlackMessagePosted import com.ullink.slack.simpleslackapi.listeners.SlackMessagePostedListener import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.Logger @@ -26,10 +14,7 @@ import javax.inject.Inject * @author Pete Cornish {@literal } */ open class SlackChatServiceImpl @Inject constructor(private val sessionService: SlackSessionService, - private val templateService: TemplateService, - private val configService: ConfigService, - private val authorisationService: AuthorisationService, - private val actionPerformService: ActionPerformService) : ChatService { + private val messageService: MessageService) : ChatService { private val logger: Logger = LogManager.getLogger(SlackChatServiceImpl::class.java) @@ -46,139 +31,34 @@ open class SlackChatServiceImpl @Inject constructor(private val sessionService: */ protected open val messagePostedListeners = listOf(SlackMessagePostedListener { event, session -> // filter out messages from other channels - if (!ChatSettings.chat.channelNames.map { channelName -> session.findChannelByName(channelName).id } + if (!ChatSettings.chat.channelNames + .map { channelName -> session.findChannelByName(channelName).id } .contains(event.channel.id)) return@SlackMessagePostedListener // ignore own messages if (session.sessionPersona().id == event.sender.id) return@SlackMessagePostedListener + val trigger = TriggerContext(event.channel.id, event.sender.id, event.sender.userName, event.timestamp, event.threadTimestamp) + try { val messageContent = event.messageContent.trim() val splitCmd = messageContent.split("\"?( |$)(?=(([^\"]*\"){2})*[^\"]*$)\"?".toRegex()).filterNot(String::isBlank) if (splitCmd.isNotEmpty() && isAddressedToBot(session.sessionPersona(), splitCmd[0])) { - // indicate busy... - session.addReactionToMessage(event.channel, event.timeStamp, "hourglass_flowing_sand") // skip element 0, which contains the bot's username val commandOnly = splitCmd.subList(1, splitCmd.size) - parseMessage(commandOnly)?.let { parsed -> - logger.info("Handling command '$messageContent' from ${event.sender.userName}") - parsed.groupStartMessage?.let { session.sendMessage(event.channel, it) } - parsed.actions.forEach { action -> handleAction(session, event, action, parsed) } - - } ?: run { - logger.warn("Ignored command '$messageContent' from ${event.sender.userName}") - session.addReactionToMessage(event.channel, event.timeStamp, "question") - printUsage(event) - } + messageService.handleMessage(commandOnly, trigger) } } catch (e: Exception) { logger.error("Error parsing message event: $event", e) session.addReactionToMessage(event.channel, event.timeStamp, "x") - printUsage(event) + messageService.printUsage(trigger) return@SlackMessagePostedListener } }) - - /** - * Determine the Action to perform based on the provided command. - */ - private fun parseMessage(commandOnly: List): ActionWrapper? { - try { - templateService.findSatisfiedTemplates(commandOnly).let { satisfied -> - if (satisfied.size == 1) { - return with(satisfied.first()) { - ActionWrapper(buildActions(), - if (actionMessageMode == ActionMessageMode.GROUP) buildStartMessage() else null, - if (actionMessageMode == ActionMessageMode.GROUP) buildCompleteMessage() else null) - } - } else { - throw IllegalStateException("Could not find a unique matching action for command: $commandOnly") - } - } - - } catch (e: IllegalStateException) { - logger.warn("Unable to parse message: $commandOnly - ${e.message}") - return null - } - } - - /** - * Post a message with usage information. - */ - private fun printUsage(event: SlackMessagePosted) { - val msg = StringBuilder() - - if (configService.actions().isEmpty()) { - msg.append("Oops :broken_heart: you don't have any actions configured - add some to _${Settings.actionConfigFile}_") - } else { - msg.append("Sorry, I didn't understand :confused: Try typing _@${sessionService.botUsername} help_ for examples.") - } - - sessionService.sendMessage(event, msg.toString()) - } - - /** - * Check if the action is permitted, and if so, carry it out. - */ - private fun handleAction(session: SlackSession, event: SlackMessagePosted, action: Action, - actionWrapper: ActionWrapper) { - - logger.info("Handling action: $action") - - authorisationService.checkPermission(action, { permitted -> - if (permitted) { - // respond with acknowledgement - action.startMessage?.let { sessionService.sendMessage(event, it) } - - when (action) { - is CustomAction -> performCustomAction(session, event, action, actionWrapper) - else -> postSuccessfulReaction(session, event, true, actionWrapper) - } - - } else { - session.addReactionToMessage(event.channel, event.timeStamp, "no_entry") - sessionService.sendMessage(event, - "Sorry, <@${event.sender.id}>, you're not allowed to perform ${action.shortDescription}.") - } - }, event.sender.userName) - } - - /** - * Perform the custom action and add a reaction with the outcome. - */ - private fun performCustomAction(session: SlackSession, event: SlackMessagePosted, action: CustomAction, - actionWrapper: ActionWrapper) { - - val trigger = TriggerContext(event.channel.id, event.sender.id, event.sender.userName, event.timestamp, event.threadTimestamp) - - // schedule action execution - val request = PerformActionRequest.Builder.build(trigger, action.actionType, action.actionConfig, action.args) - - actionPerformService.perform(request).thenAccept { (message, finalResult) -> - postSuccessfulReaction(session, event, finalResult, actionWrapper) - message?.let { sessionService.sendMessage(event, it) } - - }.onException { ex -> - logger.error("Error performing custom action $action", ex) - - session.addReactionToMessage(event.channel, event.timeStamp, "x") - sessionService.sendMessage(event, - "Hmm, something went wrong :face_with_head_bandage:\r\n```${ex.message}```") - } - } - - private fun postSuccessfulReaction(session: SlackSession, event: SlackMessagePosted, finalResult: Boolean, - actionWrapper: ActionWrapper) { - - session.addReactionToMessage(event.channel, event.timeStamp, if (finalResult) "white_check_mark" else "ok") - - if (++actionWrapper.successful == actionWrapper.actions.size) - actionWrapper.groupCompleteMessage?.let { sessionService.sendMessage(event, it) } - } } /** diff --git a/settings.gradle b/settings.gradle index e267cab5..c4a2026e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -30,6 +30,9 @@ project(':core:engine').name = 'core-engine' include ':core:rest' project(':core:rest').name = 'core-rest' +include ':frontends:common' +project(':frontends:common').name = 'frontends-common' + include ':frontends:slack' project(':frontends:slack').name = 'frontends-slack' From 57da531074e2ef4bfe77be69ac321d299a0e6f7d Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Mon, 5 Jun 2017 23:43:57 +0100 Subject: [PATCH 18/36] Tidy up. --- backends/items/build.gradle | 5 +---- bots/slack-items/build.gradle | 7 ++----- build.gradle | 2 +- frontends/common/build.gradle | 5 +---- 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/backends/items/build.gradle b/backends/items/build.gradle index 4fd4def4..dbfe7689 100644 --- a/backends/items/build.gradle +++ b/backends/items/build.gradle @@ -4,15 +4,12 @@ buildscript { } } -apply plugin: "kotlin" +apply plugin: 'kotlin' dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$version_kotlin" compile project(':core:core-engine') - - testCompile "junit:junit:$version_junit" - testCompile "org.jetbrains.kotlin:kotlin-test-junit:$version_kotlin" } publishing { diff --git a/bots/slack-items/build.gradle b/bots/slack-items/build.gradle index 4a8ed8b1..801804d9 100644 --- a/bots/slack-items/build.gradle +++ b/bots/slack-items/build.gradle @@ -4,8 +4,8 @@ buildscript { } } -apply plugin: "kotlin" -apply plugin: "application" +apply plugin: 'kotlin' +apply plugin: 'application' dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$version_kotlin" @@ -14,9 +14,6 @@ dependencies { compile project(':backends:backends-items') compile project(':stores:stores-redis') compile project(':frontends:frontends-slack') - - testCompile "junit:junit:$version_junit" - testCompile "org.jetbrains.kotlin:kotlin-test-junit:$version_kotlin" } mainClassName = 'com.gatehill.corebot.MainKt' diff --git a/build.gradle b/build.gradle index ecc70850..ea43ee76 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ allprojects { - version = '0.8.2-SNAPSHOT' + version = '0.9.0-SNAPSHOT' group = 'com.gatehill.corebot' ext.mavenSnapshotRepository = 's3://gatehillsoftware-maven/snapshots' diff --git a/frontends/common/build.gradle b/frontends/common/build.gradle index 4fd4def4..dbfe7689 100644 --- a/frontends/common/build.gradle +++ b/frontends/common/build.gradle @@ -4,15 +4,12 @@ buildscript { } } -apply plugin: "kotlin" +apply plugin: 'kotlin' dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$version_kotlin" compile project(':core:core-engine') - - testCompile "junit:junit:$version_junit" - testCompile "org.jetbrains.kotlin:kotlin-test-junit:$version_kotlin" } publishing { From 2c6da307fad72e82f8469042835acd6a98c3a799 Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Tue, 6 Jun 2017 17:42:20 +0100 Subject: [PATCH 19/36] Adds greeting to all item status response. --- .../gatehill/corebot/driver/items/service/ClaimService.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt index defe47ce..38f6d5df 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt @@ -3,6 +3,7 @@ package com.gatehill.corebot.driver.items.service import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.gatehill.corebot.action.model.PerformActionResult import com.gatehill.corebot.action.model.TriggerContext +import com.gatehill.corebot.chat.ChatGenerator import com.gatehill.corebot.chat.SessionService import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig @@ -23,6 +24,7 @@ import javax.inject.Named */ class ClaimService @Inject constructor(private val configService: ConfigService, private val sessionService: SessionService, + private val chatGenerator: ChatGenerator, @Named("itemStore") private val dataStore: DataStore) { /** @@ -118,7 +120,7 @@ class ClaimService @Inject constructor(private val configService: ConfigService, future.complete(PerformActionResult(message)) if (ItemSettings.showStatusOnChange) { - sessionService.sendMessage(trigger, describeAllItemStatus()) + sessionService.sendMessage(trigger, buildAllItemStatus()) } } @@ -167,7 +169,9 @@ class ClaimService @Inject constructor(private val configService: ConfigService, OwnerDisplayMode.REAL_NAME -> sessionService.lookupUserRealName(owner) } - fun describeAllItemStatus(): String { + fun describeAllItemStatus() = "${chatGenerator.greeting()} :simple_smile: ${buildAllItemStatus()}" + + private fun buildAllItemStatus(): String { val status = StringBuilder() configService.actions().keys.forEach { itemName -> From fb97e3bcddb732300169d73f7873bfc14fd7267a Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Thu, 8 Jun 2017 11:11:37 +0100 Subject: [PATCH 20/36] Indents item borrower list. Adds optional status update after reply message. --- .../items/action/ItemsActionDriverImpl.kt | 8 ++-- .../driver/items/service/ClaimService.kt | 39 +++++++++---------- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt index c1fdaf33..bf27a2db 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt @@ -18,10 +18,10 @@ class ItemsActionDriverImpl @Inject constructor(private val claimService: ClaimS val future = CompletableFuture() try { when (actionType) { - ItemsActionType.ITEM_BORROW -> claimService.claimItem(trigger, future, action, args, trigger.userId) - ItemsActionType.ITEM_BORROW_AS_USER -> claimService.claimItem(trigger, future, action, args, trigger.userId) - ItemsActionType.ITEM_RETURN -> claimService.releaseItem(trigger, future, action, trigger.userId) - ItemsActionType.ITEM_EVICT -> claimService.evictItemClaims(trigger, future, action, trigger.userId) + ItemsActionType.ITEM_BORROW -> claimService.claimItem(future, action, args, trigger.userId) + ItemsActionType.ITEM_BORROW_AS_USER -> claimService.claimItem(future, action, args, trigger.userId) + ItemsActionType.ITEM_RETURN -> claimService.releaseItem(future, action, trigger.userId) + ItemsActionType.ITEM_EVICT -> claimService.evictItemClaims(future, action, trigger.userId) ItemsActionType.ITEM_STATUS -> claimService.checkItemStatus(future, action) else -> { throw UnsupportedOperationException( diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt index 38f6d5df..f3866fe1 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt @@ -2,7 +2,6 @@ package com.gatehill.corebot.driver.items.service import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.gatehill.corebot.action.model.PerformActionResult -import com.gatehill.corebot.action.model.TriggerContext import com.gatehill.corebot.chat.ChatGenerator import com.gatehill.corebot.chat.SessionService import com.gatehill.corebot.config.ConfigService @@ -40,10 +39,9 @@ class ClaimService @Inject constructor(private val configService: ConfigService, private val itemClaims get() = dataStore.partition("itemClaims") - fun claimItem(trigger: TriggerContext, future: CompletableFuture, action: ActionConfig, args: Map, - triggerMessageSenderId: String) { - + fun claimItem(future: CompletableFuture, action: ActionConfig, args: Map, triggerMessageSenderId: String) { val itemName = action.name + synchronized(itemName) { checkItemClaims(action) { claims -> val reason: String = args[BorrowItemTemplate.reasonPlaceholder]!! @@ -63,14 +61,12 @@ class ClaimService @Inject constructor(private val configService: ConfigService, }) val borrowerDescription = if (borrower == triggerMessageSenderId) " <@$borrower>, you've" else ", <@$borrower> has" - completeWithStatusCheck(trigger, future, "OK$borrowerDescription borrowed :lock: *$itemName* for _${reason}_.") + completeWithStatusCheck(future, "OK$borrowerDescription borrowed :lock: *$itemName* for _${reason}_.") } } } - fun releaseItem(trigger: TriggerContext, future: CompletableFuture, action: ActionConfig, - triggerMessageSenderId: String) { - + fun releaseItem(future: CompletableFuture, action: ActionConfig, triggerMessageSenderId: String) { val itemName = action.name synchronized(itemName) { @@ -81,19 +77,18 @@ class ClaimService @Inject constructor(private val configService: ConfigService, itemClaims[itemName] = ItemClaims(claims.toMutableList().apply { remove(claim) }) - completeWithStatusCheck(trigger, future, "OK <@$triggerMessageSenderId>, you've returned *$itemName*.") + completeWithStatusCheck(future, "OK <@$triggerMessageSenderId>, you've returned *$itemName*.") } ?: run { - completeWithStatusCheck(trigger, future, "BTW <@$triggerMessageSenderId>, you weren't borrowing *$itemName* :wink:") + completeWithStatusCheck(future, "BTW <@$triggerMessageSenderId>, you weren't borrowing *$itemName* :wink:") } } } } - fun evictItemClaims(trigger: TriggerContext, future: CompletableFuture, action: ActionConfig, - triggerMessageSenderId: String) { - + fun evictItemClaims(future: CompletableFuture, action: ActionConfig, triggerMessageSenderId: String) { val itemName = action.name + synchronized(itemName) { checkItemClaims(action) { claims -> itemClaims.remove(itemName) @@ -108,20 +103,22 @@ class ClaimService @Inject constructor(private val configService: ConfigService, "\n_(FYI ${nonSelfBorrowers.map { "<@$it>" }.joinToString()})_" } - completeWithStatusCheck(trigger, future, "OK <@$triggerMessageSenderId>, I've :unlock: evicted all borrowers (${claims.size}) from *$itemName*.$previousBorrowers") + completeWithStatusCheck(future, "OK <@$triggerMessageSenderId>, I've :unlock: evicted all borrowers (${claims.size}) from *$itemName*.$previousBorrowers") } } } /** - * Follow up with status. + * Complete with the result message, optionally printing the current status. */ - private fun completeWithStatusCheck(trigger: TriggerContext, future: CompletableFuture, message: String) { - future.complete(PerformActionResult(message)) - - if (ItemSettings.showStatusOnChange) { - sessionService.sendMessage(trigger, buildAllItemStatus()) + private fun completeWithStatusCheck(future: CompletableFuture, message: String) { + val finalMessage = if (ItemSettings.showStatusOnChange) { + "$message ${buildAllItemStatus()}" + } else { + message } + + future.complete(PerformActionResult(finalMessage)) } fun checkItemClaims(itemName: String, callback: (List) -> Unit) = @@ -150,7 +147,7 @@ class ClaimService @Inject constructor(private val configService: ConfigService, val claimsList = StringBuilder() claims.forEach { (owner, reason, subItem) -> val subItemDescription = subItem?.let { if (it.isNotBlank()) "$it: " else null } ?: "" - claimsList.append("\n• $subItemDescription${describeOwner(owner)} - $reason") + claimsList.append("\n • $subItemDescription${describeOwner(owner)} - $reason") } "There are ${claims.size} borrowers of *$itemName*:$claimsList" From 83fd36e8e69a81b1c41dbee44e6f9ac02177d69b Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Thu, 8 Jun 2017 13:16:53 +0100 Subject: [PATCH 21/36] Adds the ability to evict a borrower from an item. --- .../items/action/ItemsActionDriverImpl.kt | 1 + .../items/action/model/ItemsActionType.kt | 1 + .../template/BorrowItemAsUserTemplate.kt | 6 +-- .../template/EvictUserFromItemTemplate.kt | 22 ++++++++ .../driver/items/service/ClaimService.kt | 50 +++++++++++++++---- .../kotlin/com/gatehill/corebot/Bootstrap.kt | 2 + .../src/main/resources/default-security.yml | 1 + .../gatehill/corebot/action/LockService.kt | 9 ++-- .../gatehill/corebot/chat/ChatGenerator.kt | 3 +- .../src/main/resources/default-chat.yml | 5 ++ 10 files changed, 82 insertions(+), 18 deletions(-) create mode 100644 backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictUserFromItemTemplate.kt diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt index bf27a2db..4ec79054 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt @@ -22,6 +22,7 @@ class ItemsActionDriverImpl @Inject constructor(private val claimService: ClaimS ItemsActionType.ITEM_BORROW_AS_USER -> claimService.claimItem(future, action, args, trigger.userId) ItemsActionType.ITEM_RETURN -> claimService.releaseItem(future, action, trigger.userId) ItemsActionType.ITEM_EVICT -> claimService.evictItemClaims(future, action, trigger.userId) + ItemsActionType.ITEM_EVICT_USER -> claimService.evictUserFromItem(future, action, args, trigger.userId) ItemsActionType.ITEM_STATUS -> claimService.checkItemStatus(future, action) else -> { throw UnsupportedOperationException( diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/model/ItemsActionType.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/model/ItemsActionType.kt index e95f7c9f..4271826e 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/model/ItemsActionType.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/model/ItemsActionType.kt @@ -8,6 +8,7 @@ class ItemsActionType(name: String, description: String) : CoreActionType(name, val ITEM_BORROW_AS_USER = ItemsActionType("ITEM_BORROW_AS_USER", "Borrow an item as another user") val ITEM_RETURN = ItemsActionType("ITEM_RETURN", "Return an item") val ITEM_EVICT = ItemsActionType("ITEM_EVICT", "Evict all borrowers from an item") + val ITEM_EVICT_USER = ItemsActionType("ITEM_EVICT_USER", "Evict a user from an item") val ITEM_STATUS = ItemsActionType("ITEM_STATUS", "Check status of an item") val ALL_STATUS = ItemsActionType("ALL_STATUS", "Check status of all items") } diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemAsUserTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemAsUserTemplate.kt index 0004626c..5d27451f 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemAsUserTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemAsUserTemplate.kt @@ -12,11 +12,11 @@ import javax.inject.Inject */ class BorrowItemAsUserTemplate @Inject constructor(configService: ConfigService) : BorrowItemTemplate(configService) { override val actionType: ActionType = ItemsActionType.ITEM_BORROW_AS_USER - override val tokens = LinkedList(listOf("as", "{$borrower}", "borrow", "{$itemPlaceholder}", "{$subItemPlaceholder}", "for", "{$reasonPlaceholder}")) + override val tokens = LinkedList(listOf("as", "{$borrowerPlaceholder}", "borrow", "{$itemPlaceholder}", "{$subItemPlaceholder}", "for", "{$reasonPlaceholder}")) override val templateRegex: Pattern? - get() = "as\\s\\<@(?[a-zA-Z0-9]+)\\>\\sborrow\\s+(?[a-zA-Z0-9]+)\\s*(?.*)\\s+for\\s+(?.+)".toPattern() + get() = "as\\s\\<@(?<$borrowerPlaceholder>[a-zA-Z0-9]+)\\>\\sborrow\\s+(?<$itemPlaceholder>[a-zA-Z0-9]+)\\s*(?.*)\\s+for\\s+(?.+)".toPattern() companion object { - val borrower = "borrower" + val borrowerPlaceholder = "borrower" } } diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictUserFromItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictUserFromItemTemplate.kt new file mode 100644 index 00000000..5f02216f --- /dev/null +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictUserFromItemTemplate.kt @@ -0,0 +1,22 @@ +package com.gatehill.corebot.driver.items.chat.model.template + +import com.gatehill.corebot.chat.model.action.ActionType +import com.gatehill.corebot.config.ConfigService +import com.gatehill.corebot.driver.items.action.model.ItemsActionType +import java.util.LinkedList +import java.util.regex.Pattern +import javax.inject.Inject + +/** + * Evict a borrower from an item. + */ +class EvictUserFromItemTemplate @Inject constructor(configService: ConfigService) : BorrowItemTemplate(configService) { + override val actionType: ActionType = ItemsActionType.ITEM_EVICT_USER + override val tokens = LinkedList(listOf("evict", "{$borrowerPlaceholder}", "from", "{$itemPlaceholder}")) + override val templateRegex: Pattern? + get() = "evict\\s\\<@(?<$borrowerPlaceholder>[a-zA-Z0-9]+)\\>\\sfrom\\s+(?<$itemPlaceholder>[a-zA-Z0-9]+)".toPattern() + + companion object { + val borrowerPlaceholder = "borrower" + } +} diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt index f3866fe1..1fc6b257 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt @@ -8,8 +8,8 @@ import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.items.chat.model.template.BorrowItemAsUserTemplate import com.gatehill.corebot.driver.items.chat.model.template.BorrowItemTemplate -import com.gatehill.corebot.driver.items.config.OwnerDisplayMode import com.gatehill.corebot.driver.items.config.ItemSettings +import com.gatehill.corebot.driver.items.config.OwnerDisplayMode import com.gatehill.corebot.store.DataStore import com.gatehill.corebot.store.partition import java.util.concurrent.CompletableFuture @@ -48,7 +48,7 @@ class ClaimService @Inject constructor(private val configService: ConfigService, val subItem: String? = args[BorrowItemTemplate.subItemPlaceholder] // check if on behalf of another user - val borrower = args[BorrowItemAsUserTemplate.borrower] ?: triggerMessageSenderId + val borrower = args[BorrowItemAsUserTemplate.borrowerPlaceholder] ?: triggerMessageSenderId itemClaims[itemName] = ItemClaims(claims.toMutableList().apply { @@ -61,7 +61,7 @@ class ClaimService @Inject constructor(private val configService: ConfigService, }) val borrowerDescription = if (borrower == triggerMessageSenderId) " <@$borrower>, you've" else ", <@$borrower> has" - completeWithStatusCheck(future, "OK$borrowerDescription borrowed :lock: *$itemName* for _${reason}_.") + completeWithStatusCheck(future, "${chatGenerator.confirmation()}$borrowerDescription borrowed :lock: *$itemName* for _${reason}_.") } } } @@ -77,7 +77,7 @@ class ClaimService @Inject constructor(private val configService: ConfigService, itemClaims[itemName] = ItemClaims(claims.toMutableList().apply { remove(claim) }) - completeWithStatusCheck(future, "OK <@$triggerMessageSenderId>, you've returned *$itemName*.") + completeWithStatusCheck(future, "${chatGenerator.confirmation()} <@$triggerMessageSenderId>, you've returned *$itemName*.") } ?: run { completeWithStatusCheck(future, "BTW <@$triggerMessageSenderId>, you weren't borrowing *$itemName* :wink:") @@ -86,6 +86,9 @@ class ClaimService @Inject constructor(private val configService: ConfigService, } } + /** + * Evict all borrowers from an item. + */ fun evictItemClaims(future: CompletableFuture, action: ActionConfig, triggerMessageSenderId: String) { val itemName = action.name @@ -93,17 +96,42 @@ class ClaimService @Inject constructor(private val configService: ConfigService, checkItemClaims(action) { claims -> itemClaims.remove(itemName) - val nonSelfBorrowers = claims + val previousBorrowers = claims .map { it.owner } .filter { it != triggerMessageSenderId } + .let { nonSelfBorrowers -> + if (nonSelfBorrowers.isEmpty()) { + "" + } else { + "\n_(FYI ${nonSelfBorrowers.map { "<@$it>" }.joinToString()})_" + } + } + + completeWithStatusCheck(future, "${chatGenerator.confirmation()} <@$triggerMessageSenderId>, I've :unlock: evicted all borrowers (${claims.size}) from *$itemName*.$previousBorrowers") + } + } + } - val previousBorrowers = if (nonSelfBorrowers.isEmpty()) { - "" - } else { - "\n_(FYI ${nonSelfBorrowers.map { "<@$it>" }.joinToString()})_" + /** + * Evict a single borrower from an item. + */ + fun evictUserFromItem(future: CompletableFuture, action: ActionConfig, args: Map, triggerMessageSenderId: String) { + val itemName = action.name + + synchronized(itemName) { + checkItemClaims(action) { claims -> + val borrower = args[BorrowItemAsUserTemplate.borrowerPlaceholder]!! + + when (claims.size) { + 1 -> itemClaims.remove(itemName) + else -> itemClaims[itemName] = ItemClaims(claims.toMutableList().apply { + claims.firstOrNull { it.owner == borrower }?.let { existing -> + remove(existing) + } + }) } - completeWithStatusCheck(future, "OK <@$triggerMessageSenderId>, I've :unlock: evicted all borrowers (${claims.size}) from *$itemName*.$previousBorrowers") + completeWithStatusCheck(future, "${chatGenerator.confirmation()} <@$triggerMessageSenderId>, I've :unlock: evicted <@$borrower> from *$itemName*.") } } } @@ -147,7 +175,7 @@ class ClaimService @Inject constructor(private val configService: ConfigService, val claimsList = StringBuilder() claims.forEach { (owner, reason, subItem) -> val subItemDescription = subItem?.let { if (it.isNotBlank()) "$it: " else null } ?: "" - claimsList.append("\n • $subItemDescription${describeOwner(owner)} - $reason") + claimsList.append("\n${" ".repeat(4)}• $subItemDescription${describeOwner(owner)} - $reason") } "There are ${claims.size} borrowers of *$itemName*:$claimsList" diff --git a/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt b/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt index 300d799f..fa6dd9c2 100644 --- a/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt +++ b/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt @@ -7,6 +7,7 @@ import com.gatehill.corebot.driver.items.action.ItemsActionDriverImpl import com.gatehill.corebot.driver.items.chat.model.template.BorrowItemAsUserTemplate import com.gatehill.corebot.driver.items.chat.model.template.BorrowItemTemplate import com.gatehill.corebot.driver.items.chat.model.template.EvictItemTemplate +import com.gatehill.corebot.driver.items.chat.model.template.EvictUserFromItemTemplate import com.gatehill.corebot.driver.items.chat.model.template.ReturnItemTemplate import com.gatehill.corebot.driver.items.chat.model.template.StatusAllTemplate import com.gatehill.corebot.driver.items.chat.model.template.StatusItemTemplate @@ -27,6 +28,7 @@ class Bootstrap @Inject constructor(actionDriverFactory: ActionDriverFactory, templateService.registerTemplate(BorrowItemAsUserTemplate::class.java) templateService.registerTemplate(ReturnItemTemplate::class.java) templateService.registerTemplate(EvictItemTemplate::class.java) + templateService.registerTemplate(EvictUserFromItemTemplate::class.java) templateService.registerTemplate(StatusItemTemplate::class.java) templateService.registerTemplate(StatusAllTemplate::class.java) } diff --git a/bots/slack-items/src/main/resources/default-security.yml b/bots/slack-items/src/main/resources/default-security.yml index b259d6f2..9777346b 100644 --- a/bots/slack-items/src/main/resources/default-security.yml +++ b/bots/slack-items/src/main/resources/default-security.yml @@ -12,6 +12,7 @@ security: - item_borrow_as_user - item_return - item_evict + - item_evict_user - item_status - all_status tags: diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/LockService.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/LockService.kt index 6b207444..97eb88a9 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/action/LockService.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/LockService.kt @@ -1,6 +1,7 @@ package com.gatehill.corebot.action import com.gatehill.corebot.action.model.PerformActionResult +import com.gatehill.corebot.chat.ChatGenerator import com.gatehill.corebot.chat.model.template.BaseLockOptionTemplate import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.store.DataStore @@ -14,7 +15,9 @@ import javax.inject.Named * * @author Pete Cornish {@literal } */ -class LockService @Inject constructor(@Named("lockStore") private val lockStore: DataStore) { +class LockService @Inject constructor(@Named("lockStore") private val lockStore: DataStore, + private val chatGenerator: ChatGenerator) { + /** * A lock held on an action. */ @@ -51,7 +54,7 @@ class LockService @Inject constructor(@Named("lockStore") private val lockStore: } ?: run { // acquire actionLocks[action.name] = ActionLock(triggerMessageSenderId) - future.complete(PerformActionResult("OK, I've locked :lock: *${action.name}* for you.")) + future.complete(PerformActionResult("${chatGenerator.confirmation()}, I've locked :lock: *${action.name}* for you.")) } } } @@ -61,7 +64,7 @@ class LockService @Inject constructor(@Named("lockStore") private val lockStore: lock?.let { // unlock actionLocks.remove(action.name) - future.complete(PerformActionResult("OK, I've unlocked :unlock: *${action.name}* for you.")) + future.complete(PerformActionResult("${chatGenerator.confirmation()}, I've unlocked :unlock: *${action.name}* for you.")) } ?: run { // already unlocked diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/ChatGenerator.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/ChatGenerator.kt index 3edad7d1..c0864496 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/ChatGenerator.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/ChatGenerator.kt @@ -2,7 +2,7 @@ package com.gatehill.corebot.chat import com.gatehill.corebot.config.Settings import com.gatehill.corebot.util.yamlMapper -import java.util.* +import java.util.Random /** * @author Pete Cornish {@literal } @@ -35,4 +35,5 @@ class ChatGenerator(dictionary: Map>? = null) { fun badNewsEmoji() = chooseOne("badNewsEmoji") fun greeting() = chooseOne("greeting") fun ready() = chooseOne("ready") + fun confirmation() = chooseOne("confirmation") } diff --git a/core/engine/src/main/resources/default-chat.yml b/core/engine/src/main/resources/default-chat.yml index 8c875f4b..d0b9001f 100644 --- a/core/engine/src/main/resources/default-chat.yml +++ b/core/engine/src/main/resources/default-chat.yml @@ -36,3 +36,8 @@ greeting: - "Hi" - "Hello" - "Hey" + +confirmation: + - "OK" + - "Alright" + - "Okay" From 4572147307f19648f43caf9ee018ae4c119c61bf Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Sun, 11 Jun 2017 17:16:38 +0100 Subject: [PATCH 22/36] Adds defensive check for null message content. --- CHANGELOG.md | 3 ++- .../corebot/chat/SlackChatServiceImpl.kt | 18 +++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e837328..376e0d76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## Unreleased ### Added -- ... +- Adds a bot to allow simple borrow/return of items. +- Adds a new 'items' backend. ## [0.8.1] - 2017-06-11 ### Added diff --git a/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt b/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt index ab4c0e40..475e651d 100644 --- a/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt +++ b/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt @@ -41,16 +41,16 @@ open class SlackChatServiceImpl @Inject constructor(private val sessionService: val trigger = TriggerContext(event.channel.id, event.sender.id, event.sender.userName, event.timestamp, event.threadTimestamp) try { - val messageContent = event.messageContent.trim() - val splitCmd = messageContent.split("\"?( |$)(?=(([^\"]*\"){2})*[^\"]*$)\"?".toRegex()).filterNot(String::isBlank) + // some message events have null content + event.messageContent?.trim()?.let { messageContent -> + val splitCmd = messageContent.split("\"?( |$)(?=(([^\"]*\"){2})*[^\"]*$)\"?".toRegex()).filterNot(String::isBlank) - if (splitCmd.isNotEmpty() && isAddressedToBot(session.sessionPersona(), splitCmd[0])) { - - // skip element 0, which contains the bot's username - val commandOnly = splitCmd.subList(1, splitCmd.size) - - messageService.handleMessage(commandOnly, trigger) - } + if (splitCmd.isNotEmpty() && isAddressedToBot(session.sessionPersona(), splitCmd[0])) { + // skip element 0, which contains the bot's username + val commandOnly = splitCmd.subList(1, splitCmd.size) + messageService.handleMessage(commandOnly, trigger) + } + } ?: logger.trace("Ignoring event with null message: $event") } catch (e: Exception) { logger.error("Error parsing message event: $event", e) From 23f9c42997a41b4ff91bd43e3a771a89ebc1696b Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Mon, 12 Jun 2017 13:57:29 +0100 Subject: [PATCH 23/36] Makes join message conditional. --- .../gatehill/corebot/chat/SlackSessionServiceImpl.kt | 12 +++++++----- .../com/gatehill/corebot/config/ChatSettings.kt | 5 +++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackSessionServiceImpl.kt b/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackSessionServiceImpl.kt index 119cb6b2..36d3a9b2 100644 --- a/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackSessionServiceImpl.kt +++ b/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackSessionServiceImpl.kt @@ -36,12 +36,14 @@ open class SlackSessionServiceImpl @Inject constructor(configService: ConfigServ * Allow subclasses to hook into Slack events. */ protected open val connectedListeners = listOf(SlackConnectedListener { _, theSession -> - ChatSettings.chat.channelNames.forEach { - val joinMessage = configService.joinMessage ?: - "${chatGenerator.greeting()} :simple_smile: ${chatGenerator.ready()}." + if (ChatSettings.chat.postJoinMessage) { + ChatSettings.chat.channelNames.forEach { + val joinMessage = configService.joinMessage ?: + "${chatGenerator.greeting()} :simple_smile: ${chatGenerator.ready()}." - theSession.findChannelByName(it)?.let { channel -> theSession.sendMessage(channel, joinMessage) } - ?: logger.warn("Unable to find channel: $it") + theSession.findChannelByName(it)?.let { channel -> theSession.sendMessage(channel, joinMessage) } + ?: logger.warn("Unable to find channel: $it") + } } }) diff --git a/frontends/slack/src/main/kotlin/com/gatehill/corebot/config/ChatSettings.kt b/frontends/slack/src/main/kotlin/com/gatehill/corebot/config/ChatSettings.kt index 7f66cb2c..b5cac235 100644 --- a/frontends/slack/src/main/kotlin/com/gatehill/corebot/config/ChatSettings.kt +++ b/frontends/slack/src/main/kotlin/com/gatehill/corebot/config/ChatSettings.kt @@ -11,11 +11,12 @@ object ChatSettings { val channelNames by lazy { (System.getenv("SLACK_CHANNELS") ?: "corebot").split(",").map(String::trim) } + val postJoinMessage by lazy { System.getenv("SLACK_ENABLE_JOIN_MESSAGE")?.toBoolean() ?: true } } class Threads { - val replyInThread by lazy { (System.getenv("SLACK_REPLY_IN_THREAD")?.toBoolean() ?: false) } - val allowThreadedTriggers by lazy { (System.getenv("SLACK_ALLOW_THREADED_TRIGGERS")?.toBoolean() ?: false) } + val replyInThread by lazy { System.getenv("SLACK_REPLY_IN_THREAD")?.toBoolean() ?: false } + val allowThreadedTriggers by lazy { System.getenv("SLACK_ALLOW_THREADED_TRIGGERS")?.toBoolean() ?: false } } val chat = Chat() From cacfe5d5ab67721fd3b86783bb76ceca4b188009 Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Wed, 14 Jun 2017 01:35:37 +0100 Subject: [PATCH 24/36] Bumps Kotlin version. Adds MySQL store. --- bots/common/src/main/resources/log4j2.xml | 1 + bots/slack-items/build.gradle | 1 + build.gradle | 3 +- circle.yml | 2 +- settings.gradle | 3 + stores/mysql/README.md | 14 +++ stores/mysql/build.gradle | 14 +++ .../corebot/store/mysql/MysqlDataStoreImpl.kt | 97 +++++++++++++++++++ .../store/mysql/config/StoreSettings.kt | 12 +++ stores/redis/README.md | 2 +- 10 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 stores/mysql/README.md create mode 100644 stores/mysql/build.gradle create mode 100644 stores/mysql/src/main/kotlin/com/gatehill/corebot/store/mysql/MysqlDataStoreImpl.kt create mode 100644 stores/mysql/src/main/kotlin/com/gatehill/corebot/store/mysql/config/StoreSettings.kt diff --git a/bots/common/src/main/resources/log4j2.xml b/bots/common/src/main/resources/log4j2.xml index efebad69..47ad27e8 100644 --- a/bots/common/src/main/resources/log4j2.xml +++ b/bots/common/src/main/resources/log4j2.xml @@ -12,5 +12,6 @@ + diff --git a/bots/slack-items/build.gradle b/bots/slack-items/build.gradle index 801804d9..57eefc6b 100644 --- a/bots/slack-items/build.gradle +++ b/bots/slack-items/build.gradle @@ -13,6 +13,7 @@ dependencies { compile project(':bots:bots-common') compile project(':backends:backends-items') compile project(':stores:stores-redis') + compile project(':stores:stores-mysql') compile project(':frontends:frontends-slack') } diff --git a/build.gradle b/build.gradle index ea43ee76..7ae2fc3a 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ allprojects { ext.awsSecretKey = System.getenv('AWS_SECRET_ACCESS_KEY') ?: project.findProperty('AWS_SECRET_ACCESS_KEY') buildscript { - ext.version_kotlin = '1.1.2-4' + ext.version_kotlin = '1.1.2-5' ext.version_junit_platform_gradle = '1.0.0-M4' ext.version_spek = '1.1.2' ext.version_kluent = '1.22' @@ -28,6 +28,7 @@ allprojects { mavenCentral() jcenter() maven { url "https://jitpack.io" } + maven { url "http://dl.bintray.com/kotlin/exposed" } } apply plugin: "maven-publish" diff --git a/circle.yml b/circle.yml index de264f9c..1864e935 100644 --- a/circle.yml +++ b/circle.yml @@ -27,7 +27,7 @@ deployment: - /bin/bash ./scripts/docker-build.sh beta edge: - branch: feature/ownable-items + branch: feature/mysql-store commands: - ./gradlew publish --stacktrace - /bin/bash ./scripts/docker-build.sh edge diff --git a/settings.gradle b/settings.gradle index c4a2026e..5e553cf7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -38,3 +38,6 @@ project(':frontends:slack').name = 'frontends-slack' include ':stores:redis' project(':stores:redis').name = 'stores-redis' + +include ':stores:mysql' +project(':stores:mysql').name = 'stores-mysql' diff --git a/stores/mysql/README.md b/stores/mysql/README.md new file mode 100644 index 00000000..3aba3adc --- /dev/null +++ b/stores/mysql/README.md @@ -0,0 +1,14 @@ +# MySQL store + +Enable the MySQL store implementation with: + + DATA_STORE_IMPL=com.gatehill.corebot.store.mysql.MysqlDataStoreImpl + MYSQL_CONNECTION_STRING=jdbc:mysql://localhost:3306/corebot + MYSQL_USERNAME=corebot + MYSQL_PASSWORD=Corebot123! + +## Local server + +Example MySQL instance: + + docker run --name corebot-mysql -p 3306:3306 -e MYSQL_DATABASE=corebot -e MYSQL_USER=corebot -e MYSQL_PASSWORD=Corebot123! -e MYSQL_RANDOM_ROOT_PASSWORD=yes --rm -it mysql diff --git a/stores/mysql/build.gradle b/stores/mysql/build.gradle new file mode 100644 index 00000000..6f529b51 --- /dev/null +++ b/stores/mysql/build.gradle @@ -0,0 +1,14 @@ +buildscript { + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$version_kotlin" + } +} + +apply plugin: 'kotlin' + +dependencies { + compile project(':core:core-engine') + + compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.42' + compile 'org.jetbrains.exposed:exposed:0.8' +} diff --git a/stores/mysql/src/main/kotlin/com/gatehill/corebot/store/mysql/MysqlDataStoreImpl.kt b/stores/mysql/src/main/kotlin/com/gatehill/corebot/store/mysql/MysqlDataStoreImpl.kt new file mode 100644 index 00000000..e132a039 --- /dev/null +++ b/stores/mysql/src/main/kotlin/com/gatehill/corebot/store/mysql/MysqlDataStoreImpl.kt @@ -0,0 +1,97 @@ +package com.gatehill.corebot.store.mysql + +import com.fasterxml.jackson.databind.ObjectMapper +import com.gatehill.corebot.store.DataStore +import com.gatehill.corebot.store.DataStorePartition +import com.gatehill.corebot.store.mysql.config.StoreSettings +import com.gatehill.corebot.util.jsonMapper +import org.jetbrains.exposed.dao.EntityID +import org.jetbrains.exposed.dao.IntEntity +import org.jetbrains.exposed.dao.IntEntityClass +import org.jetbrains.exposed.dao.IntIdTable +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.SchemaUtils.createMissingTablesAndColumns +import org.jetbrains.exposed.sql.Slf4jSqlLogger +import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.deleteWhere +import org.jetbrains.exposed.sql.transactions.transaction +import javax.sql.rowset.serial.SerialBlob + +/** + * A MySQL store. + * + * @author Pete Cornish {@literal } + */ +class MysqlDataStoreImpl : DataStore { + private val partitions = mutableMapOf>() + + init { + Database.connect(StoreSettings.connectionString, driver = "com.mysql.jdbc.Driver", user = StoreSettings.username, password = StoreSettings.password) + transaction { + logger.addLogger(Slf4jSqlLogger) + createMissingTablesAndColumns(KeyValues) + } + } + + @Suppress("UNCHECKED_CAST") + override fun partitionForClass(partitionId: String, valueClass: Class): DataStorePartition = + partitions[partitionId] as DataStorePartition? ?: + MysqlDataStorePartitionImpl(jsonMapper, valueClass, partitionId).apply { partitions[partitionId] = this } +} + +object KeyValues : IntIdTable() { + val key = varchar("key", 100).index() + val partition = varchar("partition", 100).index() + val value = blob("value") +} + +class KeyValue(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(KeyValues) + + var key by KeyValues.key + var partition by KeyValues.partition + var value by KeyValues.value +} + +/** + * A MySQL partition. + * + * @author Pete Cornish {@literal } + */ +private class MysqlDataStorePartitionImpl(private val mapper: ObjectMapper, + private val clazz: Class, + private val partitionId: String) : DataStorePartition { + + override fun set(key: K, value: V) { + transaction { + logger.addLogger(Slf4jSqlLogger) + + KeyValue.new { + this.key = key.toString() + this.partition = partitionId + this.value = SerialBlob(mapper.writeValueAsBytes(value)) + } + } + } + + override fun get(key: K): V? = transaction { + logger.addLogger(Slf4jSqlLogger) + + KeyValue.find { + (KeyValues.key eq key) and (KeyValues.partition eq partitionId) + + }.firstOrNull()?.let { + mapper.readValue(it.value.binaryStream.use { it.readBytes() }, clazz) + } + } + + override fun remove(key: K) { + transaction { + logger.addLogger(Slf4jSqlLogger) + + KeyValues.deleteWhere { + (KeyValues.key eq key) and (KeyValues.partition eq partitionId) + } + } + } +} diff --git a/stores/mysql/src/main/kotlin/com/gatehill/corebot/store/mysql/config/StoreSettings.kt b/stores/mysql/src/main/kotlin/com/gatehill/corebot/store/mysql/config/StoreSettings.kt new file mode 100644 index 00000000..0955c36f --- /dev/null +++ b/stores/mysql/src/main/kotlin/com/gatehill/corebot/store/mysql/config/StoreSettings.kt @@ -0,0 +1,12 @@ +package com.gatehill.corebot.store.mysql.config + +/** + * Data store settings. + * + * @author Pete Cornish {@literal } + */ +object StoreSettings { + val connectionString by lazy { System.getenv("MYSQL_CONNECTION_STRING") ?: "jdbc:mysql://localhost:3306/corebot" } + val username by lazy { System.getenv("MYSQL_USERNAME") ?: "corebot" } + val password by lazy { System.getenv("MYSQL_PASSWORD") ?: "Corebot123!" } +} diff --git a/stores/redis/README.md b/stores/redis/README.md index 1b2165e9..b07afa76 100644 --- a/stores/redis/README.md +++ b/stores/redis/README.md @@ -6,7 +6,7 @@ Enable the Redis store implementation with: ## Local server -Example Redis: +Example Redis instance: docker run --rm -it -p 6379:6379 -v /tmp/redis:/data --name redis redis From c71989f7e3b1f5ca25523c752ed951b485ba219a Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Sat, 17 Jun 2017 00:41:07 +0100 Subject: [PATCH 25/36] Adds Maven publishing configuration. --- stores/mysql/build.gradle | 17 +++++++++++++++++ stores/redis/build.gradle | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/stores/mysql/build.gradle b/stores/mysql/build.gradle index 6f529b51..f903c167 100644 --- a/stores/mysql/build.gradle +++ b/stores/mysql/build.gradle @@ -12,3 +12,20 @@ dependencies { compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.42' compile 'org.jetbrains.exposed:exposed:0.8' } + +publishing { + publications { + maven(MavenPublication) { + from components.java + repositories { + maven { + url mavenSnapshotRepository + credentials(AwsCredentials) { + accessKey awsAccessKey + secretKey awsSecretKey + } + } + } + } + } +} diff --git a/stores/redis/build.gradle b/stores/redis/build.gradle index d870e30d..df0bd37d 100644 --- a/stores/redis/build.gradle +++ b/stores/redis/build.gradle @@ -11,3 +11,20 @@ dependencies { compile('redis.clients:jedis:2.9.0') } + +publishing { + publications { + maven(MavenPublication) { + from components.java + repositories { + maven { + url mavenSnapshotRepository + credentials(AwsCredentials) { + accessKey awsAccessKey + secretKey awsSecretKey + } + } + } + } + } +} From 04b6d202db9822c78efa95af0b8eb88e9a62130a Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Sat, 17 Jun 2017 00:41:29 +0100 Subject: [PATCH 26/36] Adds Maven publishing configuration. --- stores/redis/build.gradle | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/stores/redis/build.gradle b/stores/redis/build.gradle index d870e30d..df0bd37d 100644 --- a/stores/redis/build.gradle +++ b/stores/redis/build.gradle @@ -11,3 +11,20 @@ dependencies { compile('redis.clients:jedis:2.9.0') } + +publishing { + publications { + maven(MavenPublication) { + from components.java + repositories { + maven { + url mavenSnapshotRepository + credentials(AwsCredentials) { + accessKey awsAccessKey + secretKey awsSecretKey + } + } + } + } + } +} From 50a6b978535d279198a3ef4f1057ef9c05c34866 Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Tue, 20 Jun 2017 23:32:56 +0100 Subject: [PATCH 27/36] Removes MySQL store dependency by default. --- bots/slack-items/build.gradle | 1 - circle.yml | 6 ------ 2 files changed, 7 deletions(-) diff --git a/bots/slack-items/build.gradle b/bots/slack-items/build.gradle index 57eefc6b..801804d9 100644 --- a/bots/slack-items/build.gradle +++ b/bots/slack-items/build.gradle @@ -13,7 +13,6 @@ dependencies { compile project(':bots:bots-common') compile project(':backends:backends-items') compile project(':stores:stores-redis') - compile project(':stores:stores-mysql') compile project(':frontends:frontends-slack') } diff --git a/circle.yml b/circle.yml index 1864e935..4abdf419 100644 --- a/circle.yml +++ b/circle.yml @@ -25,9 +25,3 @@ deployment: commands: - ./gradlew publish --stacktrace - /bin/bash ./scripts/docker-build.sh beta - - edge: - branch: feature/mysql-store - commands: - - ./gradlew publish --stacktrace - - /bin/bash ./scripts/docker-build.sh edge From b534825d2baa589eccf1211e594d788700aedfc2 Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Sat, 24 Jun 2017 12:50:31 +0100 Subject: [PATCH 28/36] Back-port of command changes from websocket frontend. --- .../chat/model/template/TriggerJobTemplate.kt | 3 +- .../items/action/ItemsActionDriverImpl.kt | 17 +++---- .../chat/model/template/BorrowItemTemplate.kt | 3 +- .../chat/model/template/EvictItemTemplate.kt | 3 +- .../chat/model/template/ReturnItemTemplate.kt | 3 +- .../chat/model/template/StatusAllTemplate.kt | 5 ++- .../chat/model/template/StatusItemTemplate.kt | 3 +- .../driver/items/service/ClaimService.kt | 45 ++++++++++--------- bots/slack-deploy/build.gradle | 2 +- .../gatehill/corebot/chat/SessionService.kt | 8 ++-- .../gatehill/corebot/chat/TemplateService.kt | 18 +++++--- .../chat/model/template/ActionTemplate.kt | 6 ++- .../chat/model/template/BaseActionTemplate.kt | 3 +- .../model/template/BaseLockOptionTemplate.kt | 3 +- .../model/template/CustomActionTemplate.kt | 5 ++- .../chat/model/template/ShowHelpTemplate.kt | 3 +- .../model/template/SystemActionTemplate.kt | 5 ++- .../gatehill/corebot/chat/MessageService.kt | 23 ++++++---- .../corebot/chat/SlackChatServiceImpl.kt | 31 ++++++++----- .../corebot/chat/SlackSessionServiceImpl.kt | 14 +++--- 20 files changed, 118 insertions(+), 85 deletions(-) diff --git a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/TriggerJobTemplate.kt b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/TriggerJobTemplate.kt index 51ac662f..c022b56b 100644 --- a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/TriggerJobTemplate.kt +++ b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/TriggerJobTemplate.kt @@ -1,6 +1,7 @@ package com.gatehill.corebot.chat.model.template import com.gatehill.corebot.chat.ChatGenerator +import com.gatehill.corebot.action.model.TriggerContext import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.model.ActionConfig import java.util.LinkedList @@ -24,7 +25,7 @@ class TriggerJobTemplate(action: ActionConfig, tokens = LinkedList(action.template.split("\\s".toRegex()).filterNot(String::isBlank)) } - override fun buildStartMessage(options: Map, actionConfig: ActionConfig?): String { + override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?): String { actionConfig ?: throw IllegalArgumentException("Empty actionConfig") val msg = StringBuilder() diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt index 4ec79054..74a15ff5 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt @@ -18,16 +18,13 @@ class ItemsActionDriverImpl @Inject constructor(private val claimService: ClaimS val future = CompletableFuture() try { when (actionType) { - ItemsActionType.ITEM_BORROW -> claimService.claimItem(future, action, args, trigger.userId) - ItemsActionType.ITEM_BORROW_AS_USER -> claimService.claimItem(future, action, args, trigger.userId) - ItemsActionType.ITEM_RETURN -> claimService.releaseItem(future, action, trigger.userId) - ItemsActionType.ITEM_EVICT -> claimService.evictItemClaims(future, action, trigger.userId) - ItemsActionType.ITEM_EVICT_USER -> claimService.evictUserFromItem(future, action, args, trigger.userId) - ItemsActionType.ITEM_STATUS -> claimService.checkItemStatus(future, action) - else -> { - throw UnsupportedOperationException( - "Action type $actionType is not supported by ${javaClass.canonicalName}") - } + ItemsActionType.ITEM_EVICT_USER -> claimService.evictUserFromItem(trigger, future, action, args, trigger.userId) + ItemsActionType.ITEM_BORROW -> claimService.claimItem(trigger, future, action, args, trigger.userId) + ItemsActionType.ITEM_BORROW_AS_USER -> claimService.claimItem(trigger, future, action, args, trigger.userId) + ItemsActionType.ITEM_RETURN -> claimService.releaseItem(trigger, future, action, trigger.userId) + ItemsActionType.ITEM_EVICT -> claimService.evictItemClaims(trigger, future, action, trigger.userId) + ItemsActionType.ITEM_STATUS -> claimService.checkItemStatus(trigger, future, action) + else -> throw UnsupportedOperationException("Action type $actionType is not supported by ${javaClass.canonicalName}") } } catch (e: Exception) { future.completeExceptionally(e) diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt index dc9dc8e0..a46e5566 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt @@ -1,5 +1,6 @@ package com.gatehill.corebot.driver.items.chat.model.template +import com.gatehill.corebot.action.model.TriggerContext import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig @@ -17,7 +18,7 @@ open class BorrowItemTemplate @Inject constructor(configService: ConfigService) override val templateRegex: Pattern? get() = "borrow\\s+(?[a-zA-Z0-9]+)\\s*(?.*)\\s+for\\s+(?.+)".toPattern() - override fun buildStartMessage(options: Map, actionConfig: ActionConfig?) = "" + override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?) = "" companion object { val subItemPlaceholder = "optionalSubItemName" diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictItemTemplate.kt index a2407597..8591be66 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictItemTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictItemTemplate.kt @@ -1,5 +1,6 @@ package com.gatehill.corebot.driver.items.chat.model.template +import com.gatehill.corebot.action.model.TriggerContext import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig @@ -13,5 +14,5 @@ import javax.inject.Inject class EvictItemTemplate @Inject constructor(configService: ConfigService) : BaseItemTemplate(configService) { override val actionType: ActionType = ItemsActionType.ITEM_EVICT override val tokens = LinkedList(listOf("evict", "{$itemPlaceholder}")) - override fun buildStartMessage(options: Map, actionConfig: ActionConfig?) = "" + override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?) = "" } diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/ReturnItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/ReturnItemTemplate.kt index ee689602..1b007bc4 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/ReturnItemTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/ReturnItemTemplate.kt @@ -1,5 +1,6 @@ package com.gatehill.corebot.driver.items.chat.model.template +import com.gatehill.corebot.action.model.TriggerContext import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig @@ -13,5 +14,5 @@ import javax.inject.Inject class ReturnItemTemplate @Inject constructor(configService: ConfigService) : BaseItemTemplate(configService) { override val actionType: ActionType = ItemsActionType.ITEM_RETURN override val tokens = LinkedList(listOf("return", "{$itemPlaceholder}")) - override fun buildStartMessage(options: Map, actionConfig: ActionConfig?) = "" + override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?) = "" } diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusAllTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusAllTemplate.kt index 94186d21..3eda2663 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusAllTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusAllTemplate.kt @@ -1,5 +1,6 @@ package com.gatehill.corebot.driver.items.chat.model.template +import com.gatehill.corebot.action.model.TriggerContext import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.chat.model.template.ActionMessageMode import com.gatehill.corebot.chat.model.template.SystemActionTemplate @@ -19,6 +20,6 @@ class StatusAllTemplate @Inject constructor(val claimService: ClaimService) : Sy override val actionType: ActionType = ItemsActionType.ALL_STATUS override val tokens = LinkedList(listOf("status")) - override fun buildStartMessage(options: Map, actionConfig: ActionConfig?) = - claimService.describeAllItemStatus() + override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?) = + claimService.describeAllItemStatus(trigger) } diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusItemTemplate.kt index 1f117827..5ce4e650 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusItemTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusItemTemplate.kt @@ -1,5 +1,6 @@ package com.gatehill.corebot.driver.items.chat.model.template +import com.gatehill.corebot.action.model.TriggerContext import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig @@ -13,5 +14,5 @@ import javax.inject.Inject class StatusItemTemplate @Inject constructor(configService: ConfigService) : BaseItemTemplate(configService) { override val actionType: ActionType = ItemsActionType.ITEM_STATUS override val tokens = LinkedList(listOf("status", "{$itemPlaceholder}")) - override fun buildStartMessage(options: Map, actionConfig: ActionConfig?) = "" + override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?) = "" } diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt index 1fc6b257..a3b0647b 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt @@ -2,6 +2,7 @@ package com.gatehill.corebot.driver.items.service import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.gatehill.corebot.action.model.PerformActionResult +import com.gatehill.corebot.action.model.TriggerContext import com.gatehill.corebot.chat.ChatGenerator import com.gatehill.corebot.chat.SessionService import com.gatehill.corebot.config.ConfigService @@ -39,7 +40,7 @@ class ClaimService @Inject constructor(private val configService: ConfigService, private val itemClaims get() = dataStore.partition("itemClaims") - fun claimItem(future: CompletableFuture, action: ActionConfig, args: Map, triggerMessageSenderId: String) { + fun claimItem(trigger: TriggerContext, future: CompletableFuture, action: ActionConfig, args: Map, triggerMessageSenderId: String) { val itemName = action.name synchronized(itemName) { @@ -61,12 +62,12 @@ class ClaimService @Inject constructor(private val configService: ConfigService, }) val borrowerDescription = if (borrower == triggerMessageSenderId) " <@$borrower>, you've" else ", <@$borrower> has" - completeWithStatusCheck(future, "${chatGenerator.confirmation()}$borrowerDescription borrowed :lock: *$itemName* for _${reason}_.") + completeWithStatusCheck(trigger, future, "${chatGenerator.confirmation()}$borrowerDescription borrowed :lock: *$itemName* for _${reason}_.") } } } - fun releaseItem(future: CompletableFuture, action: ActionConfig, triggerMessageSenderId: String) { + fun releaseItem(trigger: TriggerContext, future: CompletableFuture, action: ActionConfig, triggerMessageSenderId: String) { val itemName = action.name synchronized(itemName) { @@ -77,10 +78,10 @@ class ClaimService @Inject constructor(private val configService: ConfigService, itemClaims[itemName] = ItemClaims(claims.toMutableList().apply { remove(claim) }) - completeWithStatusCheck(future, "${chatGenerator.confirmation()} <@$triggerMessageSenderId>, you've returned *$itemName*.") + completeWithStatusCheck(trigger, future, "${chatGenerator.confirmation()} <@$triggerMessageSenderId>, you've returned *$itemName*.") } ?: run { - completeWithStatusCheck(future, "BTW <@$triggerMessageSenderId>, you weren't borrowing *$itemName* :wink:") + completeWithStatusCheck(trigger, future, "BTW <@$triggerMessageSenderId>, you weren't borrowing *$itemName* :wink:") } } } @@ -89,7 +90,7 @@ class ClaimService @Inject constructor(private val configService: ConfigService, /** * Evict all borrowers from an item. */ - fun evictItemClaims(future: CompletableFuture, action: ActionConfig, triggerMessageSenderId: String) { + fun evictItemClaims(trigger: TriggerContext, future: CompletableFuture, action: ActionConfig, triggerMessageSenderId: String) { val itemName = action.name synchronized(itemName) { @@ -107,7 +108,7 @@ class ClaimService @Inject constructor(private val configService: ConfigService, } } - completeWithStatusCheck(future, "${chatGenerator.confirmation()} <@$triggerMessageSenderId>, I've :unlock: evicted all borrowers (${claims.size}) from *$itemName*.$previousBorrowers") + completeWithStatusCheck(trigger, future, "${chatGenerator.confirmation()} <@$triggerMessageSenderId>, I've :unlock: evicted all borrowers (${claims.size}) from *$itemName*.$previousBorrowers") } } } @@ -115,7 +116,7 @@ class ClaimService @Inject constructor(private val configService: ConfigService, /** * Evict a single borrower from an item. */ - fun evictUserFromItem(future: CompletableFuture, action: ActionConfig, args: Map, triggerMessageSenderId: String) { + fun evictUserFromItem(trigger: TriggerContext, future: CompletableFuture, action: ActionConfig, args: Map, triggerMessageSenderId: String) { val itemName = action.name synchronized(itemName) { @@ -131,7 +132,7 @@ class ClaimService @Inject constructor(private val configService: ConfigService, }) } - completeWithStatusCheck(future, "${chatGenerator.confirmation()} <@$triggerMessageSenderId>, I've :unlock: evicted <@$borrower> from *$itemName*.") + completeWithStatusCheck(trigger, future, "${chatGenerator.confirmation()} <@$triggerMessageSenderId>, I've :unlock: evicted <@$borrower> from *$itemName*.") } } } @@ -139,9 +140,9 @@ class ClaimService @Inject constructor(private val configService: ConfigService, /** * Complete with the result message, optionally printing the current status. */ - private fun completeWithStatusCheck(future: CompletableFuture, message: String) { + private fun completeWithStatusCheck(trigger: TriggerContext, future: CompletableFuture, message: String) { val finalMessage = if (ItemSettings.showStatusOnChange) { - "$message ${buildAllItemStatus()}" + "$message ${buildAllItemStatus(trigger)}" } else { message } @@ -155,13 +156,13 @@ class ClaimService @Inject constructor(private val configService: ConfigService, fun checkItemClaims(action: ActionConfig, callback: (List) -> Unit) = checkItemClaims(action.name, callback) - fun checkItemStatus(future: CompletableFuture, action: ActionConfig) { - describeItem(action.name) { + fun checkItemStatus(trigger: TriggerContext, future: CompletableFuture, action: ActionConfig) { + describeItem(trigger, action.name) { future.complete(PerformActionResult(it)) } } - fun describeItem(itemName: String, callback: (String) -> Unit) { + fun describeItem(trigger: TriggerContext, itemName: String, callback: (String) -> Unit) { checkItemClaims(itemName) { claims -> val message = when { claims.isEmpty() -> "No one is borrowing *$itemName*." @@ -169,13 +170,13 @@ class ClaimService @Inject constructor(private val configService: ConfigService, val claim = claims.first() val subItemDescription = claim.subItem?.let { if (it.isNotBlank()) " ($it)" else null } ?: "" - "There is a single borrower of *$itemName*$subItemDescription: ${describeOwner(claim.owner)} - ${claim.reason}" + "There is a single borrower of *$itemName*$subItemDescription: ${describeOwner(trigger, claim.owner)} - ${claim.reason}" } else -> { val claimsList = StringBuilder() claims.forEach { (owner, reason, subItem) -> val subItemDescription = subItem?.let { if (it.isNotBlank()) "$it: " else null } ?: "" - claimsList.append("\n${" ".repeat(4)}• $subItemDescription${describeOwner(owner)} - $reason") + claimsList.append("\n${" ".repeat(4)}• $subItemDescription${describeOwner(trigger, owner)} - $reason") } "There are ${claims.size} borrowers of *$itemName*:$claimsList" @@ -189,18 +190,18 @@ class ClaimService @Inject constructor(private val configService: ConfigService, /** * Describe the owner, based on the display mode. */ - private fun describeOwner(owner: String) = when (ItemSettings.ownerDisplayMode) { - OwnerDisplayMode.USERNAME -> sessionService.lookupUsername(owner) - OwnerDisplayMode.REAL_NAME -> sessionService.lookupUserRealName(owner) + private fun describeOwner(trigger: TriggerContext, owner: String) = when (ItemSettings.ownerDisplayMode) { + OwnerDisplayMode.USERNAME -> sessionService.lookupUsername(trigger, owner) + OwnerDisplayMode.REAL_NAME -> sessionService.lookupUserRealName(trigger, owner) } - fun describeAllItemStatus() = "${chatGenerator.greeting()} :simple_smile: ${buildAllItemStatus()}" + fun describeAllItemStatus(trigger: TriggerContext) = "${chatGenerator.greeting()} :simple_smile: ${buildAllItemStatus(trigger)}" - private fun buildAllItemStatus(): String { + private fun buildAllItemStatus(trigger: TriggerContext): String { val status = StringBuilder() configService.actions().keys.forEach { itemName -> - describeItem(itemName) { + describeItem(trigger, itemName) { if (status.isNotEmpty()) { status.append("\n") } diff --git a/bots/slack-deploy/build.gradle b/bots/slack-deploy/build.gradle index 22e88257..09e9d33f 100644 --- a/bots/slack-deploy/build.gradle +++ b/bots/slack-deploy/build.gradle @@ -5,7 +5,7 @@ buildscript { } apply plugin: 'kotlin' -apply plugin: "application" +apply plugin: 'application' dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$version_kotlin" diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/SessionService.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/SessionService.kt index 9228e816..e5b81803 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/SessionService.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/SessionService.kt @@ -7,8 +7,8 @@ import com.gatehill.corebot.action.model.TriggerContext */ interface SessionService { val botUsername: String - fun sendMessage(triggerContext: TriggerContext, message: String) - fun addReaction(triggerContext: TriggerContext, emojiCode: String) - fun lookupUsername(userId: String): String - fun lookupUserRealName(userId: String): String + fun sendMessage(trigger: TriggerContext, message: String) + fun addReaction(trigger: TriggerContext, emojiCode: String) + fun lookupUsername(trigger: TriggerContext, userId: String): String + fun lookupUserRealName(trigger: TriggerContext, userId: String): String } diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt index 3bd8cb1b..f51833b8 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt @@ -17,6 +17,11 @@ class TemplateService @Inject constructor(private val injector: Injector, private val logger = LogManager.getLogger(TemplateService::class.java) + /** + * The regular expression to tokenise messages. + */ + private val messagePartRegex = "\"?( |$)(?=(([^\"]*\"){2})*[^\"]*$)\"?".toRegex() + /** * Unique set of templates. */ @@ -31,7 +36,7 @@ class TemplateService @Inject constructor(private val injector: Injector, * * @param commandOnly - the command, excluding any initial bot reference */ - fun findSatisfiedTemplates(commandOnly: List): Collection { + fun findSatisfiedTemplates(commandOnly: String): Collection { val candidates = fetchCandidates() // include those satisfying templates @@ -52,9 +57,9 @@ class TemplateService @Inject constructor(private val injector: Injector, /** * Filter candidates based on their template (or `tokens` property). */ - private fun filterSimpleTemplates(commandOnly: List, candidates: Collection) = + private fun filterSimpleTemplates(commandOnly: String, candidates: Collection) = candidates.filter { candidate -> - commandOnly.forEach { token -> + splitMessageParts(commandOnly).forEach { token -> // push command elements into the candidate templates if (!candidate.accept(token)) { return@filter false @@ -67,11 +72,14 @@ class TemplateService @Inject constructor(private val injector: Injector, candidate.tokens.isEmpty() } + private fun splitMessageParts(message: String) = + message.trim().split(messagePartRegex).filterNot(String::isBlank) + /** * Filter candidates based on their `templateRegex` property. */ - private fun filterRegexTemplates(commandOnly: List, candidates: Collection) = - candidates.map { it to it.templateRegex!!.matcher(commandOnly.joinToString(" ")) } + private fun filterRegexTemplates(commandOnly: String, candidates: Collection) = + candidates.map { it to it.templateRegex!!.matcher(commandOnly) } .filter { (_, matcher) -> matcher.matches() } .map { (template, matcher) -> injectPlaceholderValues(template, matcher) } .filter { it.onTemplateSatisfied() } diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt index 9efdeac5..52f1e2e9 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt @@ -1,5 +1,6 @@ package com.gatehill.corebot.chat.model.template +import com.gatehill.corebot.action.model.TriggerContext import com.gatehill.corebot.chat.model.action.Action import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.model.ActionConfig @@ -36,12 +37,13 @@ interface ActionTemplate { /** * List the actions from this template. */ - fun buildActions(): List + fun buildActions(trigger: TriggerContext): List /** * Build the message for when this action starts. */ - fun buildStartMessage(options: Map = emptyMap(), + fun buildStartMessage(trigger: TriggerContext, + options: Map = emptyMap(), actionConfig: ActionConfig? = null): String /** diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/BaseActionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/BaseActionTemplate.kt index f3c0d468..b6a3b876 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/BaseActionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/BaseActionTemplate.kt @@ -1,5 +1,6 @@ package com.gatehill.corebot.chat.model.template +import com.gatehill.corebot.action.model.TriggerContext import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.config.model.readActionConfigAttribute import java.util.regex.Pattern @@ -44,7 +45,7 @@ abstract class BaseActionTemplate : ActionTemplate { /** * The response message sent when this actionType is fired. */ - override fun buildStartMessage(options: Map, actionConfig: ActionConfig?) = + override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?) = actionConfig?.let { "I'm working on *${actionConfig.name}*..." } ?: run { "I'm working on it..." } override fun equals(other: Any?): Boolean { diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/BaseLockOptionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/BaseLockOptionTemplate.kt index c7fa484e..664fc8d2 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/BaseLockOptionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/BaseLockOptionTemplate.kt @@ -1,6 +1,7 @@ package com.gatehill.corebot.chat.model.template import com.gatehill.corebot.chat.ChatGenerator +import com.gatehill.corebot.action.model.TriggerContext import com.gatehill.corebot.chat.model.action.CoreActionType import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig @@ -36,7 +37,7 @@ abstract class BaseLockOptionTemplate @Inject constructor(private val configServ return actionConfigs.isNotEmpty() } - override fun buildStartMessage(options: Map, actionConfig: ActionConfig?): String { + override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?): String { return "${chatGenerator.pleaseWait()}, I'm ${if (actionType == CoreActionType.LOCK_OPTION) "locking" else "unlocking"} *$optionName $optionValue*..." } diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/CustomActionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/CustomActionTemplate.kt index 37eca2aa..fe449c37 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/CustomActionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/CustomActionTemplate.kt @@ -1,5 +1,6 @@ package com.gatehill.corebot.chat.model.template +import com.gatehill.corebot.action.model.TriggerContext import com.gatehill.corebot.chat.model.action.Action import com.gatehill.corebot.chat.model.action.CustomAction import com.gatehill.corebot.config.model.ActionConfig @@ -13,12 +14,12 @@ abstract class CustomActionTemplate : BaseActionTemplate() { /** * List the actions from this template. */ - override fun buildActions(): List { + override fun buildActions(trigger: TriggerContext): List { return actionConfigs.map { actionConfig -> val options = transform(actionConfig, placeholderValues) CustomAction(actionType, buildShortDescription(actionConfig), - if (actionMessageMode == ActionMessageMode.INDIVIDUAL && actionConfig.showJobOutcome) buildStartMessage(options, actionConfig) else null, + if (actionMessageMode == ActionMessageMode.INDIVIDUAL && actionConfig.showJobOutcome) buildStartMessage(trigger, options, actionConfig) else null, actionConfig.tags, actionConfig.driver, actionConfig, diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ShowHelpTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ShowHelpTemplate.kt index f8755cdb..4e885516 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ShowHelpTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ShowHelpTemplate.kt @@ -1,6 +1,7 @@ package com.gatehill.corebot.chat.model.template import com.gatehill.corebot.chat.ChatGenerator +import com.gatehill.corebot.action.model.TriggerContext import com.gatehill.corebot.chat.TemplateService import com.gatehill.corebot.chat.model.action.CoreActionType import com.gatehill.corebot.config.model.ActionConfig @@ -18,7 +19,7 @@ class ShowHelpTemplate @Inject constructor(private val templateService: Template override val actionMessageMode = ActionMessageMode.INDIVIDUAL override val tokens = LinkedList(listOf("help")) - override fun buildStartMessage(options: Map, actionConfig: ActionConfig?): String { + override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?): String { return "${chatGenerator.greeting()} :simple_smile: Try one of these:\r\n${templateService.usage()}" } } diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/SystemActionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/SystemActionTemplate.kt index 6b0a81d4..f44dc0f4 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/SystemActionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/SystemActionTemplate.kt @@ -1,5 +1,6 @@ package com.gatehill.corebot.chat.model.template +import com.gatehill.corebot.action.model.TriggerContext import com.gatehill.corebot.chat.model.action.Action import com.gatehill.corebot.chat.model.action.SystemAction import com.gatehill.corebot.config.model.ActionConfig @@ -11,9 +12,9 @@ abstract class SystemActionTemplate : BaseActionTemplate() { override val builtIn = true override val actionConfigs = emptyList() - override fun buildActions(): List { + override fun buildActions(trigger: TriggerContext): List { return listOf(SystemAction(actionType, buildShortDescription(), - if (actionMessageMode == ActionMessageMode.INDIVIDUAL) buildStartMessage() else null)) + if (actionMessageMode == ActionMessageMode.INDIVIDUAL) buildStartMessage(trigger) else null)) } } diff --git a/frontends/common/src/main/kotlin/com/gatehill/corebot/chat/MessageService.kt b/frontends/common/src/main/kotlin/com/gatehill/corebot/chat/MessageService.kt index bd231de6..6da14498 100644 --- a/frontends/common/src/main/kotlin/com/gatehill/corebot/chat/MessageService.kt +++ b/frontends/common/src/main/kotlin/com/gatehill/corebot/chat/MessageService.kt @@ -29,34 +29,41 @@ class MessageService @Inject constructor(private val sessionService: SessionServ private val logger: Logger = LogManager.getLogger(MessageService::class.java) /** - * Allow subclasses to hook into Slack events. + * Handle a message. */ - fun handleMessage(commandOnly: List, trigger: TriggerContext) { + fun handleMessage(trigger: TriggerContext, commandOnly: String) { // indicate busy... sessionService.addReaction(trigger, "hourglass_flowing_sand") - parseMessage(commandOnly)?.let { parsed -> + parseMessage(trigger, commandOnly)?.let { parsed -> logger.info("Handling command '$commandOnly' from ${trigger.username}") parsed.groupStartMessage?.let { sessionService.sendMessage(trigger, it) } parsed.actions.forEach { action -> handleAction(trigger, action, parsed) } } ?: run { logger.warn("Ignored command '$commandOnly' from ${trigger.username}") - sessionService.addReaction(trigger, "question") - printUsage(trigger) + handleUnknownCommand(trigger) } } + /** + * Respond indicating the command was unknown. + */ + fun handleUnknownCommand(trigger: TriggerContext) { + sessionService.addReaction(trigger, "question") + printUsage(trigger) + } + /** * Determine the Action to perform based on the provided command. */ - private fun parseMessage(commandOnly: List): ActionWrapper? { + private fun parseMessage(trigger: TriggerContext, commandOnly: String): ActionWrapper? { try { templateService.findSatisfiedTemplates(commandOnly).let { satisfied -> if (satisfied.size == 1) { return with(satisfied.first()) { - ActionWrapper(buildActions(), - if (actionMessageMode == ActionMessageMode.GROUP) buildStartMessage() else null, + ActionWrapper(buildActions(trigger), + if (actionMessageMode == ActionMessageMode.GROUP) buildStartMessage(trigger) else null, if (actionMessageMode == ActionMessageMode.GROUP) buildCompleteMessage() else null) } } else { diff --git a/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt b/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt index 475e651d..5530f9d3 100644 --- a/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt +++ b/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackChatServiceImpl.kt @@ -2,10 +2,10 @@ package com.gatehill.corebot.chat import com.gatehill.corebot.action.model.TriggerContext import com.gatehill.corebot.config.ChatSettings -import com.ullink.slack.simpleslackapi.SlackPersona import com.ullink.slack.simpleslackapi.listeners.SlackMessagePostedListener import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.Logger +import java.util.regex.Pattern import javax.inject.Inject /** @@ -17,6 +17,7 @@ open class SlackChatServiceImpl @Inject constructor(private val sessionService: private val messageService: MessageService) : ChatService { private val logger: Logger = LogManager.getLogger(SlackChatServiceImpl::class.java) + private val messageMatcher = Pattern.compile("<@(?[a-zA-Z0-9]+)>:?(\\s(?.+))?") override fun listenForEvents() { messagePostedListeners.forEach { sessionService.session.addMessagePostedListener(it) } @@ -43,13 +44,25 @@ open class SlackChatServiceImpl @Inject constructor(private val sessionService: try { // some message events have null content event.messageContent?.trim()?.let { messageContent -> - val splitCmd = messageContent.split("\"?( |$)(?=(([^\"]*\"){2})*[^\"]*$)\"?".toRegex()).filterNot(String::isBlank) + // determine whether message is addressed to the bot + val matcher = messageMatcher.matcher(messageContent) + matcher.takeIf { it.matches() }?.takeIf { it.group("botUser") == session.sessionPersona().id }?.run { - if (splitCmd.isNotEmpty() && isAddressedToBot(session.sessionPersona(), splitCmd[0])) { - // skip element 0, which contains the bot's username - val commandOnly = splitCmd.subList(1, splitCmd.size) - messageService.handleMessage(commandOnly, trigger) + // look for a command token + val command = try { + matcher.group("command") + } catch (e: IllegalStateException) { + null + } + + command?.let { + messageService.handleMessage(trigger, it) + } ?: run { + logger.warn("Ignoring malformed command '$messageContent' from ${trigger.username}") + messageService.handleUnknownCommand(trigger) + } } + } ?: logger.trace("Ignoring event with null message: $event") } catch (e: Exception) { @@ -60,9 +73,3 @@ open class SlackChatServiceImpl @Inject constructor(private val sessionService: } }) } - -/** - * Determine if the message is addressed to the bot. - */ -fun isAddressedToBot(botPersona: SlackPersona, firstToken: String) = - firstToken == "<@${botPersona.id}>" || firstToken == "<@${botPersona.id}>:" diff --git a/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackSessionServiceImpl.kt b/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackSessionServiceImpl.kt index 53e18f6b..9e136be5 100644 --- a/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackSessionServiceImpl.kt +++ b/frontends/slack/src/main/kotlin/com/gatehill/corebot/chat/SlackSessionServiceImpl.kt @@ -50,9 +50,9 @@ open class SlackSessionServiceImpl @Inject constructor(configService: ConfigServ override val botUsername: String get() = session.sessionPersona().userName - override fun sendMessage(triggerContext: TriggerContext, message: String) { - sendMessage(session.findChannelById(triggerContext.channelId), triggerContext.messageTimestamp, - triggerContext.messageThreadTimestamp, message) + override fun sendMessage(trigger: TriggerContext, message: String) { + sendMessage(session.findChannelById(trigger.channelId), trigger.messageTimestamp, + trigger.messageThreadTimestamp, message) } override fun sendMessage(event: SlackMessagePosted, message: String) { @@ -83,13 +83,13 @@ open class SlackSessionServiceImpl @Inject constructor(configService: ConfigServ session.sendMessage(channel, messageBuilder.build()) } - override fun addReaction(triggerContext: TriggerContext, emojiCode: String) { - session.addReactionToMessage(session.findChannelById(triggerContext.channelId), triggerContext.messageTimestamp, emojiCode) + override fun addReaction(trigger: TriggerContext, emojiCode: String) { + session.addReactionToMessage(session.findChannelById(trigger.channelId), trigger.messageTimestamp, emojiCode) } - override fun lookupUsername(userId: String): String = + override fun lookupUsername(trigger: TriggerContext, userId: String): String = session.findUserById(userId).userName - override fun lookupUserRealName(userId: String): String = + override fun lookupUserRealName(trigger: TriggerContext, userId: String): String = session.findUserById(userId).realName } From 80293a293a7e354c8bbf8e9271279cf2b70832a0 Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Sat, 24 Jun 2017 14:41:14 +0100 Subject: [PATCH 29/36] Moves core template parsing logic out of action templates and into template service. --- .../gatehill/corebot/chat/TemplateService.kt | 57 +++++++++++++------ .../chat/model/template/ActionTemplate.kt | 10 +--- .../chat/model/template/BaseActionTemplate.kt | 25 +------- 3 files changed, 44 insertions(+), 48 deletions(-) diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt index f51833b8..cc53f7f9 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt @@ -41,8 +41,8 @@ class TemplateService @Inject constructor(private val injector: Injector, // include those satisfying templates return mutableSetOf().apply { - addAll(filterSimpleTemplates(commandOnly, candidates.filter { null == it.templateRegex })) - addAll(filterRegexTemplates(commandOnly, candidates.filterNot { null == it.templateRegex })) + addAll(filterSimpleTemplates(candidates.filter { null == it.templateRegex }, commandOnly)) + addAll(filterRegexTemplates(candidates.filterNot { null == it.templateRegex }, commandOnly)) } } @@ -57,28 +57,51 @@ class TemplateService @Inject constructor(private val injector: Injector, /** * Filter candidates based on their template (or `tokens` property). */ - private fun filterSimpleTemplates(commandOnly: String, candidates: Collection) = - candidates.filter { candidate -> - splitMessageParts(commandOnly).forEach { token -> - // push command elements into the candidate templates - if (!candidate.accept(token)) { - return@filter false - } - } - return@filter true + private fun filterSimpleTemplates(candidates: Collection, commandOnly: String) = + candidates.filter { candidate -> parseCommand(candidate, commandOnly) } - }.filter { candidate -> - // include only satisfied candidates - candidate.tokens.isEmpty() + /** + * Split the command into elements and return `true` if all were processed successfully. + */ + private fun parseCommand(template: ActionTemplate, command: String): Boolean { + command.trim().split(messagePartRegex).filterNot(String::isBlank).forEach { element -> + // fail as soon as an element is rejected + if (!parseElement(template, element)) { + return false } + } + + // consider only fully satisfied templates + return template.tokens.isEmpty() + } + + /** + * Parse a command element and return `true` if it was accepted. + */ + private fun parseElement(template: ActionTemplate, element: String): Boolean { + if (template.tokens.size == 0) return false + val token = template.tokens.poll() + + val accepted: Boolean + + val match = "\\{(.*)}".toRegex().matchEntire(token) + if (null == match) { + // syntactic sugar + accepted = token.equals(element, ignoreCase = true) - private fun splitMessageParts(message: String) = - message.trim().split(messagePartRegex).filterNot(String::isBlank) + } else { + // option placeholder + template.placeholderValues[match.groupValues[1]] = element + accepted = true + } + + return if (accepted && template.tokens.isEmpty()) template.onTemplateSatisfied() else accepted + } /** * Filter candidates based on their `templateRegex` property. */ - private fun filterRegexTemplates(commandOnly: String, candidates: Collection) = + private fun filterRegexTemplates(candidates: Collection, commandOnly: String) = candidates.map { it to it.templateRegex!!.matcher(commandOnly) } .filter { (_, matcher) -> matcher.matches() } .map { (template, matcher) -> injectPlaceholderValues(template, matcher) } diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt index 52f1e2e9..07976c1b 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt @@ -20,17 +20,13 @@ interface ActionTemplate { val placeholderValues: MutableMap /** - * Convert the action templates to a human-readable String. + * Describe the action templates as a human-readable `String`. */ val actionTemplates: String /** - * Process the token and return true if it was accepted. - */ - fun accept(input: String): Boolean - - /** - * Hook for subclasses. + * Hook for subclasses to do things like manipulate placeholders once + * the template has been fully satisfied. */ fun onTemplateSatisfied() = true diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/BaseActionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/BaseActionTemplate.kt index b6a3b876..be2681ca 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/BaseActionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/BaseActionTemplate.kt @@ -16,26 +16,6 @@ abstract class BaseActionTemplate : ActionTemplate { override val actionTemplates: String get() = readActionConfigAttribute(actionConfigs, ActionConfig::template) - override fun accept(input: String): Boolean { - if (tokens.size == 0) return false - val token = tokens.poll() - - val accepted: Boolean - - val match = "\\{(.*)}".toRegex().matchEntire(token) - if (null == match) { - // syntactic sugar - accepted = token.equals(input, ignoreCase = true) - - } else { - // option placeholder - placeholderValues[match.groupValues[1]] = input - accepted = true - } - - return if (accepted && tokens.isEmpty()) onTemplateSatisfied() else accepted - } - /** * A short, human readable description. */ @@ -51,10 +31,7 @@ abstract class BaseActionTemplate : ActionTemplate { override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is BaseActionTemplate) return false - - if (javaClass != other.javaClass) return false - - return true + return (javaClass != other.javaClass) } override fun hashCode(): Int = javaClass.hashCode() From 2f2e39af5d24de893759c64f16773cea9593e226 Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Mon, 26 Jun 2017 00:19:41 +0100 Subject: [PATCH 30/36] Externalises templates and adds template files for existing backends. --- .../chat/model/template/DisableJobTemplate.kt | 2 - .../chat/model/template/EnableJobTemplate.kt | 2 - .../chat/model/template/TriggerJobTemplate.kt | 8 +- .../src/main/resources/jobs-templates.yml | 7 ++ .../template/BorrowItemAsUserTemplate.kt | 9 +- .../chat/model/template/BorrowItemTemplate.kt | 9 +- .../chat/model/template/EvictItemTemplate.kt | 2 - .../template/EvictUserFromItemTemplate.kt | 13 +- .../chat/model/template/ReturnItemTemplate.kt | 2 - .../chat/model/template/StatusAllTemplate.kt | 2 - .../chat/model/template/StatusItemTemplate.kt | 2 - .../driver/items/service/ClaimService.kt | 3 +- .../src/main/resources/items-templates.yml | 25 ++++ .../kotlin/com/gatehill/corebot/Bootstrap.kt | 6 +- .../kotlin/com/gatehill/corebot/Bootstrap.kt | 2 + build.gradle | 2 +- .../gatehill/corebot/chat/TemplateService.kt | 119 +++++------------- .../chat/model/template/ActionTemplate.kt | 15 ++- .../chat/model/template/BaseActionTemplate.kt | 4 +- .../chat/model/template/LockActionTemplate.kt | 2 - .../chat/model/template/LockOptionTemplate.kt | 2 - .../chat/model/template/ShowHelpTemplate.kt | 4 +- ...JobTemplate.kt => StatusActionTemplate.kt} | 8 +- .../model/template/UnlockActionTemplate.kt | 4 +- .../model/template/UnlockOptionTemplate.kt | 2 - .../corebot/chat/parser/CommandParser.kt | 7 ++ .../corebot/chat/parser/ParserConfig.kt | 3 + .../corebot/chat/parser/RegexParser.kt | 22 ++++ .../corebot/chat/parser/StringParser.kt | 55 ++++++++ .../chat/parser/TemplateConfigService.kt | 81 ++++++++++++ .../src/main/resources/core-templates.yml | 21 ++++ 31 files changed, 297 insertions(+), 148 deletions(-) create mode 100644 backends/deployment/jobs/src/main/resources/jobs-templates.yml create mode 100644 backends/items/src/main/resources/items-templates.yml rename core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/{StatusJobTemplate.kt => StatusActionTemplate.kt} (59%) create mode 100644 core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/CommandParser.kt create mode 100644 core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/ParserConfig.kt create mode 100644 core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/RegexParser.kt create mode 100644 core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/StringParser.kt create mode 100644 core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/TemplateConfigService.kt create mode 100644 core/engine/src/main/resources/core-templates.yml diff --git a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/DisableJobTemplate.kt b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/DisableJobTemplate.kt index fc385f6a..b8fa12fd 100644 --- a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/DisableJobTemplate.kt +++ b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/DisableJobTemplate.kt @@ -2,7 +2,6 @@ package com.gatehill.corebot.chat.model.template import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.ConfigService -import java.util.LinkedList import javax.inject.Inject /** @@ -11,5 +10,4 @@ import javax.inject.Inject class DisableJobTemplate @Inject constructor(configService: ConfigService) : NamedActionTemplate(configService) { override val actionType: ActionType = JobActionType.DISABLE override val actionMessageMode = ActionMessageMode.INDIVIDUAL - override val tokens = LinkedList(listOf("disable", "{${actionPlaceholder}}")) } \ No newline at end of file diff --git a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/EnableJobTemplate.kt b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/EnableJobTemplate.kt index dedbbee5..20de9cf7 100644 --- a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/EnableJobTemplate.kt +++ b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/EnableJobTemplate.kt @@ -2,7 +2,6 @@ package com.gatehill.corebot.chat.model.template import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.ConfigService -import java.util.LinkedList import javax.inject.Inject /** @@ -11,5 +10,4 @@ import javax.inject.Inject class EnableJobTemplate @Inject constructor(configService: ConfigService) : NamedActionTemplate(configService) { override val actionType: ActionType = JobActionType.ENABLE override val actionMessageMode = ActionMessageMode.INDIVIDUAL - override val tokens = LinkedList(listOf("enable", "{${actionPlaceholder}}")) } \ No newline at end of file diff --git a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/TriggerJobTemplate.kt b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/TriggerJobTemplate.kt index c022b56b..14b893f7 100644 --- a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/TriggerJobTemplate.kt +++ b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/TriggerJobTemplate.kt @@ -1,11 +1,10 @@ package com.gatehill.corebot.chat.model.template -import com.gatehill.corebot.chat.ChatGenerator import com.gatehill.corebot.action.model.TriggerContext +import com.gatehill.corebot.chat.ChatGenerator import com.gatehill.corebot.chat.model.action.ActionType +import com.gatehill.corebot.chat.parser.StringParser import com.gatehill.corebot.config.model.ActionConfig -import java.util.LinkedList -import java.util.Queue /** * Triggers job execution. @@ -18,11 +17,10 @@ class TriggerJobTemplate(action: ActionConfig, override val actionType: ActionType = JobActionType.TRIGGER override val actionMessageMode = ActionMessageMode.INDIVIDUAL override val actionConfigs: List - override val tokens: Queue init { this.actionConfigs = mutableListOf(action) - tokens = LinkedList(action.template.split("\\s".toRegex()).filterNot(String::isBlank)) + parsers += StringParser.StringParserConfig(action.template, action.template) } override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?): String { diff --git a/backends/deployment/jobs/src/main/resources/jobs-templates.yml b/backends/deployment/jobs/src/main/resources/jobs-templates.yml new file mode 100644 index 00000000..ab08517e --- /dev/null +++ b/backends/deployment/jobs/src/main/resources/jobs-templates.yml @@ -0,0 +1,7 @@ +# Jobs backend templates. +--- +DisableJobTemplate: + - template: disable {action or tag name} + +EnableJobTemplate: + - template: enable {action or tag name} diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemAsUserTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemAsUserTemplate.kt index 5d27451f..f6ad2737 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemAsUserTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemAsUserTemplate.kt @@ -1,20 +1,17 @@ package com.gatehill.corebot.driver.items.chat.model.template import com.gatehill.corebot.chat.model.action.ActionType +import com.gatehill.corebot.chat.model.template.RegexActionTemplate import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.driver.items.action.model.ItemsActionType -import java.util.LinkedList -import java.util.regex.Pattern import javax.inject.Inject /** * Borrow an item as another user. */ -class BorrowItemAsUserTemplate @Inject constructor(configService: ConfigService) : BorrowItemTemplate(configService) { +class BorrowItemAsUserTemplate @Inject constructor(configService: ConfigService) : BorrowItemTemplate(configService), RegexActionTemplate { override val actionType: ActionType = ItemsActionType.ITEM_BORROW_AS_USER - override val tokens = LinkedList(listOf("as", "{$borrowerPlaceholder}", "borrow", "{$itemPlaceholder}", "{$subItemPlaceholder}", "for", "{$reasonPlaceholder}")) - override val templateRegex: Pattern? - get() = "as\\s\\<@(?<$borrowerPlaceholder>[a-zA-Z0-9]+)\\>\\sborrow\\s+(?<$itemPlaceholder>[a-zA-Z0-9]+)\\s*(?.*)\\s+for\\s+(?.+)".toPattern() + override val placeholderKeys = listOf(borrowerPlaceholder, itemPlaceholder, subItemPlaceholder, reasonPlaceholder) companion object { val borrowerPlaceholder = "borrower" diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt index a46e5566..97e00d9c 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt @@ -2,21 +2,18 @@ package com.gatehill.corebot.driver.items.chat.model.template import com.gatehill.corebot.action.model.TriggerContext import com.gatehill.corebot.chat.model.action.ActionType +import com.gatehill.corebot.chat.model.template.RegexActionTemplate import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.items.action.model.ItemsActionType -import java.util.LinkedList -import java.util.regex.Pattern import javax.inject.Inject /** * Borrow an item. */ -open class BorrowItemTemplate @Inject constructor(configService: ConfigService) : BaseItemTemplate(configService) { +open class BorrowItemTemplate @Inject constructor(configService: ConfigService) : BaseItemTemplate(configService), RegexActionTemplate { override val actionType: ActionType = ItemsActionType.ITEM_BORROW - override val tokens = LinkedList(listOf("borrow", "{$itemPlaceholder}", "{$subItemPlaceholder}", "for", "{$reasonPlaceholder}")) - override val templateRegex: Pattern? - get() = "borrow\\s+(?[a-zA-Z0-9]+)\\s*(?.*)\\s+for\\s+(?.+)".toPattern() + override val placeholderKeys = listOf(itemPlaceholder, subItemPlaceholder, reasonPlaceholder) override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?) = "" diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictItemTemplate.kt index 8591be66..3a472b79 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictItemTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictItemTemplate.kt @@ -5,7 +5,6 @@ import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.items.action.model.ItemsActionType -import java.util.LinkedList import javax.inject.Inject /** @@ -13,6 +12,5 @@ import javax.inject.Inject */ class EvictItemTemplate @Inject constructor(configService: ConfigService) : BaseItemTemplate(configService) { override val actionType: ActionType = ItemsActionType.ITEM_EVICT - override val tokens = LinkedList(listOf("evict", "{$itemPlaceholder}")) override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?) = "" } diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictUserFromItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictUserFromItemTemplate.kt index 5f02216f..f3c2dda2 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictUserFromItemTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictUserFromItemTemplate.kt @@ -1,20 +1,21 @@ package com.gatehill.corebot.driver.items.chat.model.template +import com.gatehill.corebot.action.model.TriggerContext import com.gatehill.corebot.chat.model.action.ActionType +import com.gatehill.corebot.chat.model.template.RegexActionTemplate import com.gatehill.corebot.config.ConfigService +import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.items.action.model.ItemsActionType -import java.util.LinkedList -import java.util.regex.Pattern import javax.inject.Inject /** * Evict a borrower from an item. */ -class EvictUserFromItemTemplate @Inject constructor(configService: ConfigService) : BorrowItemTemplate(configService) { +class EvictUserFromItemTemplate @Inject constructor(configService: ConfigService) : BaseItemTemplate(configService), RegexActionTemplate { override val actionType: ActionType = ItemsActionType.ITEM_EVICT_USER - override val tokens = LinkedList(listOf("evict", "{$borrowerPlaceholder}", "from", "{$itemPlaceholder}")) - override val templateRegex: Pattern? - get() = "evict\\s\\<@(?<$borrowerPlaceholder>[a-zA-Z0-9]+)\\>\\sfrom\\s+(?<$itemPlaceholder>[a-zA-Z0-9]+)".toPattern() + override val placeholderKeys = listOf(borrowerPlaceholder, itemPlaceholder) + + override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?) = "" companion object { val borrowerPlaceholder = "borrower" diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/ReturnItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/ReturnItemTemplate.kt index 1b007bc4..92106a9c 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/ReturnItemTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/ReturnItemTemplate.kt @@ -5,7 +5,6 @@ import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.items.action.model.ItemsActionType -import java.util.LinkedList import javax.inject.Inject /** @@ -13,6 +12,5 @@ import javax.inject.Inject */ class ReturnItemTemplate @Inject constructor(configService: ConfigService) : BaseItemTemplate(configService) { override val actionType: ActionType = ItemsActionType.ITEM_RETURN - override val tokens = LinkedList(listOf("return", "{$itemPlaceholder}")) override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?) = "" } diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusAllTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusAllTemplate.kt index 3eda2663..379654fa 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusAllTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusAllTemplate.kt @@ -7,7 +7,6 @@ import com.gatehill.corebot.chat.model.template.SystemActionTemplate import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.items.action.model.ItemsActionType import com.gatehill.corebot.driver.items.service.ClaimService -import java.util.LinkedList import javax.inject.Inject /** @@ -18,7 +17,6 @@ class StatusAllTemplate @Inject constructor(val claimService: ClaimService) : Sy override val showInUsage = true override val actionMessageMode = ActionMessageMode.INDIVIDUAL override val actionType: ActionType = ItemsActionType.ALL_STATUS - override val tokens = LinkedList(listOf("status")) override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?) = claimService.describeAllItemStatus(trigger) diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusItemTemplate.kt index 5ce4e650..ea1f5586 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusItemTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusItemTemplate.kt @@ -5,7 +5,6 @@ import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.items.action.model.ItemsActionType -import java.util.LinkedList import javax.inject.Inject /** @@ -13,6 +12,5 @@ import javax.inject.Inject */ class StatusItemTemplate @Inject constructor(configService: ConfigService) : BaseItemTemplate(configService) { override val actionType: ActionType = ItemsActionType.ITEM_STATUS - override val tokens = LinkedList(listOf("status", "{$itemPlaceholder}")) override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?) = "" } diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt index a3b0647b..f05caeb2 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt @@ -9,6 +9,7 @@ import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.items.chat.model.template.BorrowItemAsUserTemplate import com.gatehill.corebot.driver.items.chat.model.template.BorrowItemTemplate +import com.gatehill.corebot.driver.items.chat.model.template.EvictUserFromItemTemplate import com.gatehill.corebot.driver.items.config.ItemSettings import com.gatehill.corebot.driver.items.config.OwnerDisplayMode import com.gatehill.corebot.store.DataStore @@ -121,7 +122,7 @@ class ClaimService @Inject constructor(private val configService: ConfigService, synchronized(itemName) { checkItemClaims(action) { claims -> - val borrower = args[BorrowItemAsUserTemplate.borrowerPlaceholder]!! + val borrower = args[EvictUserFromItemTemplate.borrowerPlaceholder]!! when (claims.size) { 1 -> itemClaims.remove(itemName) diff --git a/backends/items/src/main/resources/items-templates.yml b/backends/items/src/main/resources/items-templates.yml new file mode 100644 index 00000000..3e3b062e --- /dev/null +++ b/backends/items/src/main/resources/items-templates.yml @@ -0,0 +1,25 @@ +# Items backend templates. +--- +BorrowItemAsUserTemplate: + - template: /as\s\<@(?[a-zA-Z0-9]+)\>\sborrow\s+(?[a-zA-Z0-9]+)\s*(?.*)\s+for\s+(?.+)/ + usage: as {borrower} borrow {itemName} {optionalSubItemName} for {reason} + +BorrowItemTemplate: + - template: /borrow\s+(?[a-zA-Z0-9]+)\s*(?.*)\s+for\s+(?.+)/ + usage: borrow {itemName} {optionalSubItemName} for {reason} + +EvictItemTemplate: + - template: evict {itemName} + +EvictUserFromItemTemplate: + - template: /evict\s\<@(?[a-zA-Z0-9]+)\>\sfrom\s+(?[a-zA-Z0-9]+)/ + usage: evict {borrower} from {itemName} + +ReturnItemTemplate: + - template: return {itemName} + +StatusAllTemplate: + - template: status + +StatusItemTemplate: + - template: status {itemName} diff --git a/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt b/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt index 26937b35..b686d624 100644 --- a/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt +++ b/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt @@ -7,9 +7,10 @@ import com.gatehill.corebot.chat.model.template.EnableJobTemplate import com.gatehill.corebot.chat.model.template.LockActionTemplate import com.gatehill.corebot.chat.model.template.LockOptionTemplate import com.gatehill.corebot.chat.model.template.ShowHelpTemplate -import com.gatehill.corebot.chat.model.template.StatusJobTemplate +import com.gatehill.corebot.chat.model.template.StatusActionTemplate import com.gatehill.corebot.chat.model.template.UnlockActionTemplate import com.gatehill.corebot.chat.model.template.UnlockOptionTemplate +import com.gatehill.corebot.chat.parser.TemplateConfigService import com.gatehill.corebot.driver.jenkins.action.JenkinsActionDriver import com.gatehill.corebot.driver.rundeck.action.RundeckActionDriver import javax.inject.Inject @@ -25,10 +26,11 @@ class Bootstrap @Inject constructor(actionDriverFactory: ActionDriverFactory, actionDriverFactory.registerDriver("jenkins", JenkinsActionDriver::class.java) // templates + TemplateConfigService.registerClasspathTemplateFile("/jobs-templates.yml") templateService.registerTemplate(ShowHelpTemplate::class.java) templateService.registerTemplate(LockActionTemplate::class.java) templateService.registerTemplate(UnlockActionTemplate::class.java) - templateService.registerTemplate(StatusJobTemplate::class.java) + templateService.registerTemplate(StatusActionTemplate::class.java) templateService.registerTemplate(EnableJobTemplate::class.java) templateService.registerTemplate(DisableJobTemplate::class.java) templateService.registerTemplate(LockOptionTemplate::class.java) diff --git a/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt b/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt index fa6dd9c2..a7d3ae18 100644 --- a/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt +++ b/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt @@ -3,6 +3,7 @@ package com.gatehill.corebot import com.gatehill.corebot.action.driver.ActionDriverFactory import com.gatehill.corebot.chat.TemplateService import com.gatehill.corebot.chat.model.template.ShowHelpTemplate +import com.gatehill.corebot.chat.parser.TemplateConfigService import com.gatehill.corebot.driver.items.action.ItemsActionDriverImpl import com.gatehill.corebot.driver.items.chat.model.template.BorrowItemAsUserTemplate import com.gatehill.corebot.driver.items.chat.model.template.BorrowItemTemplate @@ -23,6 +24,7 @@ class Bootstrap @Inject constructor(actionDriverFactory: ActionDriverFactory, actionDriverFactory.registerDriver("items", ItemsActionDriverImpl::class.java) // templates + TemplateConfigService.registerClasspathTemplateFile("/items-templates.yml") templateService.registerTemplate(ShowHelpTemplate::class.java) templateService.registerTemplate(BorrowItemTemplate::class.java) templateService.registerTemplate(BorrowItemAsUserTemplate::class.java) diff --git a/build.gradle b/build.gradle index 7ae2fc3a..e82cd1ac 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ allprojects { ext.version_spek = '1.1.2' ext.version_kluent = '1.22' ext.version_log4j = '2.5' - ext.version_jackson = '2.8.8' + ext.version_jackson = '2.9.0.pr4' // supports jacksonTypeRef ext.version_retrofit = '2.1.0' ext.version_slackapi = '0c58385' ext.version_guava = '18.0' diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt index cc53f7f9..a2fe0641 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt @@ -1,10 +1,14 @@ package com.gatehill.corebot.chat import com.gatehill.corebot.chat.model.template.ActionTemplate +import com.gatehill.corebot.chat.parser.CommandParser +import com.gatehill.corebot.chat.parser.ParserConfig +import com.gatehill.corebot.chat.parser.RegexParser +import com.gatehill.corebot.chat.parser.StringParser +import com.gatehill.corebot.chat.parser.TemplateConfigService import com.gatehill.corebot.config.ConfigService import com.google.inject.Injector import org.apache.logging.log4j.LogManager -import java.util.regex.Matcher import javax.inject.Inject /** @@ -17,18 +21,13 @@ class TemplateService @Inject constructor(private val injector: Injector, private val logger = LogManager.getLogger(TemplateService::class.java) - /** - * The regular expression to tokenise messages. - */ - private val messagePartRegex = "\"?( |$)(?=(([^\"]*\"){2})*[^\"]*$)\"?".toRegex() - /** * Unique set of templates. */ private val actionTemplates = mutableSetOf>() fun registerTemplate(template: Class) { - actionTemplates.add(template) + actionTemplates += template } /** @@ -36,14 +35,15 @@ class TemplateService @Inject constructor(private val injector: Injector, * * @param commandOnly - the command, excluding any initial bot reference */ - fun findSatisfiedTemplates(commandOnly: String): Collection { - val candidates = fetchCandidates() - - // include those satisfying templates - return mutableSetOf().apply { - addAll(filterSimpleTemplates(candidates.filter { null == it.templateRegex }, commandOnly)) - addAll(filterRegexTemplates(candidates.filterNot { null == it.templateRegex }, commandOnly)) - } + fun findSatisfiedTemplates(commandOnly: String): Collection = fetchCandidates().filter { template -> + template.parsers.any { loadParser(it).parse(it, template, commandOnly) } + }.toSet() + + // TODO cache instances or load as singletons as they're stateless + private fun loadParser(parserConfig: ParserConfig): CommandParser = when (parserConfig) { + is StringParser.StringParserConfig -> injector.getInstance(StringParser::class.java) + is RegexParser.RegexParserConfig -> injector.getInstance(RegexParser::class.java) + else -> throw IllegalStateException("Unsupported parser config: ${parserConfig::javaClass}") } /** @@ -52,85 +52,34 @@ class TemplateService @Inject constructor(private val injector: Injector, private fun fetchCandidates(): Set = mutableSetOf().apply { addAll(actionTemplateConverter.convertConfigToTemplate(configService.actions().values)) addAll(actionTemplates.map({ actionTemplate -> injector.getInstance(actionTemplate) })) - } - /** - * Filter candidates based on their template (or `tokens` property). - */ - private fun filterSimpleTemplates(candidates: Collection, commandOnly: String) = - candidates.filter { candidate -> parseCommand(candidate, commandOnly) } + // populate the parser configurations + forEach { template -> + template.parsers += TemplateConfigService.loadParserConfig(template::class.java) - /** - * Split the command into elements and return `true` if all were processed successfully. - */ - private fun parseCommand(template: ActionTemplate, command: String): Boolean { - command.trim().split(messagePartRegex).filterNot(String::isBlank).forEach { element -> - // fail as soon as an element is rejected - if (!parseElement(template, element)) { - return false + // no parsers have been set + if (template.parsers.isEmpty()) { + logger.warn("No parser configuration found for template: ${template::class.java.simpleName} - action ${template.actionType.name} cannot be invoked") } } - - // consider only fully satisfied templates - return template.tokens.isEmpty() - } - - /** - * Parse a command element and return `true` if it was accepted. - */ - private fun parseElement(template: ActionTemplate, element: String): Boolean { - if (template.tokens.size == 0) return false - val token = template.tokens.poll() - - val accepted: Boolean - - val match = "\\{(.*)}".toRegex().matchEntire(token) - if (null == match) { - // syntactic sugar - accepted = token.equals(element, ignoreCase = true) - - } else { - // option placeholder - template.placeholderValues[match.groupValues[1]] = element - accepted = true - } - - return if (accepted && template.tokens.isEmpty()) template.onTemplateSatisfied() else accepted - } - - /** - * Filter candidates based on their `templateRegex` property. - */ - private fun filterRegexTemplates(candidates: Collection, commandOnly: String) = - candidates.map { it to it.templateRegex!!.matcher(commandOnly) } - .filter { (_, matcher) -> matcher.matches() } - .map { (template, matcher) -> injectPlaceholderValues(template, matcher) } - .filter { it.onTemplateSatisfied() } - - /** - * Fetch the placeholder names from the template, then populate the placeholder values. - * Return the template. - */ - private fun injectPlaceholderValues(template: ActionTemplate, matcher: Matcher): ActionTemplate { - template.tokens.filter { "\\{(.*)}".toRegex().matches(it) } - .map { it.substring(1, it.length - 1) } - .map { it to matcher.group(it) }.toMap() - .let { placeholderValues -> - logger.trace("Placeholder values for ${template.actionType}: $placeholderValues") - template.placeholderValues += placeholderValues - } - - return template } fun usage() = StringBuilder().apply { val sortedCandidates = fetchCandidates().toMutableList().apply { - sortBy { candidate -> candidate.tokens.joinToString(" ") } + sortBy { candidate -> + candidate.parsers + .filter { it.usage != null } + .map { it.usage } + .joinToString("\n") + } } val printTemplate: (ActionTemplate) -> Unit = { candidate -> - val template = candidate.tokens.joinToString(" ") - appendln(); append("_@${sessionService.botUsername} ${template}_") + appendln() + append(candidate.parsers + .filter { it.usage != null } + .map { "_@${sessionService.botUsername} ${it.usage}_" } + .joinToString("\n")) } val customActions = sortedCandidates.filter(ActionTemplate::showInUsage).filterNot(ActionTemplate::builtIn) @@ -139,9 +88,7 @@ class TemplateService @Inject constructor(private val injector: Injector, customActions.forEach(printTemplate) } - if (isNotEmpty()) { - appendln(); appendln() - } + if (isNotEmpty()) repeat(2) { appendln() } val builtInActions = sortedCandidates.filter(ActionTemplate::showInUsage).filter(ActionTemplate::builtIn) if (builtInActions.isNotEmpty()) { diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt index 07976c1b..09d5c9db 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt @@ -3,19 +3,17 @@ package com.gatehill.corebot.chat.model.template import com.gatehill.corebot.action.model.TriggerContext import com.gatehill.corebot.chat.model.action.Action import com.gatehill.corebot.chat.model.action.ActionType +import com.gatehill.corebot.chat.parser.ParserConfig import com.gatehill.corebot.config.model.ActionConfig -import java.util.Queue -import java.util.regex.Pattern /** * An abstract representation of a templated action. */ interface ActionTemplate { + val parsers: MutableList val builtIn: Boolean val showInUsage: Boolean val actionType: ActionType - val tokens: Queue - val templateRegex: Pattern? val actionMessageMode: ActionMessageMode val placeholderValues: MutableMap @@ -48,6 +46,15 @@ interface ActionTemplate { fun buildCompleteMessage(): String = "" } +/** + * Must be implemented by templates that can have regex parser config. + */ +interface PlaceholderKeysTemplate { + val placeholderKeys: List +} + +interface RegexActionTemplate : ActionTemplate, PlaceholderKeysTemplate + enum class ActionMessageMode { INDIVIDUAL, GROUP; diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/BaseActionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/BaseActionTemplate.kt index be2681ca..db10db65 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/BaseActionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/BaseActionTemplate.kt @@ -1,17 +1,17 @@ package com.gatehill.corebot.chat.model.template import com.gatehill.corebot.action.model.TriggerContext +import com.gatehill.corebot.chat.parser.ParserConfig import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.config.model.readActionConfigAttribute -import java.util.regex.Pattern /** * Parses tokens into placeholder values. */ abstract class BaseActionTemplate : ActionTemplate { + override val parsers = mutableListOf() protected abstract val actionConfigs: List override val placeholderValues = mutableMapOf() - override val templateRegex: Pattern? = null override val actionTemplates: String get() = readActionConfigAttribute(actionConfigs, ActionConfig::template) diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/LockActionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/LockActionTemplate.kt index 97c9781f..9bb0e97a 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/LockActionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/LockActionTemplate.kt @@ -3,7 +3,6 @@ package com.gatehill.corebot.chat.model.template import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.chat.model.action.CoreActionType import com.gatehill.corebot.config.ConfigService -import java.util.LinkedList import javax.inject.Inject /** @@ -12,5 +11,4 @@ import javax.inject.Inject class LockActionTemplate @Inject constructor(configService: ConfigService) : NamedActionTemplate(configService) { override val actionType: ActionType = CoreActionType.LOCK_ACTION override val actionMessageMode = ActionMessageMode.INDIVIDUAL - override val tokens = LinkedList(listOf("lock", "{${actionPlaceholder}}")) } \ No newline at end of file diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/LockOptionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/LockOptionTemplate.kt index e0198c5a..0a490db9 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/LockOptionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/LockOptionTemplate.kt @@ -4,7 +4,6 @@ import com.gatehill.corebot.chat.ChatGenerator import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.chat.model.action.CoreActionType import com.gatehill.corebot.config.ConfigService -import java.util.LinkedList import javax.inject.Inject /** @@ -14,5 +13,4 @@ class LockOptionTemplate @Inject constructor(configService: ConfigService, chatGenerator: ChatGenerator) : BaseLockOptionTemplate(configService, chatGenerator) { override val actionType: ActionType = CoreActionType.LOCK_OPTION - override val tokens = LinkedList(listOf("lock", "{$optionNamePlaceholder}", "{$optionValuePlaceholder}")) } diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ShowHelpTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ShowHelpTemplate.kt index 4e885516..93600e75 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ShowHelpTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ShowHelpTemplate.kt @@ -1,11 +1,10 @@ package com.gatehill.corebot.chat.model.template -import com.gatehill.corebot.chat.ChatGenerator import com.gatehill.corebot.action.model.TriggerContext +import com.gatehill.corebot.chat.ChatGenerator import com.gatehill.corebot.chat.TemplateService import com.gatehill.corebot.chat.model.action.CoreActionType import com.gatehill.corebot.config.model.ActionConfig -import java.util.LinkedList import javax.inject.Inject /** @@ -17,7 +16,6 @@ class ShowHelpTemplate @Inject constructor(private val templateService: Template override val showInUsage = false override val actionType = CoreActionType.HELP override val actionMessageMode = ActionMessageMode.INDIVIDUAL - override val tokens = LinkedList(listOf("help")) override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?): String { return "${chatGenerator.greeting()} :simple_smile: Try one of these:\r\n${templateService.usage()}" diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/StatusJobTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/StatusActionTemplate.kt similarity index 59% rename from core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/StatusJobTemplate.kt rename to core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/StatusActionTemplate.kt index b7940348..af0014ae 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/StatusJobTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/StatusActionTemplate.kt @@ -3,14 +3,12 @@ package com.gatehill.corebot.chat.model.template import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.chat.model.action.CoreActionType import com.gatehill.corebot.config.ConfigService -import java.util.LinkedList import javax.inject.Inject /** - * Prints status information about a job. + * Prints status information about an action. */ -class StatusJobTemplate @Inject constructor(configService: ConfigService) : NamedActionTemplate(configService) { +class StatusActionTemplate @Inject constructor(configService: ConfigService) : NamedActionTemplate(configService) { override val actionType: ActionType = CoreActionType.STATUS override val actionMessageMode = ActionMessageMode.INDIVIDUAL - override val tokens = LinkedList(listOf("status", "{$actionPlaceholder}")) -} \ No newline at end of file +} diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/UnlockActionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/UnlockActionTemplate.kt index 71209eb6..47652096 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/UnlockActionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/UnlockActionTemplate.kt @@ -3,7 +3,6 @@ package com.gatehill.corebot.chat.model.template import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.chat.model.action.CoreActionType import com.gatehill.corebot.config.ConfigService -import java.util.LinkedList import javax.inject.Inject /** @@ -12,5 +11,4 @@ import javax.inject.Inject class UnlockActionTemplate @Inject constructor(configService: ConfigService) : NamedActionTemplate(configService) { override val actionType: ActionType = CoreActionType.UNLOCK_ACTION override val actionMessageMode = ActionMessageMode.INDIVIDUAL - override val tokens = LinkedList(listOf("unlock", "{${actionPlaceholder}}")) -} \ No newline at end of file +} diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/UnlockOptionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/UnlockOptionTemplate.kt index 41bb9d80..eab6c4d5 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/UnlockOptionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/UnlockOptionTemplate.kt @@ -4,7 +4,6 @@ import com.gatehill.corebot.chat.ChatGenerator import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.chat.model.action.CoreActionType import com.gatehill.corebot.config.ConfigService -import java.util.LinkedList import javax.inject.Inject /** @@ -14,5 +13,4 @@ class UnlockOptionTemplate @Inject constructor(configService: ConfigService, chatGenerator: ChatGenerator) : BaseLockOptionTemplate(configService, chatGenerator) { override val actionType: ActionType = CoreActionType.UNLOCK_OPTION - override val tokens = LinkedList(listOf("unlock", "{$optionNamePlaceholder}", "{$optionValuePlaceholder}")) } diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/CommandParser.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/CommandParser.kt new file mode 100644 index 00000000..239a0728 --- /dev/null +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/CommandParser.kt @@ -0,0 +1,7 @@ +package com.gatehill.corebot.chat.parser + +import com.gatehill.corebot.chat.model.template.ActionTemplate + +interface CommandParser { + fun parse(config: ParserConfig, template: ActionTemplate, command: String): Boolean +} diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/ParserConfig.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/ParserConfig.kt new file mode 100644 index 00000000..7475dece --- /dev/null +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/ParserConfig.kt @@ -0,0 +1,3 @@ +package com.gatehill.corebot.chat.parser + +abstract class ParserConfig(val usage: String?) diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/RegexParser.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/RegexParser.kt new file mode 100644 index 00000000..07f725e9 --- /dev/null +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/RegexParser.kt @@ -0,0 +1,22 @@ +package com.gatehill.corebot.chat.parser + +import com.gatehill.corebot.chat.model.template.ActionTemplate +import com.gatehill.corebot.chat.model.template.RegexActionTemplate +import java.util.regex.Pattern + +class RegexParser : CommandParser { + class RegexParserConfig(val template: Pattern, + usage: String?) : ParserConfig(usage) + + override fun parse(config: ParserConfig, template: ActionTemplate, command: String): Boolean = + parseCommand(config as RegexParserConfig, template as RegexActionTemplate, command) + + /** + * Filter candidates based on their templates. + */ + private fun parseCommand(config: RegexParserConfig, template: RegexActionTemplate, command: String) = + config.template.matcher(command).takeIf { it.matches() }?.let { matcher -> + template.placeholderValues += template.placeholderKeys.map { it to matcher.group(it) }.toMap() + template.onTemplateSatisfied() + } ?: false +} diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/StringParser.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/StringParser.kt new file mode 100644 index 00000000..91c8fba8 --- /dev/null +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/StringParser.kt @@ -0,0 +1,55 @@ +package com.gatehill.corebot.chat.parser + +import com.gatehill.corebot.chat.model.template.ActionTemplate +import java.util.LinkedList +import java.util.Queue + +/** + * The regular expression to tokenise messages. + */ +private val messagePartRegex = "\"?( |$)(?=(([^\"]*\"){2})*[^\"]*$)\"?".toRegex() + +class StringParser : CommandParser { + class StringParserConfig(val template: String, + usage: String?) : ParserConfig(usage) + + override fun parse(config: ParserConfig, template: ActionTemplate, command: String) = + parseCommand(config as StringParserConfig, template, command) + + /** + * Split the command into elements and return `true` if all were processed successfully. + */ + private fun parseCommand(config: StringParserConfig, template: ActionTemplate, command: String): Boolean { + val tokens = LinkedList(config.template.split("\\s".toRegex())) + + command.trim().split(messagePartRegex).filterNot(String::isBlank).forEach { element -> + // fail as soon as an element is rejected + if (!parseElement(template, tokens, element)) { + return false + } + } + + // consider only fully satisfied templates + return tokens.isEmpty() + } + + /** + * Parse a command element and return `true` if it was accepted. + */ + private fun parseElement(template: ActionTemplate, tokens: Queue, element: String): Boolean { + if (tokens.size == 0) return false + val token = tokens.poll() + + val accepted = "\\{(.*)}".toRegex().matchEntire(token)?.let { match -> + // option placeholder + template.placeholderValues[match.groupValues[1]] = element + true + + } ?: run { + // syntactic sugar + token.equals(element, ignoreCase = true) + } + + return if (accepted && tokens.isEmpty()) template.onTemplateSatisfied() else accepted + } +} diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/TemplateConfigService.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/TemplateConfigService.kt new file mode 100644 index 00000000..ca24e087 --- /dev/null +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/TemplateConfigService.kt @@ -0,0 +1,81 @@ +package com.gatehill.corebot.chat.parser + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.gatehill.corebot.chat.model.template.ActionTemplate +import com.gatehill.corebot.util.yamlMapper +import java.io.InputStream +import java.nio.file.Path +import java.nio.file.Paths +import java.util.regex.Pattern + +object TemplateConfigService { + private data class RawTemplateConfig(val template: String, + val usage: String?) + + private const val classpathPrefix = "classpath:" + private val templateFiles = mutableListOf() + + init { + registerClasspathTemplateFile("/core-templates.yml") + } + + /** + * Return a consolidated `Map` of configurations from all `templateFiles`. + */ + private val allConfigs: Map> by lazy { + val configMap = mutableMapOf>() + + templateFiles.map { + if (it.startsWith(classpathPrefix)) { + TemplateConfigService::class.java.getResourceAsStream(it.substring(classpathPrefix.length)) + } else { + Paths.get(it).toFile().inputStream() + }.use { + readAndMerge(configMap, it) + } + } + + configMap + } + + private fun readAndMerge(configMap: MutableMap>, fileStream: InputStream) { + readTemplateFile(fileStream)?.forEach { (key, value) -> + configMap.merge(key, value) { value1, value2 -> + value1.union(value2) + } + } + } + + private fun readTemplateFile(fileStream: InputStream): Map>? = + yamlMapper.readValue>>(fileStream, + jacksonTypeRef>>()) + + fun loadParserConfig(templateName: String): List = + allConfigs.filterKeys { it == templateName }.values.flatMap { config -> + config.map { + // TODO use regex instead + if (it.template.startsWith("/") && it.template.endsWith("/")) { + RegexParser.RegexParserConfig( + template = Pattern.compile(it.template.substring(1, it.template.length - 1)), + usage = it.usage + ) + } else { + StringParser.StringParserConfig( + template = it.template, + usage = it.usage ?: it.template + ) + } + } + } + + fun loadParserConfig(templateClass: Class) = + loadParserConfig(templateClass.simpleName) + + fun registerClasspathTemplateFile(classpathFile: String) { + templateFiles += "$classpathPrefix$classpathFile" + } + + fun registerFilesystemTemplateFile(file: Path) { + templateFiles += file.toAbsolutePath().toString() + } +} diff --git a/core/engine/src/main/resources/core-templates.yml b/core/engine/src/main/resources/core-templates.yml new file mode 100644 index 00000000..583c5712 --- /dev/null +++ b/core/engine/src/main/resources/core-templates.yml @@ -0,0 +1,21 @@ +# Core templates. +--- +ShowHelpTemplate: + - template: help + - template: usage + +LockActionTemplate: + - template: lock {action or tag name} + +UnlockActionTemplate: + - template: unlock {action or tag name} + +StatusActionTemplate: + - template: status {action or tag name} + - template: status of {action or tag name} + +LockOptionTemplate: + - template: lock {option name} {option value} + +UnlockOptionTemplate: + - template: unlock {option name} {option value} From 996cf068441198b7e92c43d7510c4509ff240148 Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Thu, 29 Jun 2017 23:31:50 +0100 Subject: [PATCH 31/36] Dependency injection tidy up. --- .../deploy/TriggerActionTemplateConverter.kt | 2 +- .../main/kotlin/com/gatehill/corebot/Bot.kt | 47 ++++--------------- .../com/gatehill/corebot/CommonBotModule.kt | 35 ++++++++++++++ .../kotlin/com/gatehill/corebot/Bootstrap.kt | 9 ++-- .../main/kotlin/com/gatehill/corebot/Main.kt | 2 +- .../kotlin/com/gatehill/corebot/Bootstrap.kt | 9 ++-- .../main/kotlin/com/gatehill/corebot/Main.kt | 4 +- .../kotlin/com/gatehill/corebot/CoreModule.kt | 19 ++++++++ .../chat/model/template/ActionTemplate.kt | 1 + .../chat/model/template/ShowHelpTemplate.kt | 2 +- .../{ => template}/ActionTemplateConverter.kt | 5 +- .../TemplateConfigService.kt | 14 ++++-- .../chat/{ => template}/TemplateService.kt | 26 ++++++---- .../gatehill/corebot/chat/MessageService.kt | 1 + 14 files changed, 111 insertions(+), 65 deletions(-) create mode 100644 bots/common/src/main/kotlin/com/gatehill/corebot/CommonBotModule.kt create mode 100644 core/engine/src/main/kotlin/com/gatehill/corebot/CoreModule.kt rename core/engine/src/main/kotlin/com/gatehill/corebot/chat/{ => template}/ActionTemplateConverter.kt (83%) rename core/engine/src/main/kotlin/com/gatehill/corebot/chat/{parser => template}/TemplateConfigService.kt (88%) rename core/engine/src/main/kotlin/com/gatehill/corebot/chat/{ => template}/TemplateService.kt (82%) diff --git a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/deploy/TriggerActionTemplateConverter.kt b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/deploy/TriggerActionTemplateConverter.kt index b6227691..c1a13b2e 100644 --- a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/deploy/TriggerActionTemplateConverter.kt +++ b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/deploy/TriggerActionTemplateConverter.kt @@ -1,6 +1,6 @@ package com.gatehill.corebot.deploy -import com.gatehill.corebot.chat.ActionTemplateConverter +import com.gatehill.corebot.chat.template.ActionTemplateConverter import com.gatehill.corebot.chat.ChatGenerator import com.gatehill.corebot.chat.model.template.ActionTemplate import com.gatehill.corebot.chat.model.template.TriggerJobTemplate diff --git a/bots/common/src/main/kotlin/com/gatehill/corebot/Bot.kt b/bots/common/src/main/kotlin/com/gatehill/corebot/Bot.kt index e80d3c74..89204140 100644 --- a/bots/common/src/main/kotlin/com/gatehill/corebot/Bot.kt +++ b/bots/common/src/main/kotlin/com/gatehill/corebot/Bot.kt @@ -1,16 +1,6 @@ package com.gatehill.corebot -import com.gatehill.corebot.action.ActionOutcomeService -import com.gatehill.corebot.action.ActionOutcomeServiceImpl -import com.gatehill.corebot.action.ActionPerformService -import com.gatehill.corebot.action.DirectActionPerformServiceImpl -import com.gatehill.corebot.action.driver.ActionDriverFactory -import com.gatehill.corebot.chat.ChatGenerator import com.gatehill.corebot.chat.ChatService -import com.gatehill.corebot.chat.TemplateService -import com.gatehill.corebot.config.ConfigService -import com.gatehill.corebot.config.ConfigServiceImpl -import com.gatehill.corebot.security.AuthorisationService import com.google.inject.AbstractModule import com.google.inject.Guice.createInjector import com.google.inject.Module @@ -42,33 +32,16 @@ class Bot @Inject constructor(private val chatService: ChatService) { /** * Construct a new `Bot` and wire up its dependencies. */ - fun build(vararg extensionModules: Module): Bot = - createInjector(extensionModules).getInstance(Bot::class.java) - - /** - * Set up dependency injection. - */ - private fun createInjector(extensionModules: Array) = createInjector(object : AbstractModule() { - override fun configure() { - // utility - bind(ConfigService::class.java).to(ConfigServiceImpl::class.java).asSingleton() - bind(AuthorisationService::class.java).asSingleton() - - // chat - bind(TemplateService::class.java).asSingleton() - bind(ChatGenerator::class.java).asSingleton() - - // actions - bind(ActionPerformService::class.java).to(DirectActionPerformServiceImpl::class.java) - bind(ActionDriverFactory::class.java).asSingleton() - bind(ActionOutcomeService::class.java).to(ActionOutcomeServiceImpl::class.java) - - // extension point - extensionModules.forEach { - logger.debug("Installing injection module: ${it.javaClass.canonicalName}") - install(it) + fun build(vararg extensionModules: Module): Bot { + val injector = createInjector(CoreModule(), CommonBotModule(), object : AbstractModule() { + override fun configure() { + extensionModules.forEach { + logger.debug("Installing injection module: ${it.javaClass.canonicalName}") + install(it) + } } - } - }) + }) + return injector.getInstance(Bot::class.java) + } } } diff --git a/bots/common/src/main/kotlin/com/gatehill/corebot/CommonBotModule.kt b/bots/common/src/main/kotlin/com/gatehill/corebot/CommonBotModule.kt new file mode 100644 index 00000000..bad9301a --- /dev/null +++ b/bots/common/src/main/kotlin/com/gatehill/corebot/CommonBotModule.kt @@ -0,0 +1,35 @@ +package com.gatehill.corebot + +import com.gatehill.corebot.action.ActionOutcomeService +import com.gatehill.corebot.action.ActionOutcomeServiceImpl +import com.gatehill.corebot.action.ActionPerformService +import com.gatehill.corebot.action.DirectActionPerformServiceImpl +import com.gatehill.corebot.action.driver.ActionDriverFactory +import com.gatehill.corebot.chat.ChatGenerator +import com.gatehill.corebot.chat.template.TemplateService +import com.gatehill.corebot.config.ConfigService +import com.gatehill.corebot.config.ConfigServiceImpl +import com.gatehill.corebot.security.AuthorisationService +import com.google.inject.AbstractModule + +/** + * Common bot bindings. + * + * @author Pete Cornish {@literal } + */ +class CommonBotModule() : AbstractModule() { + override fun configure() { + // utility + bind(ConfigService::class.java).to(ConfigServiceImpl::class.java).asSingleton() + bind(AuthorisationService::class.java).asSingleton() + + // chat + bind(TemplateService::class.java).asSingleton() + bind(ChatGenerator::class.java).asSingleton() + + // actions + bind(ActionPerformService::class.java).to(DirectActionPerformServiceImpl::class.java) + bind(ActionDriverFactory::class.java).asSingleton() + bind(ActionOutcomeService::class.java).to(ActionOutcomeServiceImpl::class.java) + } +} diff --git a/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt b/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt index b686d624..f5d3381c 100644 --- a/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt +++ b/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt @@ -1,7 +1,6 @@ package com.gatehill.corebot import com.gatehill.corebot.action.driver.ActionDriverFactory -import com.gatehill.corebot.chat.TemplateService import com.gatehill.corebot.chat.model.template.DisableJobTemplate import com.gatehill.corebot.chat.model.template.EnableJobTemplate import com.gatehill.corebot.chat.model.template.LockActionTemplate @@ -10,7 +9,8 @@ import com.gatehill.corebot.chat.model.template.ShowHelpTemplate import com.gatehill.corebot.chat.model.template.StatusActionTemplate import com.gatehill.corebot.chat.model.template.UnlockActionTemplate import com.gatehill.corebot.chat.model.template.UnlockOptionTemplate -import com.gatehill.corebot.chat.parser.TemplateConfigService +import com.gatehill.corebot.chat.template.TemplateConfigService +import com.gatehill.corebot.chat.template.TemplateService import com.gatehill.corebot.driver.jenkins.action.JenkinsActionDriver import com.gatehill.corebot.driver.rundeck.action.RundeckActionDriver import javax.inject.Inject @@ -19,14 +19,15 @@ import javax.inject.Inject * @author Pete Cornish {@literal } */ class Bootstrap @Inject constructor(actionDriverFactory: ActionDriverFactory, - templateService: TemplateService) { + templateService: TemplateService, + templateConfigService: TemplateConfigService) { init { // drivers actionDriverFactory.registerDriver("rundeck", RundeckActionDriver::class.java) actionDriverFactory.registerDriver("jenkins", JenkinsActionDriver::class.java) // templates - TemplateConfigService.registerClasspathTemplateFile("/jobs-templates.yml") + templateConfigService.registerClasspathTemplateFile("/jobs-templates.yml") templateService.registerTemplate(ShowHelpTemplate::class.java) templateService.registerTemplate(LockActionTemplate::class.java) templateService.registerTemplate(UnlockActionTemplate::class.java) diff --git a/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Main.kt b/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Main.kt index a158257f..915393b2 100644 --- a/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Main.kt +++ b/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Main.kt @@ -1,7 +1,7 @@ package com.gatehill.corebot import com.gatehill.corebot.action.LockService -import com.gatehill.corebot.chat.ActionTemplateConverter +import com.gatehill.corebot.chat.template.ActionTemplateConverter import com.gatehill.corebot.deploy.TriggerActionTemplateConverter import com.gatehill.corebot.driver.jenkins.JenkinsDriverModule import com.gatehill.corebot.driver.rundeck.RundeckDriverModule diff --git a/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt b/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt index a7d3ae18..a56ecf50 100644 --- a/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt +++ b/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt @@ -1,9 +1,9 @@ package com.gatehill.corebot import com.gatehill.corebot.action.driver.ActionDriverFactory -import com.gatehill.corebot.chat.TemplateService import com.gatehill.corebot.chat.model.template.ShowHelpTemplate -import com.gatehill.corebot.chat.parser.TemplateConfigService +import com.gatehill.corebot.chat.template.TemplateConfigService +import com.gatehill.corebot.chat.template.TemplateService import com.gatehill.corebot.driver.items.action.ItemsActionDriverImpl import com.gatehill.corebot.driver.items.chat.model.template.BorrowItemAsUserTemplate import com.gatehill.corebot.driver.items.chat.model.template.BorrowItemTemplate @@ -18,13 +18,14 @@ import javax.inject.Inject * @author Pete Cornish {@literal } */ class Bootstrap @Inject constructor(actionDriverFactory: ActionDriverFactory, - templateService: TemplateService) { + templateService: TemplateService, + templateConfigService: TemplateConfigService) { init { // drivers actionDriverFactory.registerDriver("items", ItemsActionDriverImpl::class.java) // templates - TemplateConfigService.registerClasspathTemplateFile("/items-templates.yml") + templateConfigService.registerClasspathTemplateFile("/items-templates.yml") templateService.registerTemplate(ShowHelpTemplate::class.java) templateService.registerTemplate(BorrowItemTemplate::class.java) templateService.registerTemplate(BorrowItemAsUserTemplate::class.java) diff --git a/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Main.kt b/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Main.kt index 1b8344b3..c24f0501 100644 --- a/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Main.kt +++ b/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Main.kt @@ -1,7 +1,7 @@ package com.gatehill.corebot -import com.gatehill.corebot.chat.ActionTemplateConverter -import com.gatehill.corebot.chat.NoOpActionTemplateConverter +import com.gatehill.corebot.chat.template.ActionTemplateConverter +import com.gatehill.corebot.chat.template.NoOpActionTemplateConverter import com.gatehill.corebot.driver.items.ItemsDriverModule import com.gatehill.corebot.store.DataStoreModule import com.google.inject.AbstractModule diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/CoreModule.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/CoreModule.kt new file mode 100644 index 00000000..8bfa28ff --- /dev/null +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/CoreModule.kt @@ -0,0 +1,19 @@ +package com.gatehill.corebot + +import com.gatehill.corebot.chat.parser.RegexParser +import com.gatehill.corebot.chat.parser.StringParser +import com.gatehill.corebot.chat.template.TemplateConfigService +import com.google.inject.AbstractModule + +/** + * Core bindings. + * + * @author Pete Cornish {@literal } + */ +class CoreModule : AbstractModule() { + override fun configure() { + bind(TemplateConfigService::class.java).asSingleton() + bind(StringParser::class.java).asSingleton() + bind(RegexParser::class.java).asSingleton() + } +} diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt index 09d5c9db..6e8ba9f2 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt @@ -53,6 +53,7 @@ interface PlaceholderKeysTemplate { val placeholderKeys: List } +// TODO replace this with an annotation, which includes the `placeholderKeys` interface RegexActionTemplate : ActionTemplate, PlaceholderKeysTemplate enum class ActionMessageMode { diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ShowHelpTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ShowHelpTemplate.kt index 93600e75..c9c34e92 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ShowHelpTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ShowHelpTemplate.kt @@ -2,7 +2,7 @@ package com.gatehill.corebot.chat.model.template import com.gatehill.corebot.action.model.TriggerContext import com.gatehill.corebot.chat.ChatGenerator -import com.gatehill.corebot.chat.TemplateService +import com.gatehill.corebot.chat.template.TemplateService import com.gatehill.corebot.chat.model.action.CoreActionType import com.gatehill.corebot.config.model.ActionConfig import javax.inject.Inject diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/ActionTemplateConverter.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/ActionTemplateConverter.kt similarity index 83% rename from core/engine/src/main/kotlin/com/gatehill/corebot/chat/ActionTemplateConverter.kt rename to core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/ActionTemplateConverter.kt index 7e9fb638..dc8924ad 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/ActionTemplateConverter.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/ActionTemplateConverter.kt @@ -1,4 +1,4 @@ -package com.gatehill.corebot.chat +package com.gatehill.corebot.chat.template import com.gatehill.corebot.chat.model.template.ActionTemplate import com.gatehill.corebot.config.model.ActionConfig @@ -10,6 +10,9 @@ interface ActionTemplateConverter { fun convertConfigToTemplate(configs: Iterable): Collection } +/** + * An implementation that returns an empty `List`. + */ class NoOpActionTemplateConverter : ActionTemplateConverter { override fun convertConfigToTemplate(configs: Iterable): Collection = emptyList() } diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/TemplateConfigService.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/TemplateConfigService.kt similarity index 88% rename from core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/TemplateConfigService.kt rename to core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/TemplateConfigService.kt index ca24e087..7f9bdd2d 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/TemplateConfigService.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/TemplateConfigService.kt @@ -1,18 +1,24 @@ -package com.gatehill.corebot.chat.parser +package com.gatehill.corebot.chat.template import com.fasterxml.jackson.module.kotlin.jacksonTypeRef import com.gatehill.corebot.chat.model.template.ActionTemplate +import com.gatehill.corebot.chat.parser.ParserConfig +import com.gatehill.corebot.chat.parser.RegexParser +import com.gatehill.corebot.chat.parser.StringParser import com.gatehill.corebot.util.yamlMapper import java.io.InputStream import java.nio.file.Path import java.nio.file.Paths import java.util.regex.Pattern -object TemplateConfigService { +/** + * @author Pete Cornish {@literal } + */ +class TemplateConfigService { private data class RawTemplateConfig(val template: String, val usage: String?) - private const val classpathPrefix = "classpath:" + private val classpathPrefix = "classpath:" private val templateFiles = mutableListOf() init { @@ -25,7 +31,7 @@ object TemplateConfigService { private val allConfigs: Map> by lazy { val configMap = mutableMapOf>() - templateFiles.map { + templateFiles.forEach { if (it.startsWith(classpathPrefix)) { TemplateConfigService::class.java.getResourceAsStream(it.substring(classpathPrefix.length)) } else { diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/TemplateService.kt similarity index 82% rename from core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt rename to core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/TemplateService.kt index a2fe0641..676fc65a 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/TemplateService.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/TemplateService.kt @@ -1,11 +1,11 @@ -package com.gatehill.corebot.chat +package com.gatehill.corebot.chat.template +import com.gatehill.corebot.chat.SessionService import com.gatehill.corebot.chat.model.template.ActionTemplate import com.gatehill.corebot.chat.parser.CommandParser import com.gatehill.corebot.chat.parser.ParserConfig import com.gatehill.corebot.chat.parser.RegexParser import com.gatehill.corebot.chat.parser.StringParser -import com.gatehill.corebot.chat.parser.TemplateConfigService import com.gatehill.corebot.config.ConfigService import com.google.inject.Injector import org.apache.logging.log4j.LogManager @@ -17,7 +17,8 @@ import javax.inject.Inject class TemplateService @Inject constructor(private val injector: Injector, private val configService: ConfigService, private val sessionService: SessionService, - private val actionTemplateConverter: ActionTemplateConverter) { + private val actionTemplateConverter: ActionTemplateConverter, + private val templateConfigService: TemplateConfigService) { private val logger = LogManager.getLogger(TemplateService::class.java) @@ -35,19 +36,21 @@ class TemplateService @Inject constructor(private val injector: Injector, * * @param commandOnly - the command, excluding any initial bot reference */ - fun findSatisfiedTemplates(commandOnly: String): Collection = fetchCandidates().filter { template -> - template.parsers.any { loadParser(it).parse(it, template, commandOnly) } - }.toSet() + fun findSatisfiedTemplates(commandOnly: String): Collection = fetchCandidates() + .filter { template -> template.parsers.any { loadParser(it).parse(it, template, commandOnly) } } + .toSet() - // TODO cache instances or load as singletons as they're stateless + /** + * Load the `CommandParser` strategy for the given configuration. + */ private fun loadParser(parserConfig: ParserConfig): CommandParser = when (parserConfig) { is StringParser.StringParserConfig -> injector.getInstance(StringParser::class.java) is RegexParser.RegexParserConfig -> injector.getInstance(RegexParser::class.java) - else -> throw IllegalStateException("Unsupported parser config: ${parserConfig::javaClass}") + else -> throw UnsupportedOperationException("Unsupported parser config: ${parserConfig::class.java.canonicalName}") } /** - * Returns a new `Set` of candidates. + * Return a new `Set` of candidates. */ private fun fetchCandidates(): Set = mutableSetOf().apply { addAll(actionTemplateConverter.convertConfigToTemplate(configService.actions().values)) @@ -55,7 +58,7 @@ class TemplateService @Inject constructor(private val injector: Injector, // populate the parser configurations forEach { template -> - template.parsers += TemplateConfigService.loadParserConfig(template::class.java) + template.parsers += templateConfigService.loadParserConfig(template::class.java) // no parsers have been set if (template.parsers.isEmpty()) { @@ -64,6 +67,9 @@ class TemplateService @Inject constructor(private val injector: Injector, } } + /** + * Provide a human-readable usage message. + */ fun usage() = StringBuilder().apply { val sortedCandidates = fetchCandidates().toMutableList().apply { sortBy { candidate -> diff --git a/frontends/common/src/main/kotlin/com/gatehill/corebot/chat/MessageService.kt b/frontends/common/src/main/kotlin/com/gatehill/corebot/chat/MessageService.kt index 6da14498..73dda09f 100644 --- a/frontends/common/src/main/kotlin/com/gatehill/corebot/chat/MessageService.kt +++ b/frontends/common/src/main/kotlin/com/gatehill/corebot/chat/MessageService.kt @@ -7,6 +7,7 @@ import com.gatehill.corebot.chat.model.action.Action import com.gatehill.corebot.chat.model.action.ActionWrapper import com.gatehill.corebot.chat.model.action.CustomAction import com.gatehill.corebot.chat.model.template.ActionMessageMode +import com.gatehill.corebot.chat.template.TemplateService import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.Settings import com.gatehill.corebot.security.AuthorisationService From 04732717fbef191a390e4ca94452c649520de3d5 Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Sun, 2 Jul 2017 00:20:58 +0100 Subject: [PATCH 32/36] Refactors package structure to reflect filter and factory naming. --- .../jenkins/action/JenkinsActionDriver.kt | 4 +- .../action/JenkinsJobTriggerService.kt | 6 +- .../chat/model/template/DisableJobTemplate.kt | 13 ---- .../chat/model/template/EnableJobTemplate.kt | 13 ---- .../deploy/TriggerActionTemplateConverter.kt | 16 ----- .../jobs/action}/JobBaseActionDriver.kt | 9 +-- .../action/TriggerActionFactoryConverter.kt | 16 +++++ .../jobs/action/factory/DisableJobFactory.kt | 17 ++++++ .../jobs/action/factory/EnableJobFactory.kt | 17 ++++++ .../jobs/action/factory}/JobActionType.kt | 6 +- .../jobs/action/factory/TriggerJobFactory.kt} | 16 +++-- .../jobs/service}/BaseJobTriggerService.kt | 8 ++- .../jobs/service}/JobTriggerService.kt | 2 +- .../src/main/resources/jobs-templates.yml | 6 +- .../rundeck/action/RundeckActionDriver.kt | 8 +-- .../action/RundeckJobTriggerService.kt | 6 +- .../items/action/ItemsActionDriverImpl.kt | 4 +- .../factory/BaseItemFactory.kt} | 12 ++-- .../action/factory/BorrowItemAsUserFactory.kt | 24 ++++++++ .../items/action/factory/BorrowItemFactory.kt | 28 +++++++++ .../factory/EvictItemFactory.kt} | 8 ++- .../factory/EvictUserFromItemFactory.kt} | 15 +++-- .../factory/ReturnItemFactory.kt} | 8 ++- .../factory/StatusAllFactory.kt} | 12 ++-- .../factory/StatusItemFactory.kt} | 8 ++- .../items/action/model/ItemsActionType.kt | 2 +- .../template/BorrowItemAsUserTemplate.kt | 19 ------ .../chat/model/template/BorrowItemTemplate.kt | 24 -------- .../driver/items/service/ClaimService.kt | 14 ++--- .../src/main/resources/items-templates.yml | 16 ++--- .../com/gatehill/corebot/CommonBotModule.kt | 2 +- .../action/DirectActionPerformServiceImpl.kt | 2 +- .../kotlin/com/gatehill/corebot/Bootstrap.kt | 34 +++++------ .../main/kotlin/com/gatehill/corebot/Main.kt | 6 +- .../kotlin/com/gatehill/corebot/Bootstrap.kt | 34 +++++------ .../main/kotlin/com/gatehill/corebot/Main.kt | 6 +- .../action => action/model}/ActionType.kt | 2 +- .../action/model/PerformActionRequest.kt | 1 - .../kotlin/com/gatehill/corebot/CoreModule.kt | 8 +-- .../corebot/action/ActionFactoryConverter.kt | 18 ++++++ .../corebot/action/ActionOutcomeService.kt | 2 +- .../action/ActionOutcomeServiceImpl.kt | 2 +- .../gatehill/corebot/action/LockService.kt | 10 ++-- .../factory/ActionFactory.kt} | 34 +++++------ .../factory/BaseActionFactory.kt} | 14 ++--- .../factory/BaseLockOptionFactory.kt} | 16 ++--- .../factory/CustomActionFactory.kt} | 8 +-- .../action/factory/LockActionFactory.kt | 15 +++++ .../action/factory/LockOptionFactory.kt | 17 ++++++ .../factory/NamedActionFactory.kt} | 6 +- .../factory/ShowHelpFactory.kt} | 9 +-- .../action/factory/StatusActionFactory.kt | 15 +++++ .../factory/SystemActionFactory.kt} | 8 +-- .../action/factory/UnlockActionFactory.kt | 15 +++++ .../action/factory/UnlockOptionFactory.kt | 17 ++++++ .../model/action => action/model}/Action.kt | 2 +- .../action => action/model}/ActionWrapper.kt | 2 +- .../action => action/model}/CoreActionType.kt | 2 +- .../action => action/model}/CustomAction.kt | 2 +- .../action => action/model}/SystemAction.kt | 2 +- .../corebot/chat/filter/CommandFilter.kt | 8 +++ .../corebot/chat/filter/FilterConfig.kt | 3 + .../corebot/chat/filter/RegexFilter.kt | 22 +++++++ .../StringFilter.kt} | 25 ++++---- .../chat/model/template/LockActionTemplate.kt | 14 ----- .../chat/model/template/LockOptionTemplate.kt | 16 ----- .../model/template/StatusActionTemplate.kt | 14 ----- .../model/template/UnlockActionTemplate.kt | 14 ----- .../model/template/UnlockOptionTemplate.kt | 16 ----- .../corebot/chat/parser/CommandParser.kt | 7 --- .../corebot/chat/parser/ParserConfig.kt | 3 - .../corebot/chat/parser/RegexParser.kt | 22 ------- .../chat/template/ActionTemplateConverter.kt | 18 ------ .../chat/template/TemplateConfigService.kt | 23 ++++--- .../corebot/chat/template/TemplateService.kt | 60 ++++++++++--------- .../{action => }/driver/ActionDriver.kt | 4 +- .../driver/ActionDriverFactory.kt | 2 +- .../{action => }/driver/BaseActionDriver.kt | 6 +- .../{action => driver}/model/ActionStatus.kt | 2 +- .../model/TriggeredAction.kt | 2 +- .../corebot/security/AuthorisationService.kt | 2 +- .../src/main/resources/core-templates.yml | 12 ++-- .../gatehill/corebot/chat/MessageService.kt | 8 +-- 83 files changed, 507 insertions(+), 464 deletions(-) delete mode 100644 backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/DisableJobTemplate.kt delete mode 100644 backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/EnableJobTemplate.kt delete mode 100644 backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/deploy/TriggerActionTemplateConverter.kt rename backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/{action/driver => driver/jobs/action}/JobBaseActionDriver.kt (79%) create mode 100644 backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/TriggerActionFactoryConverter.kt create mode 100644 backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/factory/DisableJobFactory.kt create mode 100644 backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/factory/EnableJobFactory.kt rename backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/{chat/model/template => driver/jobs/action/factory}/JobActionType.kt (68%) rename backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/{chat/model/template/TriggerJobTemplate.kt => driver/jobs/action/factory/TriggerJobFactory.kt} (66%) rename backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/{action => driver/jobs/service}/BaseJobTriggerService.kt (96%) rename backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/{action => driver/jobs/service}/JobTriggerService.kt (92%) rename backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/{chat/model/template/BaseItemTemplate.kt => action/factory/BaseItemFactory.kt} (62%) create mode 100644 backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/BorrowItemAsUserFactory.kt create mode 100644 backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/BorrowItemFactory.kt rename backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/{chat/model/template/EvictItemTemplate.kt => action/factory/EvictItemFactory.kt} (63%) rename backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/{chat/model/template/EvictUserFromItemTemplate.kt => action/factory/EvictUserFromItemFactory.kt} (52%) rename backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/{chat/model/template/ReturnItemTemplate.kt => action/factory/ReturnItemFactory.kt} (62%) rename backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/{chat/model/template/StatusAllTemplate.kt => action/factory/StatusAllFactory.kt} (62%) rename backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/{chat/model/template/StatusItemTemplate.kt => action/factory/StatusItemFactory.kt} (63%) delete mode 100644 backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemAsUserTemplate.kt delete mode 100644 backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt rename core/api/src/main/kotlin/com/gatehill/corebot/{chat/model/action => action/model}/ActionType.kt (92%) create mode 100644 core/engine/src/main/kotlin/com/gatehill/corebot/action/ActionFactoryConverter.kt rename core/engine/src/main/kotlin/com/gatehill/corebot/{chat/model/template/ActionTemplate.kt => action/factory/ActionFactory.kt} (56%) rename core/engine/src/main/kotlin/com/gatehill/corebot/{chat/model/template/BaseActionTemplate.kt => action/factory/BaseActionFactory.kt} (70%) rename core/engine/src/main/kotlin/com/gatehill/corebot/{chat/model/template/BaseLockOptionTemplate.kt => action/factory/BaseLockOptionFactory.kt} (77%) rename core/engine/src/main/kotlin/com/gatehill/corebot/{chat/model/template/CustomActionTemplate.kt => action/factory/CustomActionFactory.kt} (89%) create mode 100644 core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/LockActionFactory.kt create mode 100644 core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/LockOptionFactory.kt rename core/engine/src/main/kotlin/com/gatehill/corebot/{chat/model/template/NamedActionTemplate.kt => action/factory/NamedActionFactory.kt} (81%) rename core/engine/src/main/kotlin/com/gatehill/corebot/{chat/model/template/ShowHelpTemplate.kt => action/factory/ShowHelpFactory.kt} (68%) create mode 100644 core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/StatusActionFactory.kt rename core/engine/src/main/kotlin/com/gatehill/corebot/{chat/model/template/SystemActionTemplate.kt => action/factory/SystemActionFactory.kt} (70%) create mode 100644 core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/UnlockActionFactory.kt create mode 100644 core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/UnlockOptionFactory.kt rename core/engine/src/main/kotlin/com/gatehill/corebot/{chat/model/action => action/model}/Action.kt (88%) rename core/engine/src/main/kotlin/com/gatehill/corebot/{chat/model/action => action/model}/ActionWrapper.kt (84%) rename core/engine/src/main/kotlin/com/gatehill/corebot/{chat/model/action => action/model}/CoreActionType.kt (92%) rename core/engine/src/main/kotlin/com/gatehill/corebot/{chat/model/action => action/model}/CustomAction.kt (93%) rename core/engine/src/main/kotlin/com/gatehill/corebot/{chat/model/action => action/model}/SystemAction.kt (87%) create mode 100644 core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/CommandFilter.kt create mode 100644 core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/FilterConfig.kt create mode 100644 core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/RegexFilter.kt rename core/engine/src/main/kotlin/com/gatehill/corebot/chat/{parser/StringParser.kt => filter/StringFilter.kt} (54%) delete mode 100644 core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/LockActionTemplate.kt delete mode 100644 core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/LockOptionTemplate.kt delete mode 100644 core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/StatusActionTemplate.kt delete mode 100644 core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/UnlockActionTemplate.kt delete mode 100644 core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/UnlockOptionTemplate.kt delete mode 100644 core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/CommandParser.kt delete mode 100644 core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/ParserConfig.kt delete mode 100644 core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/RegexParser.kt delete mode 100644 core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/ActionTemplateConverter.kt rename core/engine/src/main/kotlin/com/gatehill/corebot/{action => }/driver/ActionDriver.kt (84%) rename core/engine/src/main/kotlin/com/gatehill/corebot/{action => }/driver/ActionDriverFactory.kt (96%) rename core/engine/src/main/kotlin/com/gatehill/corebot/{action => }/driver/BaseActionDriver.kt (94%) rename core/engine/src/main/kotlin/com/gatehill/corebot/{action => driver}/model/ActionStatus.kt (87%) rename core/engine/src/main/kotlin/com/gatehill/corebot/{action => driver}/model/TriggeredAction.kt (87%) diff --git a/backends/deployment/jenkins/src/main/kotlin/com/gatehill/corebot/driver/jenkins/action/JenkinsActionDriver.kt b/backends/deployment/jenkins/src/main/kotlin/com/gatehill/corebot/driver/jenkins/action/JenkinsActionDriver.kt index 8b3a0552..4f498697 100644 --- a/backends/deployment/jenkins/src/main/kotlin/com/gatehill/corebot/driver/jenkins/action/JenkinsActionDriver.kt +++ b/backends/deployment/jenkins/src/main/kotlin/com/gatehill/corebot/driver/jenkins/action/JenkinsActionDriver.kt @@ -1,10 +1,10 @@ package com.gatehill.corebot.driver.jenkins.action import com.gatehill.corebot.action.LockService -import com.gatehill.corebot.action.driver.ActionDriver -import com.gatehill.corebot.action.driver.JobBaseActionDriver +import com.gatehill.corebot.driver.ActionDriver import com.gatehill.corebot.driver.base.action.ApiClientBuilder import com.gatehill.corebot.driver.jenkins.config.DriverSettings +import com.gatehill.corebot.driver.jobs.action.JobBaseActionDriver import okhttp3.Credentials import java.util.HashMap import javax.inject.Inject diff --git a/backends/deployment/jenkins/src/main/kotlin/com/gatehill/corebot/driver/jenkins/action/JenkinsJobTriggerService.kt b/backends/deployment/jenkins/src/main/kotlin/com/gatehill/corebot/driver/jenkins/action/JenkinsJobTriggerService.kt index 63ea88f3..20a07538 100644 --- a/backends/deployment/jenkins/src/main/kotlin/com/gatehill/corebot/driver/jenkins/action/JenkinsJobTriggerService.kt +++ b/backends/deployment/jenkins/src/main/kotlin/com/gatehill/corebot/driver/jenkins/action/JenkinsJobTriggerService.kt @@ -1,15 +1,15 @@ package com.gatehill.corebot.driver.jenkins.action import com.gatehill.corebot.action.ActionOutcomeService -import com.gatehill.corebot.action.BaseJobTriggerService import com.gatehill.corebot.action.LockService -import com.gatehill.corebot.action.model.ActionStatus import com.gatehill.corebot.action.model.PerformActionResult import com.gatehill.corebot.action.model.TriggerContext -import com.gatehill.corebot.action.model.TriggeredAction import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.jenkins.config.DriverSettings import com.gatehill.corebot.driver.jenkins.model.BuildDetails +import com.gatehill.corebot.driver.jobs.service.BaseJobTriggerService +import com.gatehill.corebot.driver.model.ActionStatus +import com.gatehill.corebot.driver.model.TriggeredAction import com.gatehill.corebot.util.onException import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.Logger diff --git a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/DisableJobTemplate.kt b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/DisableJobTemplate.kt deleted file mode 100644 index b8fa12fd..00000000 --- a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/DisableJobTemplate.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.gatehill.corebot.chat.model.template - -import com.gatehill.corebot.chat.model.action.ActionType -import com.gatehill.corebot.config.ConfigService -import javax.inject.Inject - -/** - * Disables a job. - */ -class DisableJobTemplate @Inject constructor(configService: ConfigService) : NamedActionTemplate(configService) { - override val actionType: ActionType = JobActionType.DISABLE - override val actionMessageMode = ActionMessageMode.INDIVIDUAL -} \ No newline at end of file diff --git a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/EnableJobTemplate.kt b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/EnableJobTemplate.kt deleted file mode 100644 index 20de9cf7..00000000 --- a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/EnableJobTemplate.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.gatehill.corebot.chat.model.template - -import com.gatehill.corebot.chat.model.action.ActionType -import com.gatehill.corebot.config.ConfigService -import javax.inject.Inject - -/** - * Enables a job. - */ -class EnableJobTemplate @Inject constructor(configService: ConfigService) : NamedActionTemplate(configService) { - override val actionType: ActionType = JobActionType.ENABLE - override val actionMessageMode = ActionMessageMode.INDIVIDUAL -} \ No newline at end of file diff --git a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/deploy/TriggerActionTemplateConverter.kt b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/deploy/TriggerActionTemplateConverter.kt deleted file mode 100644 index c1a13b2e..00000000 --- a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/deploy/TriggerActionTemplateConverter.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.gatehill.corebot.deploy - -import com.gatehill.corebot.chat.template.ActionTemplateConverter -import com.gatehill.corebot.chat.ChatGenerator -import com.gatehill.corebot.chat.model.template.ActionTemplate -import com.gatehill.corebot.chat.model.template.TriggerJobTemplate -import com.gatehill.corebot.config.model.ActionConfig -import javax.inject.Inject - -/** - * @author Pete Cornish {@literal } - */ -class TriggerActionTemplateConverter @Inject constructor(private val chatGenerator: ChatGenerator) : ActionTemplateConverter { - override fun convertConfigToTemplate(configs: Iterable): Collection = - configs.map { TriggerJobTemplate(it, chatGenerator) }.toList() -} diff --git a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/action/driver/JobBaseActionDriver.kt b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/JobBaseActionDriver.kt similarity index 79% rename from backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/action/driver/JobBaseActionDriver.kt rename to backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/JobBaseActionDriver.kt index e58458ce..436616d2 100644 --- a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/action/driver/JobBaseActionDriver.kt +++ b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/JobBaseActionDriver.kt @@ -1,12 +1,13 @@ -package com.gatehill.corebot.action.driver +package com.gatehill.corebot.driver.jobs.action -import com.gatehill.corebot.action.JobTriggerService import com.gatehill.corebot.action.LockService +import com.gatehill.corebot.action.model.ActionType import com.gatehill.corebot.action.model.PerformActionResult import com.gatehill.corebot.action.model.TriggerContext -import com.gatehill.corebot.chat.model.action.ActionType -import com.gatehill.corebot.chat.model.template.JobActionType import com.gatehill.corebot.config.model.ActionConfig +import com.gatehill.corebot.driver.BaseActionDriver +import com.gatehill.corebot.driver.jobs.action.factory.JobActionType +import com.gatehill.corebot.driver.jobs.service.JobTriggerService import java.util.concurrent.CompletableFuture import javax.inject.Inject diff --git a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/TriggerActionFactoryConverter.kt b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/TriggerActionFactoryConverter.kt new file mode 100644 index 00000000..1af9043d --- /dev/null +++ b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/TriggerActionFactoryConverter.kt @@ -0,0 +1,16 @@ +package com.gatehill.corebot.driver.jobs.action + +import com.gatehill.corebot.action.ActionFactoryConverter +import com.gatehill.corebot.action.factory.ActionFactory +import com.gatehill.corebot.chat.ChatGenerator +import com.gatehill.corebot.config.model.ActionConfig +import com.gatehill.corebot.driver.jobs.action.factory.TriggerJobFactory +import javax.inject.Inject + +/** + * @author Pete Cornish {@literal } + */ +class TriggerActionFactoryConverter @Inject constructor(private val chatGenerator: ChatGenerator) : ActionFactoryConverter { + override fun convertConfigToFactory(configs: Iterable): Collection = + configs.map { TriggerJobFactory(it, chatGenerator) }.toList() +} diff --git a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/factory/DisableJobFactory.kt b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/factory/DisableJobFactory.kt new file mode 100644 index 00000000..092b23c0 --- /dev/null +++ b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/factory/DisableJobFactory.kt @@ -0,0 +1,17 @@ +package com.gatehill.corebot.driver.jobs.action.factory + +import com.gatehill.corebot.action.factory.ActionMessageMode +import com.gatehill.corebot.action.factory.NamedActionFactory +import com.gatehill.corebot.action.factory.Template +import com.gatehill.corebot.action.model.ActionType +import com.gatehill.corebot.config.ConfigService +import javax.inject.Inject + +/** + * Disables a job. + */ +@Template("disableJob") +class DisableJobFactory @Inject constructor(configService: ConfigService) : NamedActionFactory(configService) { + override val actionType: ActionType = JobActionType.DISABLE + override val actionMessageMode = ActionMessageMode.INDIVIDUAL +} diff --git a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/factory/EnableJobFactory.kt b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/factory/EnableJobFactory.kt new file mode 100644 index 00000000..691a4f68 --- /dev/null +++ b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/factory/EnableJobFactory.kt @@ -0,0 +1,17 @@ +package com.gatehill.corebot.driver.jobs.action.factory + +import com.gatehill.corebot.action.factory.ActionMessageMode +import com.gatehill.corebot.action.factory.NamedActionFactory +import com.gatehill.corebot.action.factory.Template +import com.gatehill.corebot.action.model.ActionType +import com.gatehill.corebot.config.ConfigService +import javax.inject.Inject + +/** + * Enables a job. + */ +@Template("enableJob") +class EnableJobFactory @Inject constructor(configService: ConfigService) : NamedActionFactory(configService) { + override val actionType: ActionType = JobActionType.ENABLE + override val actionMessageMode = ActionMessageMode.INDIVIDUAL +} diff --git a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/JobActionType.kt b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/factory/JobActionType.kt similarity index 68% rename from backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/JobActionType.kt rename to backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/factory/JobActionType.kt index 849415b4..fcee3199 100644 --- a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/JobActionType.kt +++ b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/factory/JobActionType.kt @@ -1,7 +1,7 @@ -package com.gatehill.corebot.chat.model.template +package com.gatehill.corebot.driver.jobs.action.factory -import com.gatehill.corebot.chat.model.action.ActionType -import com.gatehill.corebot.chat.model.action.CoreActionType +import com.gatehill.corebot.action.model.ActionType +import com.gatehill.corebot.action.model.CoreActionType /** * Extends the action types with job specific actions. diff --git a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/TriggerJobTemplate.kt b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/factory/TriggerJobFactory.kt similarity index 66% rename from backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/TriggerJobTemplate.kt rename to backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/factory/TriggerJobFactory.kt index 14b893f7..dcbc0d0a 100644 --- a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/chat/model/template/TriggerJobTemplate.kt +++ b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/factory/TriggerJobFactory.kt @@ -1,16 +1,20 @@ -package com.gatehill.corebot.chat.model.template +package com.gatehill.corebot.driver.jobs.action.factory +import com.gatehill.corebot.action.factory.ActionMessageMode +import com.gatehill.corebot.action.factory.CustomActionFactory +import com.gatehill.corebot.action.factory.Template +import com.gatehill.corebot.action.model.ActionType import com.gatehill.corebot.action.model.TriggerContext import com.gatehill.corebot.chat.ChatGenerator -import com.gatehill.corebot.chat.model.action.ActionType -import com.gatehill.corebot.chat.parser.StringParser +import com.gatehill.corebot.chat.filter.StringFilter import com.gatehill.corebot.config.model.ActionConfig /** * Triggers job execution. */ -class TriggerJobTemplate(action: ActionConfig, - private val chatGenerator: ChatGenerator) : CustomActionTemplate() { +@Template("triggerJob") +class TriggerJobFactory(action: ActionConfig, + private val chatGenerator: ChatGenerator) : CustomActionFactory() { override val builtIn = false override val showInUsage = true @@ -20,7 +24,7 @@ class TriggerJobTemplate(action: ActionConfig, init { this.actionConfigs = mutableListOf(action) - parsers += StringParser.StringParserConfig(action.template, action.template) + parsers += StringFilter.StringFilterConfig(action.template, action.template) } override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?): String { diff --git a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/action/BaseJobTriggerService.kt b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/service/BaseJobTriggerService.kt similarity index 96% rename from backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/action/BaseJobTriggerService.kt rename to backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/service/BaseJobTriggerService.kt index 29eef3ab..1d906fda 100644 --- a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/action/BaseJobTriggerService.kt +++ b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/service/BaseJobTriggerService.kt @@ -1,11 +1,13 @@ -package com.gatehill.corebot.action +package com.gatehill.corebot.driver.jobs.service -import com.gatehill.corebot.action.model.ActionStatus +import com.gatehill.corebot.action.ActionOutcomeService +import com.gatehill.corebot.action.LockService import com.gatehill.corebot.action.model.PerformActionResult import com.gatehill.corebot.action.model.TriggerContext -import com.gatehill.corebot.action.model.TriggeredAction import com.gatehill.corebot.config.Settings import com.gatehill.corebot.config.model.ActionConfig +import com.gatehill.corebot.driver.model.ActionStatus +import com.gatehill.corebot.driver.model.TriggeredAction import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.Logger import java.util.Timer diff --git a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/action/JobTriggerService.kt b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/service/JobTriggerService.kt similarity index 92% rename from backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/action/JobTriggerService.kt rename to backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/service/JobTriggerService.kt index 005af399..3e7fdf01 100644 --- a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/action/JobTriggerService.kt +++ b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/service/JobTriggerService.kt @@ -1,4 +1,4 @@ -package com.gatehill.corebot.action +package com.gatehill.corebot.driver.jobs.service import com.gatehill.corebot.action.model.PerformActionResult import com.gatehill.corebot.action.model.TriggerContext diff --git a/backends/deployment/jobs/src/main/resources/jobs-templates.yml b/backends/deployment/jobs/src/main/resources/jobs-templates.yml index ab08517e..1fcb3d3c 100644 --- a/backends/deployment/jobs/src/main/resources/jobs-templates.yml +++ b/backends/deployment/jobs/src/main/resources/jobs-templates.yml @@ -1,7 +1,7 @@ -# Jobs backend templates. +# Templates for jobs backend. --- -DisableJobTemplate: +disableJob: - template: disable {action or tag name} -EnableJobTemplate: +enableJob: - template: enable {action or tag name} diff --git a/backends/deployment/rundeck/src/main/kotlin/com/gatehill/corebot/driver/rundeck/action/RundeckActionDriver.kt b/backends/deployment/rundeck/src/main/kotlin/com/gatehill/corebot/driver/rundeck/action/RundeckActionDriver.kt index f28a8531..09b355fe 100644 --- a/backends/deployment/rundeck/src/main/kotlin/com/gatehill/corebot/driver/rundeck/action/RundeckActionDriver.kt +++ b/backends/deployment/rundeck/src/main/kotlin/com/gatehill/corebot/driver/rundeck/action/RundeckActionDriver.kt @@ -1,14 +1,14 @@ package com.gatehill.corebot.driver.rundeck.action import com.gatehill.corebot.action.LockService -import com.gatehill.corebot.action.driver.ActionDriver -import com.gatehill.corebot.action.driver.JobBaseActionDriver +import com.gatehill.corebot.action.model.ActionType import com.gatehill.corebot.action.model.PerformActionResult import com.gatehill.corebot.action.model.TriggerContext -import com.gatehill.corebot.chat.model.action.ActionType -import com.gatehill.corebot.chat.model.template.JobActionType import com.gatehill.corebot.config.model.ActionConfig +import com.gatehill.corebot.driver.ActionDriver import com.gatehill.corebot.driver.base.action.ApiClientBuilder +import com.gatehill.corebot.driver.jobs.action.JobBaseActionDriver +import com.gatehill.corebot.driver.jobs.action.factory.JobActionType import com.gatehill.corebot.driver.rundeck.config.DriverSettings import java.util.HashMap import java.util.concurrent.CompletableFuture diff --git a/backends/deployment/rundeck/src/main/kotlin/com/gatehill/corebot/driver/rundeck/action/RundeckJobTriggerService.kt b/backends/deployment/rundeck/src/main/kotlin/com/gatehill/corebot/driver/rundeck/action/RundeckJobTriggerService.kt index 32988cc6..0d4d4354 100644 --- a/backends/deployment/rundeck/src/main/kotlin/com/gatehill/corebot/driver/rundeck/action/RundeckJobTriggerService.kt +++ b/backends/deployment/rundeck/src/main/kotlin/com/gatehill/corebot/driver/rundeck/action/RundeckJobTriggerService.kt @@ -1,13 +1,13 @@ package com.gatehill.corebot.driver.rundeck.action import com.gatehill.corebot.action.ActionOutcomeService -import com.gatehill.corebot.action.BaseJobTriggerService import com.gatehill.corebot.action.LockService -import com.gatehill.corebot.action.model.ActionStatus import com.gatehill.corebot.action.model.PerformActionResult import com.gatehill.corebot.action.model.TriggerContext -import com.gatehill.corebot.action.model.TriggeredAction import com.gatehill.corebot.config.model.ActionConfig +import com.gatehill.corebot.driver.jobs.service.BaseJobTriggerService +import com.gatehill.corebot.driver.model.ActionStatus +import com.gatehill.corebot.driver.model.TriggeredAction import com.gatehill.corebot.driver.rundeck.model.ExecutionDetails import com.gatehill.corebot.driver.rundeck.model.ExecutionInfo import com.gatehill.corebot.driver.rundeck.model.ExecutionOptions diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt index 74a15ff5..0feab7c9 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/ItemsActionDriverImpl.kt @@ -1,10 +1,10 @@ package com.gatehill.corebot.driver.items.action -import com.gatehill.corebot.action.driver.ActionDriver +import com.gatehill.corebot.action.model.ActionType import com.gatehill.corebot.action.model.PerformActionResult import com.gatehill.corebot.action.model.TriggerContext -import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.model.ActionConfig +import com.gatehill.corebot.driver.ActionDriver import com.gatehill.corebot.driver.items.action.model.ItemsActionType import com.gatehill.corebot.driver.items.service.ClaimService import java.util.concurrent.CompletableFuture diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BaseItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/BaseItemFactory.kt similarity index 62% rename from backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BaseItemTemplate.kt rename to backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/BaseItemFactory.kt index cf1e01f6..13997251 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BaseItemTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/BaseItemFactory.kt @@ -1,7 +1,7 @@ -package com.gatehill.corebot.driver.items.chat.model.template +package com.gatehill.corebot.driver.items.action.factory -import com.gatehill.corebot.chat.model.template.ActionMessageMode -import com.gatehill.corebot.chat.model.template.CustomActionTemplate +import com.gatehill.corebot.action.factory.ActionMessageMode +import com.gatehill.corebot.action.factory.CustomActionFactory import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig import javax.inject.Inject @@ -9,7 +9,7 @@ import javax.inject.Inject /** * Common item functionality. */ -abstract class BaseItemTemplate @Inject constructor(private val configService: ConfigService) : CustomActionTemplate() { +abstract class BaseItemFactory @Inject constructor(private val configService: ConfigService) : CustomActionFactory() { override val builtIn = false override val showInUsage = true override val actionMessageMode = ActionMessageMode.INDIVIDUAL @@ -18,7 +18,7 @@ abstract class BaseItemTemplate @Inject constructor(private val configService: C protected val itemName: String get() = placeholderValues[itemPlaceholder]!! - override fun onTemplateSatisfied(): Boolean { + override fun onSatisfied(): Boolean { actionConfigs += configService.actions() .filterKeys { it.equals(itemName, ignoreCase = true) } .values @@ -27,6 +27,6 @@ abstract class BaseItemTemplate @Inject constructor(private val configService: C } companion object { - val itemPlaceholder = "itemName" + const val itemPlaceholder = "itemName" } } diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/BorrowItemAsUserFactory.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/BorrowItemAsUserFactory.kt new file mode 100644 index 00000000..62dcc410 --- /dev/null +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/BorrowItemAsUserFactory.kt @@ -0,0 +1,24 @@ +package com.gatehill.corebot.driver.items.action.factory + +import com.gatehill.corebot.action.factory.Template +import com.gatehill.corebot.action.model.ActionType +import com.gatehill.corebot.config.ConfigService +import com.gatehill.corebot.driver.items.action.model.ItemsActionType +import javax.inject.Inject + +/** + * Borrow an item as another user. + */ +@Template("borrowItemAsUser", placeholderKeys = arrayOf( + BorrowItemAsUserFactory.borrowerPlaceholder, + BaseItemFactory.itemPlaceholder, + BorrowItemFactory.subItemPlaceholder, + BorrowItemFactory.reasonPlaceholder +)) +class BorrowItemAsUserFactory @Inject constructor(configService: ConfigService) : BorrowItemFactory(configService) { + override val actionType: ActionType = ItemsActionType.ITEM_BORROW_AS_USER + + companion object { + const val borrowerPlaceholder = "borrower" + } +} diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/BorrowItemFactory.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/BorrowItemFactory.kt new file mode 100644 index 00000000..8c2d7286 --- /dev/null +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/BorrowItemFactory.kt @@ -0,0 +1,28 @@ +package com.gatehill.corebot.driver.items.action.factory + +import com.gatehill.corebot.action.factory.Template +import com.gatehill.corebot.action.model.ActionType +import com.gatehill.corebot.action.model.TriggerContext +import com.gatehill.corebot.config.ConfigService +import com.gatehill.corebot.config.model.ActionConfig +import com.gatehill.corebot.driver.items.action.model.ItemsActionType +import javax.inject.Inject + +/** + * Borrow an item. + */ +@Template("borrowItem", placeholderKeys = arrayOf( + BaseItemFactory.itemPlaceholder, + BorrowItemFactory.subItemPlaceholder, + BorrowItemFactory.reasonPlaceholder +)) +open class BorrowItemFactory @Inject constructor(configService: ConfigService) : BaseItemFactory(configService) { + override val actionType: ActionType = ItemsActionType.ITEM_BORROW + + override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?) = "" + + companion object { + const val subItemPlaceholder = "optionalSubItemName" + const val reasonPlaceholder = "reason" + } +} diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/EvictItemFactory.kt similarity index 63% rename from backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictItemTemplate.kt rename to backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/EvictItemFactory.kt index 3a472b79..6494fee0 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictItemTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/EvictItemFactory.kt @@ -1,7 +1,8 @@ -package com.gatehill.corebot.driver.items.chat.model.template +package com.gatehill.corebot.driver.items.action.factory +import com.gatehill.corebot.action.factory.Template +import com.gatehill.corebot.action.model.ActionType import com.gatehill.corebot.action.model.TriggerContext -import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.items.action.model.ItemsActionType @@ -10,7 +11,8 @@ import javax.inject.Inject /** * Evict all borrowers from an item. */ -class EvictItemTemplate @Inject constructor(configService: ConfigService) : BaseItemTemplate(configService) { +@Template("evictItem") +class EvictItemFactory @Inject constructor(configService: ConfigService) : BaseItemFactory(configService) { override val actionType: ActionType = ItemsActionType.ITEM_EVICT override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?) = "" } diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictUserFromItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/EvictUserFromItemFactory.kt similarity index 52% rename from backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictUserFromItemTemplate.kt rename to backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/EvictUserFromItemFactory.kt index f3c2dda2..a742ae1a 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/EvictUserFromItemTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/EvictUserFromItemFactory.kt @@ -1,8 +1,8 @@ -package com.gatehill.corebot.driver.items.chat.model.template +package com.gatehill.corebot.driver.items.action.factory +import com.gatehill.corebot.action.factory.Template +import com.gatehill.corebot.action.model.ActionType import com.gatehill.corebot.action.model.TriggerContext -import com.gatehill.corebot.chat.model.action.ActionType -import com.gatehill.corebot.chat.model.template.RegexActionTemplate import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.items.action.model.ItemsActionType @@ -11,13 +11,16 @@ import javax.inject.Inject /** * Evict a borrower from an item. */ -class EvictUserFromItemTemplate @Inject constructor(configService: ConfigService) : BaseItemTemplate(configService), RegexActionTemplate { +@Template("evictUserFromItem", placeholderKeys = arrayOf( + EvictUserFromItemFactory.borrowerPlaceholder, + BaseItemFactory.itemPlaceholder +)) +class EvictUserFromItemFactory @Inject constructor(configService: ConfigService) : BaseItemFactory(configService) { override val actionType: ActionType = ItemsActionType.ITEM_EVICT_USER - override val placeholderKeys = listOf(borrowerPlaceholder, itemPlaceholder) override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?) = "" companion object { - val borrowerPlaceholder = "borrower" + const val borrowerPlaceholder = "borrower" } } diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/ReturnItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/ReturnItemFactory.kt similarity index 62% rename from backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/ReturnItemTemplate.kt rename to backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/ReturnItemFactory.kt index 92106a9c..c2a4a2cd 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/ReturnItemTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/ReturnItemFactory.kt @@ -1,7 +1,8 @@ -package com.gatehill.corebot.driver.items.chat.model.template +package com.gatehill.corebot.driver.items.action.factory +import com.gatehill.corebot.action.factory.Template +import com.gatehill.corebot.action.model.ActionType import com.gatehill.corebot.action.model.TriggerContext -import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.items.action.model.ItemsActionType @@ -10,7 +11,8 @@ import javax.inject.Inject /** * Return a borrowed item. */ -class ReturnItemTemplate @Inject constructor(configService: ConfigService) : BaseItemTemplate(configService) { +@Template("returnItem") +class ReturnItemFactory @Inject constructor(configService: ConfigService) : BaseItemFactory(configService) { override val actionType: ActionType = ItemsActionType.ITEM_RETURN override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?) = "" } diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusAllTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/StatusAllFactory.kt similarity index 62% rename from backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusAllTemplate.kt rename to backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/StatusAllFactory.kt index 379654fa..4a60c4ff 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusAllTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/StatusAllFactory.kt @@ -1,9 +1,10 @@ -package com.gatehill.corebot.driver.items.chat.model.template +package com.gatehill.corebot.driver.items.action.factory +import com.gatehill.corebot.action.factory.ActionMessageMode +import com.gatehill.corebot.action.factory.SystemActionFactory +import com.gatehill.corebot.action.factory.Template +import com.gatehill.corebot.action.model.ActionType import com.gatehill.corebot.action.model.TriggerContext -import com.gatehill.corebot.chat.model.action.ActionType -import com.gatehill.corebot.chat.model.template.ActionMessageMode -import com.gatehill.corebot.chat.model.template.SystemActionTemplate import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.items.action.model.ItemsActionType import com.gatehill.corebot.driver.items.service.ClaimService @@ -12,7 +13,8 @@ import javax.inject.Inject /** * Show status and claims for all items. */ -class StatusAllTemplate @Inject constructor(val claimService: ClaimService) : SystemActionTemplate() { +@Template("statusAllItems") +class StatusAllFactory @Inject constructor(val claimService: ClaimService) : SystemActionFactory() { override val builtIn = false override val showInUsage = true override val actionMessageMode = ActionMessageMode.INDIVIDUAL diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/StatusItemFactory.kt similarity index 63% rename from backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusItemTemplate.kt rename to backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/StatusItemFactory.kt index ea1f5586..67622fad 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/StatusItemTemplate.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/StatusItemFactory.kt @@ -1,7 +1,8 @@ -package com.gatehill.corebot.driver.items.chat.model.template +package com.gatehill.corebot.driver.items.action.factory +import com.gatehill.corebot.action.factory.Template +import com.gatehill.corebot.action.model.ActionType import com.gatehill.corebot.action.model.TriggerContext -import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.driver.items.action.model.ItemsActionType @@ -10,7 +11,8 @@ import javax.inject.Inject /** * Show status and claims for an item. */ -class StatusItemTemplate @Inject constructor(configService: ConfigService) : BaseItemTemplate(configService) { +@Template("statusItem") +class StatusItemFactory @Inject constructor(configService: ConfigService) : BaseItemFactory(configService) { override val actionType: ActionType = ItemsActionType.ITEM_STATUS override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?) = "" } diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/model/ItemsActionType.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/model/ItemsActionType.kt index 4271826e..00ac41da 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/model/ItemsActionType.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/model/ItemsActionType.kt @@ -1,6 +1,6 @@ package com.gatehill.corebot.driver.items.action.model -import com.gatehill.corebot.chat.model.action.CoreActionType +import com.gatehill.corebot.action.model.CoreActionType class ItemsActionType(name: String, description: String) : CoreActionType(name, description) { companion object { diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemAsUserTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemAsUserTemplate.kt deleted file mode 100644 index f6ad2737..00000000 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemAsUserTemplate.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.gatehill.corebot.driver.items.chat.model.template - -import com.gatehill.corebot.chat.model.action.ActionType -import com.gatehill.corebot.chat.model.template.RegexActionTemplate -import com.gatehill.corebot.config.ConfigService -import com.gatehill.corebot.driver.items.action.model.ItemsActionType -import javax.inject.Inject - -/** - * Borrow an item as another user. - */ -class BorrowItemAsUserTemplate @Inject constructor(configService: ConfigService) : BorrowItemTemplate(configService), RegexActionTemplate { - override val actionType: ActionType = ItemsActionType.ITEM_BORROW_AS_USER - override val placeholderKeys = listOf(borrowerPlaceholder, itemPlaceholder, subItemPlaceholder, reasonPlaceholder) - - companion object { - val borrowerPlaceholder = "borrower" - } -} diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt deleted file mode 100644 index 97e00d9c..00000000 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/chat/model/template/BorrowItemTemplate.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.gatehill.corebot.driver.items.chat.model.template - -import com.gatehill.corebot.action.model.TriggerContext -import com.gatehill.corebot.chat.model.action.ActionType -import com.gatehill.corebot.chat.model.template.RegexActionTemplate -import com.gatehill.corebot.config.ConfigService -import com.gatehill.corebot.config.model.ActionConfig -import com.gatehill.corebot.driver.items.action.model.ItemsActionType -import javax.inject.Inject - -/** - * Borrow an item. - */ -open class BorrowItemTemplate @Inject constructor(configService: ConfigService) : BaseItemTemplate(configService), RegexActionTemplate { - override val actionType: ActionType = ItemsActionType.ITEM_BORROW - override val placeholderKeys = listOf(itemPlaceholder, subItemPlaceholder, reasonPlaceholder) - - override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?) = "" - - companion object { - val subItemPlaceholder = "optionalSubItemName" - val reasonPlaceholder = "reason" - } -} diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt index f05caeb2..64f6331a 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/service/ClaimService.kt @@ -7,9 +7,9 @@ import com.gatehill.corebot.chat.ChatGenerator import com.gatehill.corebot.chat.SessionService import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig -import com.gatehill.corebot.driver.items.chat.model.template.BorrowItemAsUserTemplate -import com.gatehill.corebot.driver.items.chat.model.template.BorrowItemTemplate -import com.gatehill.corebot.driver.items.chat.model.template.EvictUserFromItemTemplate +import com.gatehill.corebot.driver.items.action.factory.BorrowItemAsUserFactory +import com.gatehill.corebot.driver.items.action.factory.BorrowItemFactory +import com.gatehill.corebot.driver.items.action.factory.EvictUserFromItemFactory import com.gatehill.corebot.driver.items.config.ItemSettings import com.gatehill.corebot.driver.items.config.OwnerDisplayMode import com.gatehill.corebot.store.DataStore @@ -46,11 +46,11 @@ class ClaimService @Inject constructor(private val configService: ConfigService, synchronized(itemName) { checkItemClaims(action) { claims -> - val reason: String = args[BorrowItemTemplate.reasonPlaceholder]!! - val subItem: String? = args[BorrowItemTemplate.subItemPlaceholder] + val reason: String = args[BorrowItemFactory.reasonPlaceholder]!! + val subItem: String? = args[BorrowItemFactory.subItemPlaceholder] // check if on behalf of another user - val borrower = args[BorrowItemAsUserTemplate.borrowerPlaceholder] ?: triggerMessageSenderId + val borrower = args[BorrowItemAsUserFactory.borrowerPlaceholder] ?: triggerMessageSenderId itemClaims[itemName] = ItemClaims(claims.toMutableList().apply { @@ -122,7 +122,7 @@ class ClaimService @Inject constructor(private val configService: ConfigService, synchronized(itemName) { checkItemClaims(action) { claims -> - val borrower = args[EvictUserFromItemTemplate.borrowerPlaceholder]!! + val borrower = args[EvictUserFromItemFactory.borrowerPlaceholder]!! when (claims.size) { 1 -> itemClaims.remove(itemName) diff --git a/backends/items/src/main/resources/items-templates.yml b/backends/items/src/main/resources/items-templates.yml index 3e3b062e..296db469 100644 --- a/backends/items/src/main/resources/items-templates.yml +++ b/backends/items/src/main/resources/items-templates.yml @@ -1,25 +1,25 @@ -# Items backend templates. +# Templates for items backend. --- -BorrowItemAsUserTemplate: +borrowItemAsUser: - template: /as\s\<@(?[a-zA-Z0-9]+)\>\sborrow\s+(?[a-zA-Z0-9]+)\s*(?.*)\s+for\s+(?.+)/ usage: as {borrower} borrow {itemName} {optionalSubItemName} for {reason} -BorrowItemTemplate: +borrowItem: - template: /borrow\s+(?[a-zA-Z0-9]+)\s*(?.*)\s+for\s+(?.+)/ usage: borrow {itemName} {optionalSubItemName} for {reason} -EvictItemTemplate: +evictItem: - template: evict {itemName} -EvictUserFromItemTemplate: +evictUserFromItem: - template: /evict\s\<@(?[a-zA-Z0-9]+)\>\sfrom\s+(?[a-zA-Z0-9]+)/ usage: evict {borrower} from {itemName} -ReturnItemTemplate: +returnItem: - template: return {itemName} -StatusAllTemplate: +statusAllItems: - template: status -StatusItemTemplate: +statusItem: - template: status {itemName} diff --git a/bots/common/src/main/kotlin/com/gatehill/corebot/CommonBotModule.kt b/bots/common/src/main/kotlin/com/gatehill/corebot/CommonBotModule.kt index bad9301a..3a76513f 100644 --- a/bots/common/src/main/kotlin/com/gatehill/corebot/CommonBotModule.kt +++ b/bots/common/src/main/kotlin/com/gatehill/corebot/CommonBotModule.kt @@ -4,11 +4,11 @@ import com.gatehill.corebot.action.ActionOutcomeService import com.gatehill.corebot.action.ActionOutcomeServiceImpl import com.gatehill.corebot.action.ActionPerformService import com.gatehill.corebot.action.DirectActionPerformServiceImpl -import com.gatehill.corebot.action.driver.ActionDriverFactory import com.gatehill.corebot.chat.ChatGenerator import com.gatehill.corebot.chat.template.TemplateService import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.ConfigServiceImpl +import com.gatehill.corebot.driver.ActionDriverFactory import com.gatehill.corebot.security.AuthorisationService import com.google.inject.AbstractModule diff --git a/bots/common/src/main/kotlin/com/gatehill/corebot/action/DirectActionPerformServiceImpl.kt b/bots/common/src/main/kotlin/com/gatehill/corebot/action/DirectActionPerformServiceImpl.kt index 1975d889..a7e53ee1 100644 --- a/bots/common/src/main/kotlin/com/gatehill/corebot/action/DirectActionPerformServiceImpl.kt +++ b/bots/common/src/main/kotlin/com/gatehill/corebot/action/DirectActionPerformServiceImpl.kt @@ -1,8 +1,8 @@ package com.gatehill.corebot.action -import com.gatehill.corebot.action.driver.ActionDriverFactory import com.gatehill.corebot.action.model.PerformActionRequest import com.gatehill.corebot.action.model.PerformActionResult +import com.gatehill.corebot.driver.ActionDriverFactory import java.util.concurrent.CompletableFuture import javax.inject.Inject diff --git a/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt b/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt index f5d3381c..c78505ff 100644 --- a/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt +++ b/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt @@ -1,17 +1,17 @@ package com.gatehill.corebot -import com.gatehill.corebot.action.driver.ActionDriverFactory -import com.gatehill.corebot.chat.model.template.DisableJobTemplate -import com.gatehill.corebot.chat.model.template.EnableJobTemplate -import com.gatehill.corebot.chat.model.template.LockActionTemplate -import com.gatehill.corebot.chat.model.template.LockOptionTemplate -import com.gatehill.corebot.chat.model.template.ShowHelpTemplate -import com.gatehill.corebot.chat.model.template.StatusActionTemplate -import com.gatehill.corebot.chat.model.template.UnlockActionTemplate -import com.gatehill.corebot.chat.model.template.UnlockOptionTemplate +import com.gatehill.corebot.action.factory.LockActionFactory +import com.gatehill.corebot.action.factory.LockOptionFactory +import com.gatehill.corebot.action.factory.ShowHelpFactory +import com.gatehill.corebot.action.factory.StatusActionFactory +import com.gatehill.corebot.action.factory.UnlockActionFactory +import com.gatehill.corebot.action.factory.UnlockOptionFactory import com.gatehill.corebot.chat.template.TemplateConfigService import com.gatehill.corebot.chat.template.TemplateService +import com.gatehill.corebot.driver.ActionDriverFactory import com.gatehill.corebot.driver.jenkins.action.JenkinsActionDriver +import com.gatehill.corebot.driver.jobs.action.factory.DisableJobFactory +import com.gatehill.corebot.driver.jobs.action.factory.EnableJobFactory import com.gatehill.corebot.driver.rundeck.action.RundeckActionDriver import javax.inject.Inject @@ -28,13 +28,13 @@ class Bootstrap @Inject constructor(actionDriverFactory: ActionDriverFactory, // templates templateConfigService.registerClasspathTemplateFile("/jobs-templates.yml") - templateService.registerTemplate(ShowHelpTemplate::class.java) - templateService.registerTemplate(LockActionTemplate::class.java) - templateService.registerTemplate(UnlockActionTemplate::class.java) - templateService.registerTemplate(StatusActionTemplate::class.java) - templateService.registerTemplate(EnableJobTemplate::class.java) - templateService.registerTemplate(DisableJobTemplate::class.java) - templateService.registerTemplate(LockOptionTemplate::class.java) - templateService.registerTemplate(UnlockOptionTemplate::class.java) + templateService.registerTemplate(ShowHelpFactory::class.java) + templateService.registerTemplate(LockActionFactory::class.java) + templateService.registerTemplate(UnlockActionFactory::class.java) + templateService.registerTemplate(StatusActionFactory::class.java) + templateService.registerTemplate(EnableJobFactory::class.java) + templateService.registerTemplate(DisableJobFactory::class.java) + templateService.registerTemplate(LockOptionFactory::class.java) + templateService.registerTemplate(UnlockOptionFactory::class.java) } } diff --git a/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Main.kt b/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Main.kt index 915393b2..ab2735db 100644 --- a/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Main.kt +++ b/bots/slack-deploy/src/main/kotlin/com/gatehill/corebot/Main.kt @@ -1,9 +1,9 @@ package com.gatehill.corebot +import com.gatehill.corebot.action.ActionFactoryConverter import com.gatehill.corebot.action.LockService -import com.gatehill.corebot.chat.template.ActionTemplateConverter -import com.gatehill.corebot.deploy.TriggerActionTemplateConverter import com.gatehill.corebot.driver.jenkins.JenkinsDriverModule +import com.gatehill.corebot.driver.jobs.action.TriggerActionFactoryConverter import com.gatehill.corebot.driver.rundeck.RundeckDriverModule import com.gatehill.corebot.store.DataStoreModule import com.google.inject.AbstractModule @@ -16,7 +16,7 @@ private class DeployBotModule : AbstractModule() { override fun configure() { bind(Bootstrap::class.java).asEagerSingleton() bind(LockService::class.java).asSingleton() - bind(ActionTemplateConverter::class.java).to(TriggerActionTemplateConverter::class.java).asSingleton() + bind(ActionFactoryConverter::class.java).to(TriggerActionFactoryConverter::class.java).asSingleton() // data stores install(DataStoreModule("lockStore")) diff --git a/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt b/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt index a56ecf50..286e3ef7 100644 --- a/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt +++ b/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Bootstrap.kt @@ -1,17 +1,17 @@ package com.gatehill.corebot -import com.gatehill.corebot.action.driver.ActionDriverFactory -import com.gatehill.corebot.chat.model.template.ShowHelpTemplate +import com.gatehill.corebot.action.factory.ShowHelpFactory import com.gatehill.corebot.chat.template.TemplateConfigService import com.gatehill.corebot.chat.template.TemplateService +import com.gatehill.corebot.driver.ActionDriverFactory import com.gatehill.corebot.driver.items.action.ItemsActionDriverImpl -import com.gatehill.corebot.driver.items.chat.model.template.BorrowItemAsUserTemplate -import com.gatehill.corebot.driver.items.chat.model.template.BorrowItemTemplate -import com.gatehill.corebot.driver.items.chat.model.template.EvictItemTemplate -import com.gatehill.corebot.driver.items.chat.model.template.EvictUserFromItemTemplate -import com.gatehill.corebot.driver.items.chat.model.template.ReturnItemTemplate -import com.gatehill.corebot.driver.items.chat.model.template.StatusAllTemplate -import com.gatehill.corebot.driver.items.chat.model.template.StatusItemTemplate +import com.gatehill.corebot.driver.items.action.factory.BorrowItemAsUserFactory +import com.gatehill.corebot.driver.items.action.factory.BorrowItemFactory +import com.gatehill.corebot.driver.items.action.factory.EvictItemFactory +import com.gatehill.corebot.driver.items.action.factory.EvictUserFromItemFactory +import com.gatehill.corebot.driver.items.action.factory.ReturnItemFactory +import com.gatehill.corebot.driver.items.action.factory.StatusAllFactory +import com.gatehill.corebot.driver.items.action.factory.StatusItemFactory import javax.inject.Inject /** @@ -26,13 +26,13 @@ class Bootstrap @Inject constructor(actionDriverFactory: ActionDriverFactory, // templates templateConfigService.registerClasspathTemplateFile("/items-templates.yml") - templateService.registerTemplate(ShowHelpTemplate::class.java) - templateService.registerTemplate(BorrowItemTemplate::class.java) - templateService.registerTemplate(BorrowItemAsUserTemplate::class.java) - templateService.registerTemplate(ReturnItemTemplate::class.java) - templateService.registerTemplate(EvictItemTemplate::class.java) - templateService.registerTemplate(EvictUserFromItemTemplate::class.java) - templateService.registerTemplate(StatusItemTemplate::class.java) - templateService.registerTemplate(StatusAllTemplate::class.java) + templateService.registerTemplate(ShowHelpFactory::class.java) + templateService.registerTemplate(BorrowItemFactory::class.java) + templateService.registerTemplate(BorrowItemAsUserFactory::class.java) + templateService.registerTemplate(ReturnItemFactory::class.java) + templateService.registerTemplate(EvictItemFactory::class.java) + templateService.registerTemplate(EvictUserFromItemFactory::class.java) + templateService.registerTemplate(StatusItemFactory::class.java) + templateService.registerTemplate(StatusAllFactory::class.java) } } diff --git a/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Main.kt b/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Main.kt index c24f0501..740fc4a5 100644 --- a/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Main.kt +++ b/bots/slack-items/src/main/kotlin/com/gatehill/corebot/Main.kt @@ -1,7 +1,7 @@ package com.gatehill.corebot -import com.gatehill.corebot.chat.template.ActionTemplateConverter -import com.gatehill.corebot.chat.template.NoOpActionTemplateConverter +import com.gatehill.corebot.action.ActionFactoryConverter +import com.gatehill.corebot.action.NoOpActionFactoryConverter import com.gatehill.corebot.driver.items.ItemsDriverModule import com.gatehill.corebot.store.DataStoreModule import com.google.inject.AbstractModule @@ -13,7 +13,7 @@ fun main(args: Array) { private class ItemsBotModule : AbstractModule() { override fun configure() { bind(Bootstrap::class.java).asEagerSingleton() - bind(ActionTemplateConverter::class.java).to(NoOpActionTemplateConverter::class.java).asSingleton() + bind(ActionFactoryConverter::class.java).to(NoOpActionFactoryConverter::class.java).asSingleton() // data stores install(DataStoreModule("itemStore")) diff --git a/core/api/src/main/kotlin/com/gatehill/corebot/chat/model/action/ActionType.kt b/core/api/src/main/kotlin/com/gatehill/corebot/action/model/ActionType.kt similarity index 92% rename from core/api/src/main/kotlin/com/gatehill/corebot/chat/model/action/ActionType.kt rename to core/api/src/main/kotlin/com/gatehill/corebot/action/model/ActionType.kt index 9e7bbd21..b46197ab 100644 --- a/core/api/src/main/kotlin/com/gatehill/corebot/chat/model/action/ActionType.kt +++ b/core/api/src/main/kotlin/com/gatehill/corebot/action/model/ActionType.kt @@ -1,4 +1,4 @@ -package com.gatehill.corebot.chat.model.action +package com.gatehill.corebot.action.model /** * Actions that can be performed. Doubles as a list of named permissions in the security configuration. diff --git a/core/api/src/main/kotlin/com/gatehill/corebot/action/model/PerformActionRequest.kt b/core/api/src/main/kotlin/com/gatehill/corebot/action/model/PerformActionRequest.kt index 1e78bd80..b4e503af 100644 --- a/core/api/src/main/kotlin/com/gatehill/corebot/action/model/PerformActionRequest.kt +++ b/core/api/src/main/kotlin/com/gatehill/corebot/action/model/PerformActionRequest.kt @@ -1,6 +1,5 @@ package com.gatehill.corebot.action.model -import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.model.ActionConfig /** diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/CoreModule.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/CoreModule.kt index 8bfa28ff..2971810a 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/CoreModule.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/CoreModule.kt @@ -1,7 +1,7 @@ package com.gatehill.corebot -import com.gatehill.corebot.chat.parser.RegexParser -import com.gatehill.corebot.chat.parser.StringParser +import com.gatehill.corebot.chat.filter.RegexFilter +import com.gatehill.corebot.chat.filter.StringFilter import com.gatehill.corebot.chat.template.TemplateConfigService import com.google.inject.AbstractModule @@ -13,7 +13,7 @@ import com.google.inject.AbstractModule class CoreModule : AbstractModule() { override fun configure() { bind(TemplateConfigService::class.java).asSingleton() - bind(StringParser::class.java).asSingleton() - bind(RegexParser::class.java).asSingleton() + bind(StringFilter::class.java).asSingleton() + bind(RegexFilter::class.java).asSingleton() } } diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/ActionFactoryConverter.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/ActionFactoryConverter.kt new file mode 100644 index 00000000..0c3a5f0c --- /dev/null +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/ActionFactoryConverter.kt @@ -0,0 +1,18 @@ +package com.gatehill.corebot.action + +import com.gatehill.corebot.action.factory.ActionFactory +import com.gatehill.corebot.config.model.ActionConfig + +/** + * @author Pete Cornish {@literal } + */ +interface ActionFactoryConverter { + fun convertConfigToFactory(configs: Iterable): Collection +} + +/** + * An implementation that returns an empty `List`. + */ +class NoOpActionFactoryConverter : ActionFactoryConverter { + override fun convertConfigToFactory(configs: Iterable): Collection = emptyList() +} diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/ActionOutcomeService.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/ActionOutcomeService.kt index 6ed02727..117fef39 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/action/ActionOutcomeService.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/ActionOutcomeService.kt @@ -1,8 +1,8 @@ package com.gatehill.corebot.action -import com.gatehill.corebot.action.model.ActionStatus import com.gatehill.corebot.action.model.TriggerContext import com.gatehill.corebot.config.model.ActionConfig +import com.gatehill.corebot.driver.model.ActionStatus /** * Handles the outcome of performing an action and notifies the user appropriately. diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/ActionOutcomeServiceImpl.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/ActionOutcomeServiceImpl.kt index af7e743d..d5ebfe96 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/action/ActionOutcomeServiceImpl.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/ActionOutcomeServiceImpl.kt @@ -1,11 +1,11 @@ package com.gatehill.corebot.action -import com.gatehill.corebot.action.model.ActionStatus import com.gatehill.corebot.action.model.TriggerContext import com.gatehill.corebot.chat.ChatGenerator import com.gatehill.corebot.chat.SessionService import com.gatehill.corebot.config.Settings import com.gatehill.corebot.config.model.ActionConfig +import com.gatehill.corebot.driver.model.ActionStatus import org.apache.logging.log4j.LogManager import java.util.concurrent.TimeUnit import javax.inject.Inject diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/LockService.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/LockService.kt index 97eb88a9..6f42dec0 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/action/LockService.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/LockService.kt @@ -1,8 +1,8 @@ package com.gatehill.corebot.action +import com.gatehill.corebot.action.factory.BaseLockOptionFactory import com.gatehill.corebot.action.model.PerformActionResult import com.gatehill.corebot.chat.ChatGenerator -import com.gatehill.corebot.chat.model.template.BaseLockOptionTemplate import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.store.DataStore import com.gatehill.corebot.store.partition @@ -78,8 +78,8 @@ class LockService @Inject constructor(@Named("lockStore") private val lockStore: fun lockOption(future: CompletableFuture, action: ActionConfig, args: Map, triggerMessageSenderId: String) { - val optionName = args[BaseLockOptionTemplate.optionNamePlaceholder]!! - val optionValue = args[BaseLockOptionTemplate.optionValuePlaceholder]!! + val optionName = args[BaseLockOptionFactory.optionNamePlaceholder]!! + val optionValue = args[BaseLockOptionFactory.optionValuePlaceholder]!! val lock = optionLocks[action.name] if (isOptionLocked(lock, optionName, optionValue)) { @@ -105,8 +105,8 @@ class LockService @Inject constructor(@Named("lockStore") private val lockStore: } fun unlockOption(future: CompletableFuture, action: ActionConfig, args: Map) { - val optionName = args[BaseLockOptionTemplate.optionNamePlaceholder]!! - val optionValue = args[BaseLockOptionTemplate.optionValuePlaceholder]!! + val optionName = args[BaseLockOptionFactory.optionNamePlaceholder]!! + val optionValue = args[BaseLockOptionFactory.optionValuePlaceholder]!! val lock = optionLocks[action.name] if (isOptionLocked(lock, optionName, optionValue)) { diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/ActionFactory.kt similarity index 56% rename from core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt rename to core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/ActionFactory.kt index 6e8ba9f2..fb64bcaa 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ActionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/ActionFactory.kt @@ -1,32 +1,27 @@ -package com.gatehill.corebot.chat.model.template +package com.gatehill.corebot.action.factory +import com.gatehill.corebot.action.model.Action +import com.gatehill.corebot.action.model.ActionType import com.gatehill.corebot.action.model.TriggerContext -import com.gatehill.corebot.chat.model.action.Action -import com.gatehill.corebot.chat.model.action.ActionType -import com.gatehill.corebot.chat.parser.ParserConfig +import com.gatehill.corebot.chat.filter.FilterConfig import com.gatehill.corebot.config.model.ActionConfig /** - * An abstract representation of a templated action. + * Produces actions of a given type. */ -interface ActionTemplate { - val parsers: MutableList +interface ActionFactory { + val parsers: MutableList val builtIn: Boolean val showInUsage: Boolean val actionType: ActionType val actionMessageMode: ActionMessageMode val placeholderValues: MutableMap - /** - * Describe the action templates as a human-readable `String`. - */ - val actionTemplates: String - /** * Hook for subclasses to do things like manipulate placeholders once * the template has been fully satisfied. */ - fun onTemplateSatisfied() = true + fun onSatisfied() = true /** * List the actions from this template. @@ -47,14 +42,13 @@ interface ActionTemplate { } /** - * Must be implemented by templates that can have regex parser config. + * Metadata for a template. + * Specifying `placeholderKeys` allows regex templates. */ -interface PlaceholderKeysTemplate { - val placeholderKeys: List -} - -// TODO replace this with an annotation, which includes the `placeholderKeys` -interface RegexActionTemplate : ActionTemplate, PlaceholderKeysTemplate +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class Template(val templateName: String, + val placeholderKeys: Array = emptyArray()) enum class ActionMessageMode { INDIVIDUAL, diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/BaseActionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/BaseActionFactory.kt similarity index 70% rename from core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/BaseActionTemplate.kt rename to core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/BaseActionFactory.kt index db10db65..75454dcc 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/BaseActionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/BaseActionFactory.kt @@ -1,21 +1,17 @@ -package com.gatehill.corebot.chat.model.template +package com.gatehill.corebot.action.factory import com.gatehill.corebot.action.model.TriggerContext -import com.gatehill.corebot.chat.parser.ParserConfig +import com.gatehill.corebot.chat.filter.FilterConfig import com.gatehill.corebot.config.model.ActionConfig -import com.gatehill.corebot.config.model.readActionConfigAttribute /** * Parses tokens into placeholder values. */ -abstract class BaseActionTemplate : ActionTemplate { - override val parsers = mutableListOf() +abstract class BaseActionFactory : ActionFactory { + override val parsers = mutableListOf() protected abstract val actionConfigs: List override val placeholderValues = mutableMapOf() - override val actionTemplates: String - get() = readActionConfigAttribute(actionConfigs, ActionConfig::template) - /** * A short, human readable description. */ @@ -30,7 +26,7 @@ abstract class BaseActionTemplate : ActionTemplate { override fun equals(other: Any?): Boolean { if (this === other) return true - if (other !is BaseActionTemplate) return false + if (other !is BaseActionFactory) return false return (javaClass != other.javaClass) } diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/BaseLockOptionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/BaseLockOptionFactory.kt similarity index 77% rename from core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/BaseLockOptionTemplate.kt rename to core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/BaseLockOptionFactory.kt index 664fc8d2..c13ce583 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/BaseLockOptionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/BaseLockOptionFactory.kt @@ -1,8 +1,8 @@ -package com.gatehill.corebot.chat.model.template +package com.gatehill.corebot.action.factory -import com.gatehill.corebot.chat.ChatGenerator +import com.gatehill.corebot.action.model.CoreActionType import com.gatehill.corebot.action.model.TriggerContext -import com.gatehill.corebot.chat.model.action.CoreActionType +import com.gatehill.corebot.chat.ChatGenerator import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig import javax.inject.Inject @@ -10,8 +10,8 @@ import javax.inject.Inject /** * Locks or unlocks an option value. */ -abstract class BaseLockOptionTemplate @Inject constructor(private val configService: ConfigService, - private val chatGenerator: ChatGenerator) : CustomActionTemplate() { +abstract class BaseLockOptionFactory @Inject constructor(private val configService: ConfigService, + private val chatGenerator: ChatGenerator) : CustomActionFactory() { override val builtIn = true override val showInUsage = true @@ -24,7 +24,7 @@ abstract class BaseLockOptionTemplate @Inject constructor(private val configServ private val optionValue: String get() = placeholderValues[optionValuePlaceholder]!! - override fun onTemplateSatisfied(): Boolean { + override fun onSatisfied(): Boolean { configService.actions().values.forEach { potentialConfig -> val lockableOptions = potentialConfig.options .filter { option -> option.key.equals(optionName, ignoreCase = true) } @@ -46,7 +46,7 @@ abstract class BaseLockOptionTemplate @Inject constructor(private val configServ } companion object { - val optionNamePlaceholder = "option name" - val optionValuePlaceholder = "option value" + const val optionNamePlaceholder = "option name" + const val optionValuePlaceholder = "option value" } } diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/CustomActionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/CustomActionFactory.kt similarity index 89% rename from core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/CustomActionTemplate.kt rename to core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/CustomActionFactory.kt index fe449c37..730dd763 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/CustomActionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/CustomActionFactory.kt @@ -1,8 +1,8 @@ -package com.gatehill.corebot.chat.model.template +package com.gatehill.corebot.action.factory +import com.gatehill.corebot.action.model.Action +import com.gatehill.corebot.action.model.CustomAction import com.gatehill.corebot.action.model.TriggerContext -import com.gatehill.corebot.chat.model.action.Action -import com.gatehill.corebot.chat.model.action.CustomAction import com.gatehill.corebot.config.model.ActionConfig import com.gatehill.corebot.config.model.TransformType import java.util.HashMap @@ -10,7 +10,7 @@ import java.util.HashMap /** * Represents a custom action. */ -abstract class CustomActionTemplate : BaseActionTemplate() { +abstract class CustomActionFactory : BaseActionFactory() { /** * List the actions from this template. */ diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/LockActionFactory.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/LockActionFactory.kt new file mode 100644 index 00000000..18844c35 --- /dev/null +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/LockActionFactory.kt @@ -0,0 +1,15 @@ +package com.gatehill.corebot.action.factory + +import com.gatehill.corebot.action.model.ActionType +import com.gatehill.corebot.action.model.CoreActionType +import com.gatehill.corebot.config.ConfigService +import javax.inject.Inject + +/** + * Locks an action. + */ +@Template("lockAction") +class LockActionFactory @Inject constructor(configService: ConfigService) : NamedActionFactory(configService) { + override val actionType: ActionType = CoreActionType.LOCK_ACTION + override val actionMessageMode = ActionMessageMode.INDIVIDUAL +} diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/LockOptionFactory.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/LockOptionFactory.kt new file mode 100644 index 00000000..0ebf75a6 --- /dev/null +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/LockOptionFactory.kt @@ -0,0 +1,17 @@ +package com.gatehill.corebot.action.factory + +import com.gatehill.corebot.action.model.ActionType +import com.gatehill.corebot.action.model.CoreActionType +import com.gatehill.corebot.chat.ChatGenerator +import com.gatehill.corebot.config.ConfigService +import javax.inject.Inject + +/** + * Locks an option value. + */ +@Template("lockOption") +class LockOptionFactory @Inject constructor(configService: ConfigService, + chatGenerator: ChatGenerator) : BaseLockOptionFactory(configService, chatGenerator) { + + override val actionType: ActionType = CoreActionType.LOCK_OPTION +} diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/NamedActionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/NamedActionFactory.kt similarity index 81% rename from core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/NamedActionTemplate.kt rename to core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/NamedActionFactory.kt index daddd52b..190b5a24 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/NamedActionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/NamedActionFactory.kt @@ -1,4 +1,4 @@ -package com.gatehill.corebot.chat.model.template +package com.gatehill.corebot.action.factory import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig @@ -7,13 +7,13 @@ import javax.inject.Inject /** * Represents a simple operation for a named action. */ -abstract class NamedActionTemplate @Inject constructor(private val configService: ConfigService) : CustomActionTemplate() { +abstract class NamedActionFactory @Inject constructor(private val configService: ConfigService) : CustomActionFactory() { protected val actionPlaceholder = "action or tag name" override val builtIn: Boolean = true override val showInUsage: Boolean = true override val actionConfigs = mutableListOf() - override fun onTemplateSatisfied(): Boolean { + override fun onSatisfied(): Boolean { val actionOrTagName = placeholderValues[actionPlaceholder] val potentialConfigs = configService.actions() diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ShowHelpTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/ShowHelpFactory.kt similarity index 68% rename from core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ShowHelpTemplate.kt rename to core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/ShowHelpFactory.kt index c9c34e92..af2802a1 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/ShowHelpTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/ShowHelpFactory.kt @@ -1,17 +1,18 @@ -package com.gatehill.corebot.chat.model.template +package com.gatehill.corebot.action.factory +import com.gatehill.corebot.action.model.CoreActionType import com.gatehill.corebot.action.model.TriggerContext import com.gatehill.corebot.chat.ChatGenerator import com.gatehill.corebot.chat.template.TemplateService -import com.gatehill.corebot.chat.model.action.CoreActionType import com.gatehill.corebot.config.model.ActionConfig import javax.inject.Inject /** * Shows a help/usage message. */ -class ShowHelpTemplate @Inject constructor(private val templateService: TemplateService, - private val chatGenerator: ChatGenerator) : SystemActionTemplate() { +@Template("help") +class ShowHelpFactory @Inject constructor(private val templateService: TemplateService, + private val chatGenerator: ChatGenerator) : SystemActionFactory() { override val showInUsage = false override val actionType = CoreActionType.HELP diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/StatusActionFactory.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/StatusActionFactory.kt new file mode 100644 index 00000000..39de47e3 --- /dev/null +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/StatusActionFactory.kt @@ -0,0 +1,15 @@ +package com.gatehill.corebot.action.factory + +import com.gatehill.corebot.action.model.ActionType +import com.gatehill.corebot.action.model.CoreActionType +import com.gatehill.corebot.config.ConfigService +import javax.inject.Inject + +/** + * Prints status information about an action. + */ +@Template("statusAction") +class StatusActionFactory @Inject constructor(configService: ConfigService) : NamedActionFactory(configService) { + override val actionType: ActionType = CoreActionType.STATUS + override val actionMessageMode = ActionMessageMode.INDIVIDUAL +} diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/SystemActionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/SystemActionFactory.kt similarity index 70% rename from core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/SystemActionTemplate.kt rename to core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/SystemActionFactory.kt index f44dc0f4..39c8b1c9 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/SystemActionTemplate.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/SystemActionFactory.kt @@ -1,14 +1,14 @@ -package com.gatehill.corebot.chat.model.template +package com.gatehill.corebot.action.factory +import com.gatehill.corebot.action.model.Action +import com.gatehill.corebot.action.model.SystemAction import com.gatehill.corebot.action.model.TriggerContext -import com.gatehill.corebot.chat.model.action.Action -import com.gatehill.corebot.chat.model.action.SystemAction import com.gatehill.corebot.config.model.ActionConfig /** * Represents a system action. */ -abstract class SystemActionTemplate : BaseActionTemplate() { +abstract class SystemActionFactory : BaseActionFactory() { override val builtIn = true override val actionConfigs = emptyList() diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/UnlockActionFactory.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/UnlockActionFactory.kt new file mode 100644 index 00000000..ebfaf844 --- /dev/null +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/UnlockActionFactory.kt @@ -0,0 +1,15 @@ +package com.gatehill.corebot.action.factory + +import com.gatehill.corebot.action.model.ActionType +import com.gatehill.corebot.action.model.CoreActionType +import com.gatehill.corebot.config.ConfigService +import javax.inject.Inject + +/** + * Unlocks an action. + */ +@Template("unlockAction") +class UnlockActionFactory @Inject constructor(configService: ConfigService) : NamedActionFactory(configService) { + override val actionType: ActionType = CoreActionType.UNLOCK_ACTION + override val actionMessageMode = ActionMessageMode.INDIVIDUAL +} diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/UnlockOptionFactory.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/UnlockOptionFactory.kt new file mode 100644 index 00000000..c0fbe1ac --- /dev/null +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/UnlockOptionFactory.kt @@ -0,0 +1,17 @@ +package com.gatehill.corebot.action.factory + +import com.gatehill.corebot.action.model.ActionType +import com.gatehill.corebot.action.model.CoreActionType +import com.gatehill.corebot.chat.ChatGenerator +import com.gatehill.corebot.config.ConfigService +import javax.inject.Inject + +/** + * Unlocks an option value. + */ +@Template("unlockOption") +class UnlockOptionFactory @Inject constructor(configService: ConfigService, + chatGenerator: ChatGenerator) : BaseLockOptionFactory(configService, chatGenerator) { + + override val actionType: ActionType = CoreActionType.UNLOCK_OPTION +} diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/action/Action.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/model/Action.kt similarity index 88% rename from core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/action/Action.kt rename to core/engine/src/main/kotlin/com/gatehill/corebot/action/model/Action.kt index 7054b309..eee05b40 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/action/Action.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/model/Action.kt @@ -1,4 +1,4 @@ -package com.gatehill.corebot.chat.model.action +package com.gatehill.corebot.action.model /** * Represents an action to perform. diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/action/ActionWrapper.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/model/ActionWrapper.kt similarity index 84% rename from core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/action/ActionWrapper.kt rename to core/engine/src/main/kotlin/com/gatehill/corebot/action/model/ActionWrapper.kt index 28dcc1a0..335b0dae 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/action/ActionWrapper.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/model/ActionWrapper.kt @@ -1,4 +1,4 @@ -package com.gatehill.corebot.chat.model.action +package com.gatehill.corebot.action.model /** * Groups actions and tracks successful executions. diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/action/CoreActionType.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/model/CoreActionType.kt similarity index 92% rename from core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/action/CoreActionType.kt rename to core/engine/src/main/kotlin/com/gatehill/corebot/action/model/CoreActionType.kt index eabcf3e3..d7371933 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/action/CoreActionType.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/model/CoreActionType.kt @@ -1,4 +1,4 @@ -package com.gatehill.corebot.chat.model.action +package com.gatehill.corebot.action.model /** * The core action types. diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/action/CustomAction.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/model/CustomAction.kt similarity index 93% rename from core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/action/CustomAction.kt rename to core/engine/src/main/kotlin/com/gatehill/corebot/action/model/CustomAction.kt index c1bf893f..23b6809b 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/action/CustomAction.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/model/CustomAction.kt @@ -1,4 +1,4 @@ -package com.gatehill.corebot.chat.model.action +package com.gatehill.corebot.action.model import com.gatehill.corebot.config.model.ActionConfig diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/action/SystemAction.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/model/SystemAction.kt similarity index 87% rename from core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/action/SystemAction.kt rename to core/engine/src/main/kotlin/com/gatehill/corebot/action/model/SystemAction.kt index d43a5ab3..9720a733 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/action/SystemAction.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/model/SystemAction.kt @@ -1,4 +1,4 @@ -package com.gatehill.corebot.chat.model.action +package com.gatehill.corebot.action.model /** * Represents a system action to perform. diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/CommandFilter.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/CommandFilter.kt new file mode 100644 index 00000000..6d88bb40 --- /dev/null +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/CommandFilter.kt @@ -0,0 +1,8 @@ +package com.gatehill.corebot.chat.filter + +import com.gatehill.corebot.action.factory.ActionFactory +import com.gatehill.corebot.action.factory.Template + +interface CommandFilter { + fun matches(config: FilterConfig, factory: ActionFactory, template: Template, command: String): Boolean +} diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/FilterConfig.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/FilterConfig.kt new file mode 100644 index 00000000..55187ba0 --- /dev/null +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/FilterConfig.kt @@ -0,0 +1,3 @@ +package com.gatehill.corebot.chat.filter + +abstract class FilterConfig(val usage: String?) diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/RegexFilter.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/RegexFilter.kt new file mode 100644 index 00000000..2abc679a --- /dev/null +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/RegexFilter.kt @@ -0,0 +1,22 @@ +package com.gatehill.corebot.chat.filter + +import com.gatehill.corebot.action.factory.ActionFactory +import com.gatehill.corebot.action.factory.Template +import java.util.regex.Pattern + +class RegexFilter : CommandFilter { + class RegexFilterConfig(val template: Pattern, + usage: String?) : FilterConfig(usage) + + override fun matches(config: FilterConfig, factory: ActionFactory, template: Template, command: String): Boolean = + parseCommand(config as RegexFilterConfig, factory, template, command) + + /** + * Filter candidates based on their templates. + */ + private fun parseCommand(config: RegexFilterConfig, factory: ActionFactory, template: Template, command: String) = + config.template.matcher(command).takeIf { it.matches() }?.let { matcher -> + factory.placeholderValues += template.placeholderKeys.map { it to matcher.group(it) }.toMap() + factory.onSatisfied() + } ?: false +} diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/StringParser.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/StringFilter.kt similarity index 54% rename from core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/StringParser.kt rename to core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/StringFilter.kt index 91c8fba8..bc38edfa 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/StringParser.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/StringFilter.kt @@ -1,6 +1,7 @@ -package com.gatehill.corebot.chat.parser +package com.gatehill.corebot.chat.filter -import com.gatehill.corebot.chat.model.template.ActionTemplate +import com.gatehill.corebot.action.factory.ActionFactory +import com.gatehill.corebot.action.factory.Template import java.util.LinkedList import java.util.Queue @@ -9,22 +10,22 @@ import java.util.Queue */ private val messagePartRegex = "\"?( |$)(?=(([^\"]*\"){2})*[^\"]*$)\"?".toRegex() -class StringParser : CommandParser { - class StringParserConfig(val template: String, - usage: String?) : ParserConfig(usage) +class StringFilter : CommandFilter { + class StringFilterConfig(val template: String, + usage: String?) : FilterConfig(usage) - override fun parse(config: ParserConfig, template: ActionTemplate, command: String) = - parseCommand(config as StringParserConfig, template, command) + override fun matches(config: FilterConfig, factory: ActionFactory, template: Template, command: String) = + parseCommand(config as StringFilterConfig, factory, command) /** * Split the command into elements and return `true` if all were processed successfully. */ - private fun parseCommand(config: StringParserConfig, template: ActionTemplate, command: String): Boolean { + private fun parseCommand(config: StringFilterConfig, factory: ActionFactory, command: String): Boolean { val tokens = LinkedList(config.template.split("\\s".toRegex())) command.trim().split(messagePartRegex).filterNot(String::isBlank).forEach { element -> // fail as soon as an element is rejected - if (!parseElement(template, tokens, element)) { + if (!parseElement(factory, tokens, element)) { return false } } @@ -36,13 +37,13 @@ class StringParser : CommandParser { /** * Parse a command element and return `true` if it was accepted. */ - private fun parseElement(template: ActionTemplate, tokens: Queue, element: String): Boolean { + private fun parseElement(factory: ActionFactory, tokens: Queue, element: String): Boolean { if (tokens.size == 0) return false val token = tokens.poll() val accepted = "\\{(.*)}".toRegex().matchEntire(token)?.let { match -> // option placeholder - template.placeholderValues[match.groupValues[1]] = element + factory.placeholderValues[match.groupValues[1]] = element true } ?: run { @@ -50,6 +51,6 @@ class StringParser : CommandParser { token.equals(element, ignoreCase = true) } - return if (accepted && tokens.isEmpty()) template.onTemplateSatisfied() else accepted + return if (accepted && tokens.isEmpty()) factory.onSatisfied() else accepted } } diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/LockActionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/LockActionTemplate.kt deleted file mode 100644 index 9bb0e97a..00000000 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/LockActionTemplate.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.gatehill.corebot.chat.model.template - -import com.gatehill.corebot.chat.model.action.ActionType -import com.gatehill.corebot.chat.model.action.CoreActionType -import com.gatehill.corebot.config.ConfigService -import javax.inject.Inject - -/** - * Locks an action. - */ -class LockActionTemplate @Inject constructor(configService: ConfigService) : NamedActionTemplate(configService) { - override val actionType: ActionType = CoreActionType.LOCK_ACTION - override val actionMessageMode = ActionMessageMode.INDIVIDUAL -} \ No newline at end of file diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/LockOptionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/LockOptionTemplate.kt deleted file mode 100644 index 0a490db9..00000000 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/LockOptionTemplate.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.gatehill.corebot.chat.model.template - -import com.gatehill.corebot.chat.ChatGenerator -import com.gatehill.corebot.chat.model.action.ActionType -import com.gatehill.corebot.chat.model.action.CoreActionType -import com.gatehill.corebot.config.ConfigService -import javax.inject.Inject - -/** - * Locks an option value. - */ -class LockOptionTemplate @Inject constructor(configService: ConfigService, - chatGenerator: ChatGenerator) : BaseLockOptionTemplate(configService, chatGenerator) { - - override val actionType: ActionType = CoreActionType.LOCK_OPTION -} diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/StatusActionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/StatusActionTemplate.kt deleted file mode 100644 index af0014ae..00000000 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/StatusActionTemplate.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.gatehill.corebot.chat.model.template - -import com.gatehill.corebot.chat.model.action.ActionType -import com.gatehill.corebot.chat.model.action.CoreActionType -import com.gatehill.corebot.config.ConfigService -import javax.inject.Inject - -/** - * Prints status information about an action. - */ -class StatusActionTemplate @Inject constructor(configService: ConfigService) : NamedActionTemplate(configService) { - override val actionType: ActionType = CoreActionType.STATUS - override val actionMessageMode = ActionMessageMode.INDIVIDUAL -} diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/UnlockActionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/UnlockActionTemplate.kt deleted file mode 100644 index 47652096..00000000 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/UnlockActionTemplate.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.gatehill.corebot.chat.model.template - -import com.gatehill.corebot.chat.model.action.ActionType -import com.gatehill.corebot.chat.model.action.CoreActionType -import com.gatehill.corebot.config.ConfigService -import javax.inject.Inject - -/** - * Unlocks an action. - */ -class UnlockActionTemplate @Inject constructor(configService: ConfigService) : NamedActionTemplate(configService) { - override val actionType: ActionType = CoreActionType.UNLOCK_ACTION - override val actionMessageMode = ActionMessageMode.INDIVIDUAL -} diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/UnlockOptionTemplate.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/UnlockOptionTemplate.kt deleted file mode 100644 index eab6c4d5..00000000 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/model/template/UnlockOptionTemplate.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.gatehill.corebot.chat.model.template - -import com.gatehill.corebot.chat.ChatGenerator -import com.gatehill.corebot.chat.model.action.ActionType -import com.gatehill.corebot.chat.model.action.CoreActionType -import com.gatehill.corebot.config.ConfigService -import javax.inject.Inject - -/** - * Unlocks an option value. - */ -class UnlockOptionTemplate @Inject constructor(configService: ConfigService, - chatGenerator: ChatGenerator) : BaseLockOptionTemplate(configService, chatGenerator) { - - override val actionType: ActionType = CoreActionType.UNLOCK_OPTION -} diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/CommandParser.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/CommandParser.kt deleted file mode 100644 index 239a0728..00000000 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/CommandParser.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.gatehill.corebot.chat.parser - -import com.gatehill.corebot.chat.model.template.ActionTemplate - -interface CommandParser { - fun parse(config: ParserConfig, template: ActionTemplate, command: String): Boolean -} diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/ParserConfig.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/ParserConfig.kt deleted file mode 100644 index 7475dece..00000000 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/ParserConfig.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.gatehill.corebot.chat.parser - -abstract class ParserConfig(val usage: String?) diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/RegexParser.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/RegexParser.kt deleted file mode 100644 index 07f725e9..00000000 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/parser/RegexParser.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.gatehill.corebot.chat.parser - -import com.gatehill.corebot.chat.model.template.ActionTemplate -import com.gatehill.corebot.chat.model.template.RegexActionTemplate -import java.util.regex.Pattern - -class RegexParser : CommandParser { - class RegexParserConfig(val template: Pattern, - usage: String?) : ParserConfig(usage) - - override fun parse(config: ParserConfig, template: ActionTemplate, command: String): Boolean = - parseCommand(config as RegexParserConfig, template as RegexActionTemplate, command) - - /** - * Filter candidates based on their templates. - */ - private fun parseCommand(config: RegexParserConfig, template: RegexActionTemplate, command: String) = - config.template.matcher(command).takeIf { it.matches() }?.let { matcher -> - template.placeholderValues += template.placeholderKeys.map { it to matcher.group(it) }.toMap() - template.onTemplateSatisfied() - } ?: false -} diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/ActionTemplateConverter.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/ActionTemplateConverter.kt deleted file mode 100644 index dc8924ad..00000000 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/ActionTemplateConverter.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.gatehill.corebot.chat.template - -import com.gatehill.corebot.chat.model.template.ActionTemplate -import com.gatehill.corebot.config.model.ActionConfig - -/** - * @author Pete Cornish {@literal } - */ -interface ActionTemplateConverter { - fun convertConfigToTemplate(configs: Iterable): Collection -} - -/** - * An implementation that returns an empty `List`. - */ -class NoOpActionTemplateConverter : ActionTemplateConverter { - override fun convertConfigToTemplate(configs: Iterable): Collection = emptyList() -} diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/TemplateConfigService.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/TemplateConfigService.kt index 7f9bdd2d..1d3c69e4 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/TemplateConfigService.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/TemplateConfigService.kt @@ -1,10 +1,11 @@ package com.gatehill.corebot.chat.template import com.fasterxml.jackson.module.kotlin.jacksonTypeRef -import com.gatehill.corebot.chat.model.template.ActionTemplate -import com.gatehill.corebot.chat.parser.ParserConfig -import com.gatehill.corebot.chat.parser.RegexParser -import com.gatehill.corebot.chat.parser.StringParser +import com.gatehill.corebot.action.factory.ActionFactory +import com.gatehill.corebot.action.factory.Template +import com.gatehill.corebot.chat.filter.FilterConfig +import com.gatehill.corebot.chat.filter.RegexFilter +import com.gatehill.corebot.chat.filter.StringFilter import com.gatehill.corebot.util.yamlMapper import java.io.InputStream import java.nio.file.Path @@ -56,17 +57,17 @@ class TemplateConfigService { yamlMapper.readValue>>(fileStream, jacksonTypeRef>>()) - fun loadParserConfig(templateName: String): List = + fun loadFilterConfig(templateName: String): List = allConfigs.filterKeys { it == templateName }.values.flatMap { config -> config.map { // TODO use regex instead if (it.template.startsWith("/") && it.template.endsWith("/")) { - RegexParser.RegexParserConfig( + RegexFilter.RegexFilterConfig( template = Pattern.compile(it.template.substring(1, it.template.length - 1)), usage = it.usage ) } else { - StringParser.StringParserConfig( + StringFilter.StringFilterConfig( template = it.template, usage = it.usage ?: it.template ) @@ -74,8 +75,12 @@ class TemplateConfigService { } } - fun loadParserConfig(templateClass: Class) = - loadParserConfig(templateClass.simpleName) + fun loadFilterConfig(factoryClass: Class) = + loadFilterConfig(readMetadata(factoryClass).templateName) + + fun readMetadata(factoryClass: Class): Template = + factoryClass.getAnnotationsByType(Template::class.java).firstOrNull() + ?: throw IllegalStateException("Missing @Template annotation for: ${factoryClass.canonicalName}") fun registerClasspathTemplateFile(classpathFile: String) { templateFiles += "$classpathPrefix$classpathFile" diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/TemplateService.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/TemplateService.kt index 676fc65a..ef2b8a0d 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/TemplateService.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/TemplateService.kt @@ -1,11 +1,12 @@ package com.gatehill.corebot.chat.template +import com.gatehill.corebot.action.ActionFactoryConverter +import com.gatehill.corebot.action.factory.ActionFactory import com.gatehill.corebot.chat.SessionService -import com.gatehill.corebot.chat.model.template.ActionTemplate -import com.gatehill.corebot.chat.parser.CommandParser -import com.gatehill.corebot.chat.parser.ParserConfig -import com.gatehill.corebot.chat.parser.RegexParser -import com.gatehill.corebot.chat.parser.StringParser +import com.gatehill.corebot.chat.filter.CommandFilter +import com.gatehill.corebot.chat.filter.FilterConfig +import com.gatehill.corebot.chat.filter.RegexFilter +import com.gatehill.corebot.chat.filter.StringFilter import com.gatehill.corebot.config.ConfigService import com.google.inject.Injector import org.apache.logging.log4j.LogManager @@ -17,18 +18,18 @@ import javax.inject.Inject class TemplateService @Inject constructor(private val injector: Injector, private val configService: ConfigService, private val sessionService: SessionService, - private val actionTemplateConverter: ActionTemplateConverter, + private val actionFactoryConverter: ActionFactoryConverter, private val templateConfigService: TemplateConfigService) { private val logger = LogManager.getLogger(TemplateService::class.java) /** - * Unique set of templates. + * Unique set of factories. */ - private val actionTemplates = mutableSetOf>() + private val actionFactories = mutableSetOf>() - fun registerTemplate(template: Class) { - actionTemplates += template + fun registerTemplate(factory: Class) { + actionFactories += factory } /** @@ -36,33 +37,38 @@ class TemplateService @Inject constructor(private val injector: Injector, * * @param commandOnly - the command, excluding any initial bot reference */ - fun findSatisfiedTemplates(commandOnly: String): Collection = fetchCandidates() - .filter { template -> template.parsers.any { loadParser(it).parse(it, template, commandOnly) } } - .toSet() + fun findSatisfiedTemplates(commandOnly: String): Collection = + fetchCandidates().filter { factory -> factory.parsers.any { filterMatch(it, factory, commandOnly) } }.toSet() /** - * Load the `CommandParser` strategy for the given configuration. + * Invoke the filter's match function for the given factory. */ - private fun loadParser(parserConfig: ParserConfig): CommandParser = when (parserConfig) { - is StringParser.StringParserConfig -> injector.getInstance(StringParser::class.java) - is RegexParser.RegexParserConfig -> injector.getInstance(RegexParser::class.java) - else -> throw UnsupportedOperationException("Unsupported parser config: ${parserConfig::class.java.canonicalName}") + private fun filterMatch(config: FilterConfig, factory: ActionFactory, commandOnly: String) = + loadFilter(config).matches(config, factory, templateConfigService.readMetadata(factory::class.java), commandOnly) + + /** + * Load the filter for the given configuration. + */ + private fun loadFilter(config: FilterConfig): CommandFilter = when (config) { + is StringFilter.StringFilterConfig -> injector.getInstance(StringFilter::class.java) + is RegexFilter.RegexFilterConfig -> injector.getInstance(RegexFilter::class.java) + else -> throw UnsupportedOperationException("Unsupported filter config: ${config::class.java.canonicalName}") } /** * Return a new `Set` of candidates. */ - private fun fetchCandidates(): Set = mutableSetOf().apply { - addAll(actionTemplateConverter.convertConfigToTemplate(configService.actions().values)) - addAll(actionTemplates.map({ actionTemplate -> injector.getInstance(actionTemplate) })) + private fun fetchCandidates(): Set = mutableSetOf().apply { + addAll(actionFactoryConverter.convertConfigToFactory(configService.actions().values)) + addAll(actionFactories.map({ actionTemplate -> injector.getInstance(actionTemplate) })) - // populate the parser configurations + // populate the filter configurations forEach { template -> - template.parsers += templateConfigService.loadParserConfig(template::class.java) + template.parsers += templateConfigService.loadFilterConfig(template::class.java) // no parsers have been set if (template.parsers.isEmpty()) { - logger.warn("No parser configuration found for template: ${template::class.java.simpleName} - action ${template.actionType.name} cannot be invoked") + logger.warn("No filter configuration found for template: ${template::class.java.simpleName} - action ${template.actionType.name} cannot be invoked") } } } @@ -80,7 +86,7 @@ class TemplateService @Inject constructor(private val injector: Injector, } } - val printTemplate: (ActionTemplate) -> Unit = { candidate -> + val printTemplate: (ActionFactory) -> Unit = { candidate -> appendln() append(candidate.parsers .filter { it.usage != null } @@ -88,7 +94,7 @@ class TemplateService @Inject constructor(private val injector: Injector, .joinToString("\n")) } - val customActions = sortedCandidates.filter(ActionTemplate::showInUsage).filterNot(ActionTemplate::builtIn) + val customActions = sortedCandidates.filter(ActionFactory::showInUsage).filterNot(ActionFactory::builtIn) if (customActions.isNotEmpty()) { append("*Custom actions*") customActions.forEach(printTemplate) @@ -96,7 +102,7 @@ class TemplateService @Inject constructor(private val injector: Injector, if (isNotEmpty()) repeat(2) { appendln() } - val builtInActions = sortedCandidates.filter(ActionTemplate::showInUsage).filter(ActionTemplate::builtIn) + val builtInActions = sortedCandidates.filter(ActionFactory::showInUsage).filter(ActionFactory::builtIn) if (builtInActions.isNotEmpty()) { append("*Built-in actions*") builtInActions.forEach(printTemplate) diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/driver/ActionDriver.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/driver/ActionDriver.kt similarity index 84% rename from core/engine/src/main/kotlin/com/gatehill/corebot/action/driver/ActionDriver.kt rename to core/engine/src/main/kotlin/com/gatehill/corebot/driver/ActionDriver.kt index 9ae84b39..2d388a03 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/action/driver/ActionDriver.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/driver/ActionDriver.kt @@ -1,8 +1,8 @@ -package com.gatehill.corebot.action.driver +package com.gatehill.corebot.driver +import com.gatehill.corebot.action.model.ActionType import com.gatehill.corebot.action.model.PerformActionResult import com.gatehill.corebot.action.model.TriggerContext -import com.gatehill.corebot.chat.model.action.ActionType import com.gatehill.corebot.config.model.ActionConfig import java.util.concurrent.CompletableFuture diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/driver/ActionDriverFactory.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/driver/ActionDriverFactory.kt similarity index 96% rename from core/engine/src/main/kotlin/com/gatehill/corebot/action/driver/ActionDriverFactory.kt rename to core/engine/src/main/kotlin/com/gatehill/corebot/driver/ActionDriverFactory.kt index 00e240c8..837c81ae 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/action/driver/ActionDriverFactory.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/driver/ActionDriverFactory.kt @@ -1,4 +1,4 @@ -package com.gatehill.corebot.action.driver +package com.gatehill.corebot.driver import com.google.inject.Injector import org.apache.logging.log4j.LogManager diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/driver/BaseActionDriver.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/driver/BaseActionDriver.kt similarity index 94% rename from core/engine/src/main/kotlin/com/gatehill/corebot/action/driver/BaseActionDriver.kt rename to core/engine/src/main/kotlin/com/gatehill/corebot/driver/BaseActionDriver.kt index 4f4eef71..488e17d4 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/action/driver/BaseActionDriver.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/driver/BaseActionDriver.kt @@ -1,10 +1,10 @@ -package com.gatehill.corebot.action.driver +package com.gatehill.corebot.driver import com.gatehill.corebot.action.LockService +import com.gatehill.corebot.action.model.ActionType +import com.gatehill.corebot.action.model.CoreActionType import com.gatehill.corebot.action.model.PerformActionResult import com.gatehill.corebot.action.model.TriggerContext -import com.gatehill.corebot.chat.model.action.ActionType -import com.gatehill.corebot.chat.model.action.CoreActionType import com.gatehill.corebot.config.model.ActionConfig import java.util.concurrent.CompletableFuture import javax.inject.Inject diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/model/ActionStatus.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/driver/model/ActionStatus.kt similarity index 87% rename from core/engine/src/main/kotlin/com/gatehill/corebot/action/model/ActionStatus.kt rename to core/engine/src/main/kotlin/com/gatehill/corebot/driver/model/ActionStatus.kt index 3aa23acf..0c0aedb7 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/action/model/ActionStatus.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/driver/model/ActionStatus.kt @@ -1,4 +1,4 @@ -package com.gatehill.corebot.action.model +package com.gatehill.corebot.driver.model /** * Models the status of a triggered action. diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/model/TriggeredAction.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/driver/model/TriggeredAction.kt similarity index 87% rename from core/engine/src/main/kotlin/com/gatehill/corebot/action/model/TriggeredAction.kt rename to core/engine/src/main/kotlin/com/gatehill/corebot/driver/model/TriggeredAction.kt index 7f68dbee..bf8a3383 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/action/model/TriggeredAction.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/driver/model/TriggeredAction.kt @@ -1,4 +1,4 @@ -package com.gatehill.corebot.action.model +package com.gatehill.corebot.driver.model import com.fasterxml.jackson.annotation.JsonIgnoreProperties diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/security/AuthorisationService.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/security/AuthorisationService.kt index e4bbfd71..7dcce2e6 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/security/AuthorisationService.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/security/AuthorisationService.kt @@ -1,6 +1,6 @@ package com.gatehill.corebot.security -import com.gatehill.corebot.chat.model.action.Action +import com.gatehill.corebot.action.model.Action import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.SecurityConfig import com.gatehill.corebot.config.model.SecurityUserConfig diff --git a/core/engine/src/main/resources/core-templates.yml b/core/engine/src/main/resources/core-templates.yml index 583c5712..098c4da5 100644 --- a/core/engine/src/main/resources/core-templates.yml +++ b/core/engine/src/main/resources/core-templates.yml @@ -1,21 +1,21 @@ # Core templates. --- -ShowHelpTemplate: +help: - template: help - template: usage -LockActionTemplate: +lockAction: - template: lock {action or tag name} -UnlockActionTemplate: +unlockAction: - template: unlock {action or tag name} -StatusActionTemplate: +statusAction: - template: status {action or tag name} - template: status of {action or tag name} -LockOptionTemplate: +lockOption: - template: lock {option name} {option value} -UnlockOptionTemplate: +unlockOption: - template: unlock {option name} {option value} diff --git a/frontends/common/src/main/kotlin/com/gatehill/corebot/chat/MessageService.kt b/frontends/common/src/main/kotlin/com/gatehill/corebot/chat/MessageService.kt index 73dda09f..538eb609 100644 --- a/frontends/common/src/main/kotlin/com/gatehill/corebot/chat/MessageService.kt +++ b/frontends/common/src/main/kotlin/com/gatehill/corebot/chat/MessageService.kt @@ -1,12 +1,12 @@ package com.gatehill.corebot.chat import com.gatehill.corebot.action.ActionPerformService +import com.gatehill.corebot.action.factory.ActionMessageMode +import com.gatehill.corebot.action.model.Action +import com.gatehill.corebot.action.model.ActionWrapper +import com.gatehill.corebot.action.model.CustomAction import com.gatehill.corebot.action.model.PerformActionRequest import com.gatehill.corebot.action.model.TriggerContext -import com.gatehill.corebot.chat.model.action.Action -import com.gatehill.corebot.chat.model.action.ActionWrapper -import com.gatehill.corebot.chat.model.action.CustomAction -import com.gatehill.corebot.chat.model.template.ActionMessageMode import com.gatehill.corebot.chat.template.TemplateService import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.Settings From 5fc93bf1a4ee8675a613b3fc4cc5aa357dd793e5 Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Sun, 2 Jul 2017 01:35:02 +0100 Subject: [PATCH 33/36] Moves more factory members into Template annotation. --- .../jobs/action/factory/DisableJobFactory.kt | 3 +- .../jobs/action/factory/EnableJobFactory.kt | 3 +- .../jobs/action/factory/TriggerJobFactory.kt | 5 +--- .../items/action/factory/BaseItemFactory.kt | 4 --- .../action/factory/BorrowItemAsUserFactory.kt | 14 +++++---- .../items/action/factory/BorrowItemFactory.kt | 12 ++++---- .../items/action/factory/EvictItemFactory.kt | 3 +- .../factory/EvictUserFromItemFactory.kt | 10 ++++--- .../items/action/factory/ReturnItemFactory.kt | 3 +- .../items/action/factory/StatusAllFactory.kt | 5 +--- .../items/action/factory/StatusItemFactory.kt | 3 +- .../corebot/action/factory/ActionFactory.kt | 20 +++++++++++-- .../action/factory/BaseActionFactory.kt | 5 +++- .../action/factory/BaseLockOptionFactory.kt | 3 -- .../action/factory/LockActionFactory.kt | 3 +- .../action/factory/LockOptionFactory.kt | 2 +- .../action/factory/NamedActionFactory.kt | 7 +++-- .../corebot/action/factory/ShowHelpFactory.kt | 4 +-- .../action/factory/StatusActionFactory.kt | 3 +- .../action/factory/SystemActionFactory.kt | 1 - .../action/factory/UnlockActionFactory.kt | 3 +- .../action/factory/UnlockOptionFactory.kt | 2 +- .../corebot/chat/filter/CommandFilter.kt | 3 +- .../corebot/chat/filter/RegexFilter.kt | 9 +++--- .../corebot/chat/filter/StringFilter.kt | 3 +- .../chat/template/TemplateConfigService.kt | 8 ++--- .../corebot/chat/template/TemplateService.kt | 29 ++++++++++++------- 27 files changed, 88 insertions(+), 82 deletions(-) diff --git a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/factory/DisableJobFactory.kt b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/factory/DisableJobFactory.kt index 092b23c0..6fb7feff 100644 --- a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/factory/DisableJobFactory.kt +++ b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/factory/DisableJobFactory.kt @@ -10,8 +10,7 @@ import javax.inject.Inject /** * Disables a job. */ -@Template("disableJob") +@Template("disableJob", builtIn = true, showInUsage = true, actionMessageMode = ActionMessageMode.INDIVIDUAL) class DisableJobFactory @Inject constructor(configService: ConfigService) : NamedActionFactory(configService) { override val actionType: ActionType = JobActionType.DISABLE - override val actionMessageMode = ActionMessageMode.INDIVIDUAL } diff --git a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/factory/EnableJobFactory.kt b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/factory/EnableJobFactory.kt index 691a4f68..09731ba9 100644 --- a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/factory/EnableJobFactory.kt +++ b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/factory/EnableJobFactory.kt @@ -10,8 +10,7 @@ import javax.inject.Inject /** * Enables a job. */ -@Template("enableJob") +@Template("enableJob", builtIn = true, showInUsage = true, actionMessageMode = ActionMessageMode.INDIVIDUAL) class EnableJobFactory @Inject constructor(configService: ConfigService) : NamedActionFactory(configService) { override val actionType: ActionType = JobActionType.ENABLE - override val actionMessageMode = ActionMessageMode.INDIVIDUAL } diff --git a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/factory/TriggerJobFactory.kt b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/factory/TriggerJobFactory.kt index dcbc0d0a..24e758ce 100644 --- a/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/factory/TriggerJobFactory.kt +++ b/backends/deployment/jobs/src/main/kotlin/com/gatehill/corebot/driver/jobs/action/factory/TriggerJobFactory.kt @@ -12,14 +12,11 @@ import com.gatehill.corebot.config.model.ActionConfig /** * Triggers job execution. */ -@Template("triggerJob") +@Template("triggerJob", builtIn = false, showInUsage = true, actionMessageMode = ActionMessageMode.INDIVIDUAL) class TriggerJobFactory(action: ActionConfig, private val chatGenerator: ChatGenerator) : CustomActionFactory() { - override val builtIn = false - override val showInUsage = true override val actionType: ActionType = JobActionType.TRIGGER - override val actionMessageMode = ActionMessageMode.INDIVIDUAL override val actionConfigs: List init { diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/BaseItemFactory.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/BaseItemFactory.kt index 13997251..ca57825e 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/BaseItemFactory.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/BaseItemFactory.kt @@ -1,6 +1,5 @@ package com.gatehill.corebot.driver.items.action.factory -import com.gatehill.corebot.action.factory.ActionMessageMode import com.gatehill.corebot.action.factory.CustomActionFactory import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.model.ActionConfig @@ -10,9 +9,6 @@ import javax.inject.Inject * Common item functionality. */ abstract class BaseItemFactory @Inject constructor(private val configService: ConfigService) : CustomActionFactory() { - override val builtIn = false - override val showInUsage = true - override val actionMessageMode = ActionMessageMode.INDIVIDUAL override val actionConfigs = mutableListOf() protected val itemName: String diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/BorrowItemAsUserFactory.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/BorrowItemAsUserFactory.kt index 62dcc410..d029b0ce 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/BorrowItemAsUserFactory.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/BorrowItemAsUserFactory.kt @@ -1,5 +1,6 @@ package com.gatehill.corebot.driver.items.action.factory +import com.gatehill.corebot.action.factory.ActionMessageMode import com.gatehill.corebot.action.factory.Template import com.gatehill.corebot.action.model.ActionType import com.gatehill.corebot.config.ConfigService @@ -9,12 +10,13 @@ import javax.inject.Inject /** * Borrow an item as another user. */ -@Template("borrowItemAsUser", placeholderKeys = arrayOf( - BorrowItemAsUserFactory.borrowerPlaceholder, - BaseItemFactory.itemPlaceholder, - BorrowItemFactory.subItemPlaceholder, - BorrowItemFactory.reasonPlaceholder -)) +@Template("borrowItemAsUser", builtIn = true, showInUsage = true, actionMessageMode = ActionMessageMode.INDIVIDUAL, + placeholderKeys = arrayOf( + BorrowItemAsUserFactory.borrowerPlaceholder, + BaseItemFactory.itemPlaceholder, + BorrowItemFactory.subItemPlaceholder, + BorrowItemFactory.reasonPlaceholder + )) class BorrowItemAsUserFactory @Inject constructor(configService: ConfigService) : BorrowItemFactory(configService) { override val actionType: ActionType = ItemsActionType.ITEM_BORROW_AS_USER diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/BorrowItemFactory.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/BorrowItemFactory.kt index 8c2d7286..4d6e0196 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/BorrowItemFactory.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/BorrowItemFactory.kt @@ -1,5 +1,6 @@ package com.gatehill.corebot.driver.items.action.factory +import com.gatehill.corebot.action.factory.ActionMessageMode import com.gatehill.corebot.action.factory.Template import com.gatehill.corebot.action.model.ActionType import com.gatehill.corebot.action.model.TriggerContext @@ -11,11 +12,12 @@ import javax.inject.Inject /** * Borrow an item. */ -@Template("borrowItem", placeholderKeys = arrayOf( - BaseItemFactory.itemPlaceholder, - BorrowItemFactory.subItemPlaceholder, - BorrowItemFactory.reasonPlaceholder -)) +@Template("borrowItem", builtIn = true, showInUsage = true, actionMessageMode = ActionMessageMode.INDIVIDUAL, + placeholderKeys = arrayOf( + BaseItemFactory.itemPlaceholder, + BorrowItemFactory.subItemPlaceholder, + BorrowItemFactory.reasonPlaceholder + )) open class BorrowItemFactory @Inject constructor(configService: ConfigService) : BaseItemFactory(configService) { override val actionType: ActionType = ItemsActionType.ITEM_BORROW diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/EvictItemFactory.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/EvictItemFactory.kt index 6494fee0..65332841 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/EvictItemFactory.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/EvictItemFactory.kt @@ -1,5 +1,6 @@ package com.gatehill.corebot.driver.items.action.factory +import com.gatehill.corebot.action.factory.ActionMessageMode import com.gatehill.corebot.action.factory.Template import com.gatehill.corebot.action.model.ActionType import com.gatehill.corebot.action.model.TriggerContext @@ -11,7 +12,7 @@ import javax.inject.Inject /** * Evict all borrowers from an item. */ -@Template("evictItem") +@Template("evictItem", builtIn = true, showInUsage = true, actionMessageMode = ActionMessageMode.INDIVIDUAL) class EvictItemFactory @Inject constructor(configService: ConfigService) : BaseItemFactory(configService) { override val actionType: ActionType = ItemsActionType.ITEM_EVICT override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?) = "" diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/EvictUserFromItemFactory.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/EvictUserFromItemFactory.kt index a742ae1a..d1c84f9a 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/EvictUserFromItemFactory.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/EvictUserFromItemFactory.kt @@ -1,5 +1,6 @@ package com.gatehill.corebot.driver.items.action.factory +import com.gatehill.corebot.action.factory.ActionMessageMode import com.gatehill.corebot.action.factory.Template import com.gatehill.corebot.action.model.ActionType import com.gatehill.corebot.action.model.TriggerContext @@ -11,10 +12,11 @@ import javax.inject.Inject /** * Evict a borrower from an item. */ -@Template("evictUserFromItem", placeholderKeys = arrayOf( - EvictUserFromItemFactory.borrowerPlaceholder, - BaseItemFactory.itemPlaceholder -)) +@Template("evictUserFromItem", builtIn = true, showInUsage = true, actionMessageMode = ActionMessageMode.INDIVIDUAL, + placeholderKeys = arrayOf( + EvictUserFromItemFactory.borrowerPlaceholder, + BaseItemFactory.itemPlaceholder + )) class EvictUserFromItemFactory @Inject constructor(configService: ConfigService) : BaseItemFactory(configService) { override val actionType: ActionType = ItemsActionType.ITEM_EVICT_USER diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/ReturnItemFactory.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/ReturnItemFactory.kt index c2a4a2cd..cba5e444 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/ReturnItemFactory.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/ReturnItemFactory.kt @@ -1,5 +1,6 @@ package com.gatehill.corebot.driver.items.action.factory +import com.gatehill.corebot.action.factory.ActionMessageMode import com.gatehill.corebot.action.factory.Template import com.gatehill.corebot.action.model.ActionType import com.gatehill.corebot.action.model.TriggerContext @@ -11,7 +12,7 @@ import javax.inject.Inject /** * Return a borrowed item. */ -@Template("returnItem") +@Template("returnItem", builtIn = true, showInUsage = true, actionMessageMode = ActionMessageMode.INDIVIDUAL) class ReturnItemFactory @Inject constructor(configService: ConfigService) : BaseItemFactory(configService) { override val actionType: ActionType = ItemsActionType.ITEM_RETURN override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?) = "" diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/StatusAllFactory.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/StatusAllFactory.kt index 4a60c4ff..9f9d4f2a 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/StatusAllFactory.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/StatusAllFactory.kt @@ -13,11 +13,8 @@ import javax.inject.Inject /** * Show status and claims for all items. */ -@Template("statusAllItems") +@Template("statusAllItems", builtIn = true, showInUsage = true, actionMessageMode = ActionMessageMode.INDIVIDUAL) class StatusAllFactory @Inject constructor(val claimService: ClaimService) : SystemActionFactory() { - override val builtIn = false - override val showInUsage = true - override val actionMessageMode = ActionMessageMode.INDIVIDUAL override val actionType: ActionType = ItemsActionType.ALL_STATUS override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?) = diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/StatusItemFactory.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/StatusItemFactory.kt index 67622fad..2c2be966 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/StatusItemFactory.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/StatusItemFactory.kt @@ -1,5 +1,6 @@ package com.gatehill.corebot.driver.items.action.factory +import com.gatehill.corebot.action.factory.ActionMessageMode import com.gatehill.corebot.action.factory.Template import com.gatehill.corebot.action.model.ActionType import com.gatehill.corebot.action.model.TriggerContext @@ -11,7 +12,7 @@ import javax.inject.Inject /** * Show status and claims for an item. */ -@Template("statusItem") +@Template("statusItem", builtIn = true, showInUsage = true, actionMessageMode = ActionMessageMode.INDIVIDUAL) class StatusItemFactory @Inject constructor(configService: ConfigService) : BaseItemFactory(configService) { override val actionType: ActionType = ItemsActionType.ITEM_STATUS override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?) = "" diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/ActionFactory.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/ActionFactory.kt index fb64bcaa..96ce91b1 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/ActionFactory.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/ActionFactory.kt @@ -7,16 +7,20 @@ import com.gatehill.corebot.chat.filter.FilterConfig import com.gatehill.corebot.config.model.ActionConfig /** - * Produces actions of a given type. + * Produces actions of a given type. Must be annotated with `com.gatehill.corebot.action.factory.Template` + * to provide metadata. */ interface ActionFactory { val parsers: MutableList - val builtIn: Boolean - val showInUsage: Boolean val actionType: ActionType val actionMessageMode: ActionMessageMode val placeholderValues: MutableMap + /** + * Reads the template metadata for this factory. + */ + fun readMetadata(): Template = readActionFactoryMetadata(this::class.java) + /** * Hook for subclasses to do things like manipulate placeholders once * the template has been fully satisfied. @@ -41,6 +45,13 @@ interface ActionFactory { fun buildCompleteMessage(): String = "" } +/** + * Reads the template metadata for a factory. + */ +fun readActionFactoryMetadata(factoryClass: Class): Template = + factoryClass.getAnnotationsByType(Template::class.java).firstOrNull() + ?: throw IllegalStateException("Missing @Template annotation for: ${factoryClass.canonicalName}") + /** * Metadata for a template. * Specifying `placeholderKeys` allows regex templates. @@ -48,6 +59,9 @@ interface ActionFactory { @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) annotation class Template(val templateName: String, + val builtIn: Boolean, + val showInUsage: Boolean, + val actionMessageMode: ActionMessageMode, val placeholderKeys: Array = emptyArray()) enum class ActionMessageMode { diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/BaseActionFactory.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/BaseActionFactory.kt index 75454dcc..303d0283 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/BaseActionFactory.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/BaseActionFactory.kt @@ -8,9 +8,12 @@ import com.gatehill.corebot.config.model.ActionConfig * Parses tokens into placeholder values. */ abstract class BaseActionFactory : ActionFactory { + final override val actionMessageMode: ActionMessageMode + get() = readMetadata().actionMessageMode + override val parsers = mutableListOf() - protected abstract val actionConfigs: List override val placeholderValues = mutableMapOf() + protected abstract val actionConfigs: List /** * A short, human readable description. diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/BaseLockOptionFactory.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/BaseLockOptionFactory.kt index c13ce583..e0262f02 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/BaseLockOptionFactory.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/BaseLockOptionFactory.kt @@ -13,9 +13,6 @@ import javax.inject.Inject abstract class BaseLockOptionFactory @Inject constructor(private val configService: ConfigService, private val chatGenerator: ChatGenerator) : CustomActionFactory() { - override val builtIn = true - override val showInUsage = true - override val actionMessageMode = ActionMessageMode.GROUP override val actionConfigs = mutableListOf() private val optionName: String diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/LockActionFactory.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/LockActionFactory.kt index 18844c35..20fd1420 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/LockActionFactory.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/LockActionFactory.kt @@ -8,8 +8,7 @@ import javax.inject.Inject /** * Locks an action. */ -@Template("lockAction") +@Template("lockAction", builtIn = true, showInUsage = true, actionMessageMode = ActionMessageMode.INDIVIDUAL) class LockActionFactory @Inject constructor(configService: ConfigService) : NamedActionFactory(configService) { override val actionType: ActionType = CoreActionType.LOCK_ACTION - override val actionMessageMode = ActionMessageMode.INDIVIDUAL } diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/LockOptionFactory.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/LockOptionFactory.kt index 0ebf75a6..772eb7c0 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/LockOptionFactory.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/LockOptionFactory.kt @@ -9,7 +9,7 @@ import javax.inject.Inject /** * Locks an option value. */ -@Template("lockOption") +@Template("lockOption", builtIn = true, showInUsage = true, actionMessageMode = ActionMessageMode.GROUP) class LockOptionFactory @Inject constructor(configService: ConfigService, chatGenerator: ChatGenerator) : BaseLockOptionFactory(configService, chatGenerator) { diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/NamedActionFactory.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/NamedActionFactory.kt index 190b5a24..8c631e6d 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/NamedActionFactory.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/NamedActionFactory.kt @@ -8,9 +8,6 @@ import javax.inject.Inject * Represents a simple operation for a named action. */ abstract class NamedActionFactory @Inject constructor(private val configService: ConfigService) : CustomActionFactory() { - protected val actionPlaceholder = "action or tag name" - override val builtIn: Boolean = true - override val showInUsage: Boolean = true override val actionConfigs = mutableListOf() override fun onSatisfied(): Boolean { @@ -31,4 +28,8 @@ abstract class NamedActionFactory @Inject constructor(private val configService: return actionConfigs.isNotEmpty() } + + companion object { + protected const val actionPlaceholder = "action or tag name" + } } diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/ShowHelpFactory.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/ShowHelpFactory.kt index af2802a1..a1242a8c 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/ShowHelpFactory.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/ShowHelpFactory.kt @@ -10,13 +10,11 @@ import javax.inject.Inject /** * Shows a help/usage message. */ -@Template("help") +@Template("help", builtIn = true, showInUsage = false, actionMessageMode = ActionMessageMode.INDIVIDUAL) class ShowHelpFactory @Inject constructor(private val templateService: TemplateService, private val chatGenerator: ChatGenerator) : SystemActionFactory() { - override val showInUsage = false override val actionType = CoreActionType.HELP - override val actionMessageMode = ActionMessageMode.INDIVIDUAL override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?): String { return "${chatGenerator.greeting()} :simple_smile: Try one of these:\r\n${templateService.usage()}" diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/StatusActionFactory.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/StatusActionFactory.kt index 39de47e3..93292dd0 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/StatusActionFactory.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/StatusActionFactory.kt @@ -8,8 +8,7 @@ import javax.inject.Inject /** * Prints status information about an action. */ -@Template("statusAction") +@Template("statusAction", builtIn = true, showInUsage = true, actionMessageMode = ActionMessageMode.INDIVIDUAL) class StatusActionFactory @Inject constructor(configService: ConfigService) : NamedActionFactory(configService) { override val actionType: ActionType = CoreActionType.STATUS - override val actionMessageMode = ActionMessageMode.INDIVIDUAL } diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/SystemActionFactory.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/SystemActionFactory.kt index 39c8b1c9..2f128066 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/SystemActionFactory.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/SystemActionFactory.kt @@ -9,7 +9,6 @@ import com.gatehill.corebot.config.model.ActionConfig * Represents a system action. */ abstract class SystemActionFactory : BaseActionFactory() { - override val builtIn = true override val actionConfigs = emptyList() override fun buildActions(trigger: TriggerContext): List { diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/UnlockActionFactory.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/UnlockActionFactory.kt index ebfaf844..fd867b85 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/UnlockActionFactory.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/UnlockActionFactory.kt @@ -8,8 +8,7 @@ import javax.inject.Inject /** * Unlocks an action. */ -@Template("unlockAction") +@Template("unlockAction", builtIn = true, showInUsage = true, actionMessageMode = ActionMessageMode.INDIVIDUAL) class UnlockActionFactory @Inject constructor(configService: ConfigService) : NamedActionFactory(configService) { override val actionType: ActionType = CoreActionType.UNLOCK_ACTION - override val actionMessageMode = ActionMessageMode.INDIVIDUAL } diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/UnlockOptionFactory.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/UnlockOptionFactory.kt index c0fbe1ac..4ca8f6e9 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/UnlockOptionFactory.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/UnlockOptionFactory.kt @@ -9,7 +9,7 @@ import javax.inject.Inject /** * Unlocks an option value. */ -@Template("unlockOption") +@Template("unlockOption", builtIn = true, showInUsage = true, actionMessageMode = ActionMessageMode.GROUP) class UnlockOptionFactory @Inject constructor(configService: ConfigService, chatGenerator: ChatGenerator) : BaseLockOptionFactory(configService, chatGenerator) { diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/CommandFilter.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/CommandFilter.kt index 6d88bb40..95240233 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/CommandFilter.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/CommandFilter.kt @@ -1,8 +1,7 @@ package com.gatehill.corebot.chat.filter import com.gatehill.corebot.action.factory.ActionFactory -import com.gatehill.corebot.action.factory.Template interface CommandFilter { - fun matches(config: FilterConfig, factory: ActionFactory, template: Template, command: String): Boolean + fun matches(config: FilterConfig, factory: ActionFactory, command: String): Boolean } diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/RegexFilter.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/RegexFilter.kt index 2abc679a..aa328907 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/RegexFilter.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/RegexFilter.kt @@ -1,22 +1,21 @@ package com.gatehill.corebot.chat.filter import com.gatehill.corebot.action.factory.ActionFactory -import com.gatehill.corebot.action.factory.Template import java.util.regex.Pattern class RegexFilter : CommandFilter { class RegexFilterConfig(val template: Pattern, usage: String?) : FilterConfig(usage) - override fun matches(config: FilterConfig, factory: ActionFactory, template: Template, command: String): Boolean = - parseCommand(config as RegexFilterConfig, factory, template, command) + override fun matches(config: FilterConfig, factory: ActionFactory, command: String): Boolean = + parseCommand(config as RegexFilterConfig, factory, command) /** * Filter candidates based on their templates. */ - private fun parseCommand(config: RegexFilterConfig, factory: ActionFactory, template: Template, command: String) = + private fun parseCommand(config: RegexFilterConfig, factory: ActionFactory, command: String) = config.template.matcher(command).takeIf { it.matches() }?.let { matcher -> - factory.placeholderValues += template.placeholderKeys.map { it to matcher.group(it) }.toMap() + factory.placeholderValues += factory.readMetadata().placeholderKeys.map { it to matcher.group(it) }.toMap() factory.onSatisfied() } ?: false } diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/StringFilter.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/StringFilter.kt index bc38edfa..594acbb5 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/StringFilter.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/StringFilter.kt @@ -1,7 +1,6 @@ package com.gatehill.corebot.chat.filter import com.gatehill.corebot.action.factory.ActionFactory -import com.gatehill.corebot.action.factory.Template import java.util.LinkedList import java.util.Queue @@ -14,7 +13,7 @@ class StringFilter : CommandFilter { class StringFilterConfig(val template: String, usage: String?) : FilterConfig(usage) - override fun matches(config: FilterConfig, factory: ActionFactory, template: Template, command: String) = + override fun matches(config: FilterConfig, factory: ActionFactory, command: String) = parseCommand(config as StringFilterConfig, factory, command) /** diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/TemplateConfigService.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/TemplateConfigService.kt index 1d3c69e4..a8de3e3d 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/TemplateConfigService.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/TemplateConfigService.kt @@ -2,7 +2,7 @@ package com.gatehill.corebot.chat.template import com.fasterxml.jackson.module.kotlin.jacksonTypeRef import com.gatehill.corebot.action.factory.ActionFactory -import com.gatehill.corebot.action.factory.Template +import com.gatehill.corebot.action.factory.readActionFactoryMetadata import com.gatehill.corebot.chat.filter.FilterConfig import com.gatehill.corebot.chat.filter.RegexFilter import com.gatehill.corebot.chat.filter.StringFilter @@ -76,11 +76,7 @@ class TemplateConfigService { } fun loadFilterConfig(factoryClass: Class) = - loadFilterConfig(readMetadata(factoryClass).templateName) - - fun readMetadata(factoryClass: Class): Template = - factoryClass.getAnnotationsByType(Template::class.java).firstOrNull() - ?: throw IllegalStateException("Missing @Template annotation for: ${factoryClass.canonicalName}") + loadFilterConfig(readActionFactoryMetadata(factoryClass).templateName) fun registerClasspathTemplateFile(classpathFile: String) { templateFiles += "$classpathPrefix$classpathFile" diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/TemplateService.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/TemplateService.kt index ef2b8a0d..bd16b074 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/TemplateService.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/template/TemplateService.kt @@ -2,6 +2,7 @@ package com.gatehill.corebot.chat.template import com.gatehill.corebot.action.ActionFactoryConverter import com.gatehill.corebot.action.factory.ActionFactory +import com.gatehill.corebot.action.factory.Template import com.gatehill.corebot.chat.SessionService import com.gatehill.corebot.chat.filter.CommandFilter import com.gatehill.corebot.chat.filter.FilterConfig @@ -44,7 +45,7 @@ class TemplateService @Inject constructor(private val injector: Injector, * Invoke the filter's match function for the given factory. */ private fun filterMatch(config: FilterConfig, factory: ActionFactory, commandOnly: String) = - loadFilter(config).matches(config, factory, templateConfigService.readMetadata(factory::class.java), commandOnly) + loadFilter(config).matches(config, factory, commandOnly) /** * Load the filter for the given configuration. @@ -77,14 +78,20 @@ class TemplateService @Inject constructor(private val injector: Injector, * Provide a human-readable usage message. */ fun usage() = StringBuilder().apply { - val sortedCandidates = fetchCandidates().toMutableList().apply { - sortBy { candidate -> - candidate.parsers - .filter { it.usage != null } - .map { it.usage } - .joinToString("\n") - } - } + val metadata = fetchCandidates() + .toMutableList() + .apply { + sortBy { candidate -> + candidate.parsers + .filter { it.usage != null } + .map { it.usage } + .joinToString("\n") + } + } + .map { it to it.readMetadata() }.toMap() + + // uses the local metadata map for each lookup + fun ActionFactory.cachedMetadata(): Template = metadata[this]!! val printTemplate: (ActionFactory) -> Unit = { candidate -> appendln() @@ -94,7 +101,7 @@ class TemplateService @Inject constructor(private val injector: Injector, .joinToString("\n")) } - val customActions = sortedCandidates.filter(ActionFactory::showInUsage).filterNot(ActionFactory::builtIn) + val customActions = metadata.keys.filter { it.cachedMetadata().showInUsage }.filterNot { it.cachedMetadata().builtIn } if (customActions.isNotEmpty()) { append("*Custom actions*") customActions.forEach(printTemplate) @@ -102,7 +109,7 @@ class TemplateService @Inject constructor(private val injector: Injector, if (isNotEmpty()) repeat(2) { appendln() } - val builtInActions = sortedCandidates.filter(ActionFactory::showInUsage).filter(ActionFactory::builtIn) + val builtInActions = metadata.keys.filter { it.cachedMetadata().showInUsage }.filter { it.cachedMetadata().builtIn } if (builtInActions.isNotEmpty()) { append("*Built-in actions*") builtInActions.forEach(printTemplate) From 8420e9831e13f7daf6ecb331856d0cb1e14f25ad Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Sun, 2 Jul 2017 17:34:59 +0100 Subject: [PATCH 34/36] Improves specifications. --- backends/deployment/jobs/build.gradle | 21 +++++ .../action/factory/DisableJobFactorySpec.kt | 50 ++++++++++++ .../action/factory/EnableJobFactorySpec.kt | 50 ++++++++++++ .../action/factory/TriggerJobFactorySpec.kt | 50 ++++++++++++ .../com/gatehill/corebot/test/TestMother.kt | 13 ++++ .../com/gatehill/corebot/CommonBotModule.kt | 3 +- build.gradle | 1 + core/engine/build.gradle | 2 + .../action/factory/NamedActionFactory.kt | 2 +- .../gatehill/corebot/chat/ChatGenerator.kt | 42 +++------- .../corebot/chat/ChatGeneratorImpl.kt | 39 ++++++++++ .../corebot/chat/filter/StringFilter.kt | 6 +- .../corebot/chat/ChatGeneratorSpec.kt | 4 +- .../corebot/chat/filter/RegexFilterSpec.kt | 74 ++++++++++++++++++ .../corebot/chat/filter/StringFilterSpec.kt | 78 +++++++++++++++++++ .../com/gatehill/corebot/test/TestMother.kt | 5 ++ 16 files changed, 401 insertions(+), 39 deletions(-) create mode 100644 backends/deployment/jobs/src/test/kotlin/com/gatehill/corebot/action/factory/DisableJobFactorySpec.kt create mode 100644 backends/deployment/jobs/src/test/kotlin/com/gatehill/corebot/action/factory/EnableJobFactorySpec.kt create mode 100644 backends/deployment/jobs/src/test/kotlin/com/gatehill/corebot/action/factory/TriggerJobFactorySpec.kt create mode 100644 backends/deployment/jobs/src/test/kotlin/com/gatehill/corebot/test/TestMother.kt create mode 100644 core/engine/src/main/kotlin/com/gatehill/corebot/chat/ChatGeneratorImpl.kt create mode 100644 core/engine/src/test/kotlin/com/gatehill/corebot/chat/filter/RegexFilterSpec.kt create mode 100644 core/engine/src/test/kotlin/com/gatehill/corebot/chat/filter/StringFilterSpec.kt create mode 100644 core/engine/src/test/kotlin/com/gatehill/corebot/test/TestMother.kt diff --git a/backends/deployment/jobs/build.gradle b/backends/deployment/jobs/build.gradle index 1624a433..336b6209 100644 --- a/backends/deployment/jobs/build.gradle +++ b/backends/deployment/jobs/build.gradle @@ -1,13 +1,34 @@ buildscript { dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$version_kotlin" + classpath "org.junit.platform:junit-platform-gradle-plugin:$version_junit_platform_gradle" } } apply plugin: 'kotlin' +apply plugin: 'org.junit.platform.gradle.plugin' + +junitPlatform { + platformVersion version_junit_platform_gradle + filters { + engines { + include 'spek' + } + } +} + +repositories { + maven { url 'http://dl.bintray.com/jetbrains/spek' } +} dependencies { compile project(':core:core-engine') + + testCompile "org.amshove.kluent:kluent:$version_kluent" + testCompile "org.jetbrains.spek:spek-api:$version_spek" + testRuntime "org.jetbrains.spek:spek-junit-platform-engine:$version_spek" + testRuntime "org.junit.platform:junit-platform-launcher:$version_junit_platform_gradle" + testCompile "com.nhaarman:mockito-kotlin-kt1.1:$version_mockito_kotlin" } publishing { diff --git a/backends/deployment/jobs/src/test/kotlin/com/gatehill/corebot/action/factory/DisableJobFactorySpec.kt b/backends/deployment/jobs/src/test/kotlin/com/gatehill/corebot/action/factory/DisableJobFactorySpec.kt new file mode 100644 index 00000000..7268cfc3 --- /dev/null +++ b/backends/deployment/jobs/src/test/kotlin/com/gatehill/corebot/action/factory/DisableJobFactorySpec.kt @@ -0,0 +1,50 @@ +import com.gatehill.corebot.action.factory.NamedActionFactory +import com.gatehill.corebot.test.TestMother +import com.gatehill.corebot.config.ConfigService +import com.gatehill.corebot.driver.jobs.action.factory.DisableJobFactory +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.verify +import org.amshove.kluent.`should be` +import org.amshove.kluent.`should equal` +import org.jetbrains.spek.api.Spek +import org.jetbrains.spek.api.dsl.given +import org.jetbrains.spek.api.dsl.it +import org.jetbrains.spek.api.dsl.on + +/** + * Specification for `DisableJobFactory`. + */ +object DisableJobFactorySpec : Spek({ + given("a disable job factory") { + val configService = mock { + on { actions() } doReturn TestMother.actions + } + val factory = DisableJobFactory(configService) + + on("providing placeholders") { + factory.placeholderValues += NamedActionFactory.actionPlaceholder to TestMother.actionName + val satisfied = factory.onSatisfied() + + it("should be satisfied") { + satisfied `should be` true + } + } + + on("building actions") { + val actions = factory.buildActions(TestMother.trigger) + + it("should produce actions") { + actions.size `should equal` 1 + } + + it("should produce actions of the correct type") { + actions.first().actionType `should equal` factory.actionType + } + + it("should have loaded actions") { + verify(configService).actions() + } + } + } +}) diff --git a/backends/deployment/jobs/src/test/kotlin/com/gatehill/corebot/action/factory/EnableJobFactorySpec.kt b/backends/deployment/jobs/src/test/kotlin/com/gatehill/corebot/action/factory/EnableJobFactorySpec.kt new file mode 100644 index 00000000..16d8833f --- /dev/null +++ b/backends/deployment/jobs/src/test/kotlin/com/gatehill/corebot/action/factory/EnableJobFactorySpec.kt @@ -0,0 +1,50 @@ +import com.gatehill.corebot.action.factory.NamedActionFactory +import com.gatehill.corebot.test.TestMother +import com.gatehill.corebot.config.ConfigService +import com.gatehill.corebot.driver.jobs.action.factory.EnableJobFactory +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.verify +import org.amshove.kluent.`should be` +import org.amshove.kluent.`should equal` +import org.jetbrains.spek.api.Spek +import org.jetbrains.spek.api.dsl.given +import org.jetbrains.spek.api.dsl.it +import org.jetbrains.spek.api.dsl.on + +/** + * Specification for `EnableJobFactory`. + */ +object EnableJobFactorySpec : Spek({ + given("an enable job factory") { + val configService = mock { + on { actions() } doReturn TestMother.actions + } + val factory = EnableJobFactory(configService) + + on("providing placeholders") { + factory.placeholderValues += NamedActionFactory.actionPlaceholder to TestMother.actionName + val satisfied = factory.onSatisfied() + + it("should be satisfied") { + satisfied `should be` true + } + } + + on("building actions") { + val actions = factory.buildActions(TestMother.trigger) + + it("should produce actions") { + actions.size `should equal` 1 + } + + it("should produce actions of the correct type") { + actions.first().actionType `should equal` factory.actionType + } + + it("should have loaded actions") { + verify(configService).actions() + } + } + } +}) diff --git a/backends/deployment/jobs/src/test/kotlin/com/gatehill/corebot/action/factory/TriggerJobFactorySpec.kt b/backends/deployment/jobs/src/test/kotlin/com/gatehill/corebot/action/factory/TriggerJobFactorySpec.kt new file mode 100644 index 00000000..1a05c0d2 --- /dev/null +++ b/backends/deployment/jobs/src/test/kotlin/com/gatehill/corebot/action/factory/TriggerJobFactorySpec.kt @@ -0,0 +1,50 @@ +import com.gatehill.corebot.action.factory.NamedActionFactory +import com.gatehill.corebot.chat.ChatGenerator +import com.gatehill.corebot.driver.jobs.action.factory.TriggerJobFactory +import com.gatehill.corebot.test.TestMother +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.verify +import org.amshove.kluent.`should be` +import org.amshove.kluent.`should equal` +import org.jetbrains.spek.api.Spek +import org.jetbrains.spek.api.dsl.given +import org.jetbrains.spek.api.dsl.it +import org.jetbrains.spek.api.dsl.on + +/** + * Specification for `TriggerJobFactory`. + */ +object TriggerJobFactorySpec : Spek({ + given("a trigger job factory") { + val chatGenerator = mock { + on { pleaseWait() } doReturn "please wait" + } + val factory = TriggerJobFactory(TestMother.actions.values.first(), chatGenerator) + + on("providing placeholders") { + factory.placeholderValues += NamedActionFactory.actionPlaceholder to TestMother.actionName + val satisfied = factory.onSatisfied() + + it("should be satisfied") { + satisfied `should be` true + } + } + + on("building actions") { + val actions = factory.buildActions(TestMother.trigger) + + it("should produce actions") { + actions.size `should equal` 1 + } + + it("should produce actions of the correct type") { + actions.first().actionType `should equal` factory.actionType + } + + it("should have called the chat generator") { + verify(chatGenerator).pleaseWait() + } + } + } +}) diff --git a/backends/deployment/jobs/src/test/kotlin/com/gatehill/corebot/test/TestMother.kt b/backends/deployment/jobs/src/test/kotlin/com/gatehill/corebot/test/TestMother.kt new file mode 100644 index 00000000..e7b96ada --- /dev/null +++ b/backends/deployment/jobs/src/test/kotlin/com/gatehill/corebot/test/TestMother.kt @@ -0,0 +1,13 @@ +package com.gatehill.corebot.test + +import com.gatehill.corebot.action.model.TriggerContext +import com.gatehill.corebot.config.model.ActionConfig + +/** + * Test data provider. + */ +object TestMother { + const val actionName = "example" + val actions = mapOf(actionName to ActionConfig("template", "jobId", actionName, null, null, null, null, null, null)) + val trigger = TriggerContext("channelId", "userId", "username", "100", null) +} diff --git a/bots/common/src/main/kotlin/com/gatehill/corebot/CommonBotModule.kt b/bots/common/src/main/kotlin/com/gatehill/corebot/CommonBotModule.kt index 3a76513f..a8379121 100644 --- a/bots/common/src/main/kotlin/com/gatehill/corebot/CommonBotModule.kt +++ b/bots/common/src/main/kotlin/com/gatehill/corebot/CommonBotModule.kt @@ -5,6 +5,7 @@ import com.gatehill.corebot.action.ActionOutcomeServiceImpl import com.gatehill.corebot.action.ActionPerformService import com.gatehill.corebot.action.DirectActionPerformServiceImpl import com.gatehill.corebot.chat.ChatGenerator +import com.gatehill.corebot.chat.ChatGeneratorImpl import com.gatehill.corebot.chat.template.TemplateService import com.gatehill.corebot.config.ConfigService import com.gatehill.corebot.config.ConfigServiceImpl @@ -25,7 +26,7 @@ class CommonBotModule() : AbstractModule() { // chat bind(TemplateService::class.java).asSingleton() - bind(ChatGenerator::class.java).asSingleton() + bind(ChatGenerator::class.java).to(ChatGeneratorImpl::class.java).asSingleton() // actions bind(ActionPerformService::class.java).to(DirectActionPerformServiceImpl::class.java) diff --git a/build.gradle b/build.gradle index e82cd1ac..181bc5af 100644 --- a/build.gradle +++ b/build.gradle @@ -18,6 +18,7 @@ allprojects { ext.version_guava = '18.0' ext.version_guice = '4.1.0' ext.version_tyrus = '1.8.3' + ext.version_mockito_kotlin = '1.5.0' repositories { mavenCentral() diff --git a/core/engine/build.gradle b/core/engine/build.gradle index ba533bcf..4e345e73 100644 --- a/core/engine/build.gradle +++ b/core/engine/build.gradle @@ -38,6 +38,8 @@ dependencies { testCompile "org.amshove.kluent:kluent:$version_kluent" testCompile "org.jetbrains.spek:spek-api:$version_spek" testRuntime "org.jetbrains.spek:spek-junit-platform-engine:$version_spek" + testRuntime "org.junit.platform:junit-platform-launcher:$version_junit_platform_gradle" + testCompile "com.nhaarman:mockito-kotlin-kt1.1:$version_mockito_kotlin" } publishing { diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/NamedActionFactory.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/NamedActionFactory.kt index 8c631e6d..96460274 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/NamedActionFactory.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/action/factory/NamedActionFactory.kt @@ -30,6 +30,6 @@ abstract class NamedActionFactory @Inject constructor(private val configService: } companion object { - protected const val actionPlaceholder = "action or tag name" + const val actionPlaceholder = "action or tag name" } } diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/ChatGenerator.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/ChatGenerator.kt index c0864496..532193ac 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/ChatGenerator.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/ChatGenerator.kt @@ -1,39 +1,15 @@ package com.gatehill.corebot.chat -import com.gatehill.corebot.config.Settings -import com.gatehill.corebot.util.yamlMapper -import java.util.Random - /** * @author Pete Cornish {@literal } */ -class ChatGenerator(dictionary: Map>? = null) { - private val dictionary: Map> - private val generator by lazy { Random() } - - init { - @Suppress("UNCHECKED_CAST") - this.dictionary = dictionary ?: - Settings.chat.chatGenerator.use { yamlMapper.readValue(it, Map::class.java) } - as Map> - } - - private fun chooseOne(key: String): String = dictionary[key]?.let { - val entry = it[generator.nextInt(it.size)] - - // e.g. ${goodNewsEmoji} - "\\$\\{([a-zA-Z]+)}".toPattern().matcher(entry) - ?.takeIf { it.find() } - ?.let { it.replaceAll(chooseOne(it.group(1))) } - ?: entry - } ?: "" - - fun pleaseWait() = chooseOne("pleaseWait") - fun goodNews() = chooseOne("goodNews") - fun goodNewsEmoji() = chooseOne("goodNewsEmoji") - fun badNews() = chooseOne("badNews") - fun badNewsEmoji() = chooseOne("badNewsEmoji") - fun greeting() = chooseOne("greeting") - fun ready() = chooseOne("ready") - fun confirmation() = chooseOne("confirmation") +interface ChatGenerator { + fun pleaseWait(): String + fun goodNews(): String + fun goodNewsEmoji(): String + fun badNews(): String + fun badNewsEmoji(): String + fun greeting(): String + fun ready(): String + fun confirmation(): String } diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/ChatGeneratorImpl.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/ChatGeneratorImpl.kt new file mode 100644 index 00000000..64371842 --- /dev/null +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/ChatGeneratorImpl.kt @@ -0,0 +1,39 @@ +package com.gatehill.corebot.chat + +import com.gatehill.corebot.config.Settings +import com.gatehill.corebot.util.yamlMapper +import java.util.Random + +/** + * @author Pete Cornish {@literal } + */ +class ChatGeneratorImpl(dictionary: Map>? = null) : ChatGenerator { + private val dictionary: Map> + private val generator by lazy { Random() } + + init { + @Suppress("UNCHECKED_CAST") + this.dictionary = dictionary ?: + Settings.chat.chatGenerator.use { yamlMapper.readValue(it, Map::class.java) } + as Map> + } + + private fun chooseOne(key: String): String = dictionary[key]?.let { + val entry = it[generator.nextInt(it.size)] + + // e.g. ${goodNewsEmoji} + "\\$\\{([a-zA-Z]+)}".toPattern().matcher(entry) + ?.takeIf { it.find() } + ?.let { it.replaceAll(chooseOne(it.group(1))) } + ?: entry + } ?: "" + + override fun pleaseWait() = chooseOne("pleaseWait") + override fun goodNews() = chooseOne("goodNews") + override fun goodNewsEmoji() = chooseOne("goodNewsEmoji") + override fun badNews() = chooseOne("badNews") + override fun badNewsEmoji() = chooseOne("badNewsEmoji") + override fun greeting() = chooseOne("greeting") + override fun ready() = chooseOne("ready") + override fun confirmation() = chooseOne("confirmation") +} diff --git a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/StringFilter.kt b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/StringFilter.kt index 594acbb5..0d897e02 100644 --- a/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/StringFilter.kt +++ b/core/engine/src/main/kotlin/com/gatehill/corebot/chat/filter/StringFilter.kt @@ -8,6 +8,8 @@ import java.util.Queue * The regular expression to tokenise messages. */ private val messagePartRegex = "\"?( |$)(?=(([^\"]*\"){2})*[^\"]*$)\"?".toRegex() +private val tokenPartRegex = "\\s+(?![^{]*})".toRegex() +private val placeholderRegex = "\\{(.*)}".toRegex() class StringFilter : CommandFilter { class StringFilterConfig(val template: String, @@ -20,7 +22,7 @@ class StringFilter : CommandFilter { * Split the command into elements and return `true` if all were processed successfully. */ private fun parseCommand(config: StringFilterConfig, factory: ActionFactory, command: String): Boolean { - val tokens = LinkedList(config.template.split("\\s".toRegex())) + val tokens = LinkedList(config.template.split(tokenPartRegex)) command.trim().split(messagePartRegex).filterNot(String::isBlank).forEach { element -> // fail as soon as an element is rejected @@ -40,7 +42,7 @@ class StringFilter : CommandFilter { if (tokens.size == 0) return false val token = tokens.poll() - val accepted = "\\{(.*)}".toRegex().matchEntire(token)?.let { match -> + val accepted = placeholderRegex.matchEntire(token)?.let { match -> // option placeholder factory.placeholderValues[match.groupValues[1]] = element true diff --git a/core/engine/src/test/kotlin/com/gatehill/corebot/chat/ChatGeneratorSpec.kt b/core/engine/src/test/kotlin/com/gatehill/corebot/chat/ChatGeneratorSpec.kt index b53476b2..7ac36786 100644 --- a/core/engine/src/test/kotlin/com/gatehill/corebot/chat/ChatGeneratorSpec.kt +++ b/core/engine/src/test/kotlin/com/gatehill/corebot/chat/ChatGeneratorSpec.kt @@ -1,4 +1,4 @@ -import com.gatehill.corebot.chat.ChatGenerator +import com.gatehill.corebot.chat.ChatGeneratorImpl import org.amshove.kluent.`should end with` import org.amshove.kluent.`should equal` import org.amshove.kluent.`should start with` @@ -12,7 +12,7 @@ import org.jetbrains.spek.api.dsl.on */ object ChatGeneratorSpec : Spek({ given("a chat generator") { - val chatGenerator = ChatGenerator(mapOf( + val chatGenerator = ChatGeneratorImpl(mapOf( "goodNewsEmoji" to listOf("emoji"), "goodNews" to listOf("good news \${goodNewsEmoji}") )) diff --git a/core/engine/src/test/kotlin/com/gatehill/corebot/chat/filter/RegexFilterSpec.kt b/core/engine/src/test/kotlin/com/gatehill/corebot/chat/filter/RegexFilterSpec.kt new file mode 100644 index 00000000..208f431b --- /dev/null +++ b/core/engine/src/test/kotlin/com/gatehill/corebot/chat/filter/RegexFilterSpec.kt @@ -0,0 +1,74 @@ +import com.gatehill.corebot.action.factory.ActionFactory +import com.gatehill.corebot.action.factory.ActionMessageMode +import com.gatehill.corebot.action.factory.Template +import com.gatehill.corebot.action.factory.readActionFactoryMetadata +import com.gatehill.corebot.chat.filter.RegexFilter +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.never +import com.nhaarman.mockito_kotlin.verify +import org.amshove.kluent.`should be false` +import org.amshove.kluent.`should be null` +import org.amshove.kluent.`should be true` +import org.amshove.kluent.`should equal` +import org.jetbrains.spek.api.Spek +import org.jetbrains.spek.api.dsl.given +import org.jetbrains.spek.api.dsl.it +import org.jetbrains.spek.api.dsl.on +import java.util.regex.Pattern + +/** + * Specification for `RegexFilter`. + */ +object RegexFilterSpec : Spek({ + given("a regex filter") { + val filter = RegexFilter() + + val mockActionFactory = { values: MutableMap -> + @Template("dummy", + builtIn = true, + showInUsage = true, + actionMessageMode = ActionMessageMode.INDIVIDUAL, + placeholderKeys = arrayOf(placeholderKey)) + abstract class DummyFactory : ActionFactory + + mock { + on { placeholderValues } doReturn values + on { readMetadata() } doReturn readActionFactoryMetadata(DummyFactory::class.java) + on { onSatisfied() } doReturn true + } + } + + on("parsing a valid placeholder") { + val placeholderValues = mutableMapOf() + val factory = mockActionFactory(placeholderValues) + val config = RegexFilter.RegexFilterConfig(Pattern.compile(templateRegex), null) + val match = filter.matches(config, factory, "test $placeholderValue") + + it("should match") { + match.`should be true`() + } + + it("should have set the placeholder value") { + verify(factory).placeholderValues + placeholderValues[placeholderKey] `should equal` placeholderValue + } + } + + on("parsing an invalid placeholder") { + val placeholderValues = mutableMapOf() + val factory = mockActionFactory(placeholderValues) + val config = RegexFilter.RegexFilterConfig(Pattern.compile(templateRegex), null) + val match = filter.matches(config, factory, "non matching") + + it("should not match") { + match.`should be false`() + } + + it("should not have set the placeholder value") { + verify(factory, never()).placeholderValues + placeholderValues[placeholderKey].`should be null`() + } + } + } +}) diff --git a/core/engine/src/test/kotlin/com/gatehill/corebot/chat/filter/StringFilterSpec.kt b/core/engine/src/test/kotlin/com/gatehill/corebot/chat/filter/StringFilterSpec.kt new file mode 100644 index 00000000..096a61b2 --- /dev/null +++ b/core/engine/src/test/kotlin/com/gatehill/corebot/chat/filter/StringFilterSpec.kt @@ -0,0 +1,78 @@ +import com.gatehill.corebot.action.factory.ActionFactory +import com.gatehill.corebot.chat.filter.StringFilter +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.never +import com.nhaarman.mockito_kotlin.verify +import org.amshove.kluent.`should be false` +import org.amshove.kluent.`should be null` +import org.amshove.kluent.`should be true` +import org.amshove.kluent.`should equal` +import org.jetbrains.spek.api.Spek +import org.jetbrains.spek.api.dsl.given +import org.jetbrains.spek.api.dsl.it +import org.jetbrains.spek.api.dsl.on + +/** + * Specification for `StringFilter`. + */ +object StringFilterSpec : Spek({ + given("a string filter") { + val filter = StringFilter() + + val mockActionFactory = { values: MutableMap -> + mock { + on { placeholderValues } doReturn values + on { onSatisfied() } doReturn true + } + } + + on("parsing a valid single word placeholder") { + val placeholderValues = mutableMapOf() + val factory = mockActionFactory(placeholderValues) + val config = StringFilter.StringFilterConfig("test {$placeholderKeySingleWord}", null) + val match = filter.matches(config, factory, "test $placeholderValue") + + it("should match") { + match.`should be true`() + } + + it("should have set the placeholder value") { + verify(factory).placeholderValues + placeholderValues[placeholderKeySingleWord] `should equal` placeholderValue + } + } + + on("parsing a valid multi-word placeholder") { + val placeholderValues = mutableMapOf() + val factory = mockActionFactory(placeholderValues) + val config = StringFilter.StringFilterConfig("test {$placeholderKeyMultiword}", null) + val match = filter.matches(config, factory, "test $placeholderValue") + + it("should match") { + match.`should be true`() + } + + it("should have set the placeholder value") { + verify(factory).placeholderValues + placeholderValues[placeholderKeyMultiword] `should equal` placeholderValue + } + } + + on("parsing an invalid placeholder") { + val placeholderValues = mutableMapOf() + val factory = mockActionFactory(placeholderValues) + val config = StringFilter.StringFilterConfig("test {$placeholderKeySingleWord}", null) + val match = filter.matches(config, factory, "non matching") + + it("should not match") { + match.`should be false`() + } + + it("should not have set the placeholder value") { + verify(factory, never()).placeholderValues + placeholderValues[placeholderKeySingleWord].`should be null`() + } + } + } +}) diff --git a/core/engine/src/test/kotlin/com/gatehill/corebot/test/TestMother.kt b/core/engine/src/test/kotlin/com/gatehill/corebot/test/TestMother.kt new file mode 100644 index 00000000..f293584b --- /dev/null +++ b/core/engine/src/test/kotlin/com/gatehill/corebot/test/TestMother.kt @@ -0,0 +1,5 @@ +const val placeholderKey = "placeholderKey" +const val placeholderKeySingleWord = "placeholderKey" +const val placeholderKeyMultiword = "multiword placeholder key" +const val placeholderValue = "placeholderValue" +const val templateRegex = "test\\s(?<$placeholderKey>[a-zA-Z0-9]+)" From fe10ca223123184667b8d1ee358fd69b01f2ee85 Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Sun, 2 Jul 2017 23:35:03 +0100 Subject: [PATCH 35/36] ktlint fixes. --- .../corebot/driver/items/action/factory/StatusItemFactory.kt | 2 +- .../src/main/kotlin/com/gatehill/corebot/CommonBotModule.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/StatusItemFactory.kt b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/StatusItemFactory.kt index 2c2be966..e0dbb2da 100644 --- a/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/StatusItemFactory.kt +++ b/backends/items/src/main/kotlin/com/gatehill/corebot/driver/items/action/factory/StatusItemFactory.kt @@ -12,7 +12,7 @@ import javax.inject.Inject /** * Show status and claims for an item. */ -@Template("statusItem", builtIn = true, showInUsage = true, actionMessageMode = ActionMessageMode.INDIVIDUAL) +@Template("statusItem", builtIn = true, showInUsage = true, actionMessageMode = ActionMessageMode.INDIVIDUAL) class StatusItemFactory @Inject constructor(configService: ConfigService) : BaseItemFactory(configService) { override val actionType: ActionType = ItemsActionType.ITEM_STATUS override fun buildStartMessage(trigger: TriggerContext, options: Map, actionConfig: ActionConfig?) = "" diff --git a/bots/common/src/main/kotlin/com/gatehill/corebot/CommonBotModule.kt b/bots/common/src/main/kotlin/com/gatehill/corebot/CommonBotModule.kt index a8379121..6d527999 100644 --- a/bots/common/src/main/kotlin/com/gatehill/corebot/CommonBotModule.kt +++ b/bots/common/src/main/kotlin/com/gatehill/corebot/CommonBotModule.kt @@ -18,7 +18,7 @@ import com.google.inject.AbstractModule * * @author Pete Cornish {@literal } */ -class CommonBotModule() : AbstractModule() { +class CommonBotModule : AbstractModule() { override fun configure() { // utility bind(ConfigService::class.java).to(ConfigServiceImpl::class.java).asSingleton() From 0ff5196fbfd746eb0ef83e8c81a0af20d0d142e3 Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Sun, 2 Jul 2017 23:35:03 +0100 Subject: [PATCH 36/36] Release 0.9.0. --- CHANGELOG.md | 10 +++++++++- build.gradle | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 376e0d76..4b6f550c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,18 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). -## Unreleased +## [0.9.0] ### Added - Adds a bot to allow simple borrow/return of items. - Adds a new 'items' backend. +- Makes join message conditional. +- Adds MySQL store. +- Now supports multiple templates. + +### Changed +- Internal structural improvements to support multiple back-end or front-end implementations. +- Improved specification coverage. +- Externalised templates to allow messages to be changed. ## [0.8.1] - 2017-06-11 ### Added diff --git a/build.gradle b/build.gradle index 181bc5af..371a8f2e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ allprojects { - version = '0.9.0-SNAPSHOT' + version = '0.9.0' group = 'com.gatehill.corebot' ext.mavenSnapshotRepository = 's3://gatehillsoftware-maven/snapshots'