├── .gitignore
├── doc
└── magebot_configuration.png
├── registration.php
├── src
├── etc
│ ├── module.xml
│ ├── frontend
│ │ └── routes.xml
│ ├── di.xml
│ ├── acl.xml
│ ├── config.xml
│ └── adminhtml
│ │ └── system.xml
├── StateMachine
│ ├── Actions.php
│ ├── Triggers.php
│ ├── States.php
│ ├── State.php
│ ├── Transition.php
│ ├── Transitions.php
│ ├── Serialization
│ │ ├── ActionFactory.php
│ │ ├── TriggerFactory.php
│ │ ├── FakeActionFactory.php
│ │ ├── FakeTriggerFactory.php
│ │ ├── NewActionFactory.php
│ │ ├── NewTriggerFactory.php
│ │ ├── LazyLoadingAction.php
│ │ ├── LazyLoadingTrigger.php
│ │ ├── SerializableAction.php
│ │ └── SerializableTrigger.php
│ ├── StateMachine.php
│ ├── UnstartedState.php
│ ├── Action.php
│ ├── FixedTrigger.php
│ ├── Trigger.php
│ ├── InitialTransition.php
│ ├── StateSet.php
│ ├── FakeTrigger.php
│ ├── CallbackAction.php
│ ├── TransitionList.php
│ ├── FakeAction.php
│ ├── ConversationContext.php
│ ├── ActionList.php
│ ├── TriggerList.php
│ ├── ConversationTransition.php
│ ├── ConversationState.php
│ └── Conversation.php
├── Botman
│ ├── BotmanConfig.php
│ ├── BotmanAction.php
│ ├── ConversationDefinitions.php
│ ├── ConversationDefinition.php
│ ├── ConversationDefinitionList.php
│ ├── BotmanActionFactory.php
│ ├── MessageAction.php
│ ├── AnswerTrigger.php
│ ├── BotmanConversationContext.php
│ ├── CacheBridge.php
│ ├── BotmanConversation.php
│ ├── FakeDriver.php
│ ├── ProxyDriver.php
│ ├── BotmanWebDriver.php
│ └── QuestionAction.php
├── Controller
│ └── Index
│ │ └── Index.php
├── MageBot.php
├── Conversations
│ └── CustomerGroupConversation.php
└── Helper
│ └── Config.php
├── tests
├── MageBotTest.php
├── unit
│ ├── StateMachine
│ │ ├── CallbackActionTest.php
│ │ ├── ActionListTest.php
│ │ ├── TransitionListTest.php
│ │ ├── StateSetTest.php
│ │ ├── LazyLoadingActionTest.php
│ │ ├── LazyLoadingTriggerTest.php
│ │ ├── SerializableTriggerTest.php
│ │ ├── SerializableActionTest.php
│ │ ├── ConversationTransitionTest.php
│ │ ├── ConversationStateTest.php
│ │ └── ConversationTest.php
│ └── Botman
│ │ ├── MessageActionTest.php
│ │ ├── BotmanConversationContextTest.php
│ │ ├── AnswerTriggerTest.php
│ │ ├── ConversationDefinitionListTest.php
│ │ ├── QuestionActionTest.php
│ │ └── BotmanConversationTest.php
└── integration
│ └── Botman
│ └── CacheTest.php
├── phpunit.xml.dist
├── README.md
└── composer.json
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | ._.DS_Store
3 | /composer.lock
4 | /vendor
5 |
--------------------------------------------------------------------------------
/doc/magebot_configuration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/magento-hackathon/magebot/HEAD/doc/magebot_configuration.png
--------------------------------------------------------------------------------
/registration.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/StateMachine/Actions.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(MageBot::class, $mageBot);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
5 |
6 | tests/unit
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/StateMachine/State.php:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/Botman/BotmanAction.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/StateMachine/Serialization/ActionFactory.php:
--------------------------------------------------------------------------------
1 | getMockBuilder(\stdClass::class)
13 | ->setMethods(['__invoke'])
14 | ->getMock();
15 | $callbackMock->expects(static::once())->method('__invoke');
16 | $action = new CallbackAction($callbackMock);
17 | $action->execute($this->createMock(ConversationContext::class));
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/StateMachine/UnstartedState.php:
--------------------------------------------------------------------------------
1 | Configuration->Services->MageBot"**
13 | 3. Define your Bot conversation (see [Define your Bot conversation](Define your Bot conversation))
14 |
15 | 
16 |
17 | ## Define your Bot conversation
18 | //ToDo
19 |
--------------------------------------------------------------------------------
/src/etc/acl.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/StateMachine/Action.php:
--------------------------------------------------------------------------------
1 | matches = $matches;
18 | }
19 |
20 | public function activated(ConversationContext $context) : bool
21 | {
22 | return $this->matches;
23 | }
24 |
25 | public function type() : string
26 | {
27 | return __CLASS__;
28 | }
29 |
30 | public function parameters() : array
31 | {
32 | return [self::PARAM_MATCHES => $this->matches];
33 | }
34 |
35 |
36 | }
--------------------------------------------------------------------------------
/src/StateMachine/Trigger.php:
--------------------------------------------------------------------------------
1 | initialState = $initialState;
17 | }
18 |
19 | public function name() : string
20 | {
21 | return '';
22 | }
23 |
24 | public function target() : State
25 | {
26 | return $this->initialState;
27 | }
28 |
29 | public function triggeredAt(State $currentState, ConversationContext $context) : bool
30 | {
31 | return $currentState instanceof UnstartedState;
32 | }
33 |
34 | public function toArray() : array
35 | {
36 | return [];
37 | }
38 |
39 | }
--------------------------------------------------------------------------------
/src/StateMachine/StateSet.php:
--------------------------------------------------------------------------------
1 | getArrayCopy(), true);
25 | }
26 |
27 | public function with(State $state) : States
28 | {
29 | return new static($state, ...$this->getArrayCopy());
30 | }
31 |
32 | }
--------------------------------------------------------------------------------
/tests/unit/StateMachine/ActionListTest.php:
--------------------------------------------------------------------------------
1 | actionExpectedToBeCalled(), $this->actionExpectedToBeCalled());
13 | $actionList->executeAll($this->createMock(ConversationContext::class));
14 | }
15 |
16 | /**
17 | * @return CallbackAction
18 | */
19 | private function actionExpectedToBeCalled():CallbackAction
20 | {
21 | $callbackMock = $this->getMockBuilder(\stdClass::class)
22 | ->setMethods(['__invoke'])
23 | ->getMock();
24 | $callbackMock->expects(static::once())->method('__invoke');
25 | return new CallbackAction($callbackMock);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/StateMachine/FakeTrigger.php:
--------------------------------------------------------------------------------
1 | parameters = $parameters;
19 | }
20 |
21 | public function type() : string
22 | {
23 | return __CLASS__;
24 | }
25 |
26 | public function parameters() : array
27 | {
28 | return $this->parameters;
29 | }
30 |
31 | public function activated(ConversationContext $context) : bool
32 | {
33 | return !empty($this->parameters['activated']);
34 | }
35 |
36 |
37 | }
--------------------------------------------------------------------------------
/src/StateMachine/CallbackAction.php:
--------------------------------------------------------------------------------
1 | callback = $callback;
18 | }
19 |
20 | public function type() : string
21 | {
22 | return __CLASS__;
23 | }
24 |
25 | public function parameters() : array
26 | {
27 | throw new \RuntimeException("CallbackAction type is not serializable");
28 | }
29 |
30 |
31 | public function execute(ConversationContext $context)
32 | {
33 | ($this->callback)();
34 | }
35 |
36 | }
--------------------------------------------------------------------------------
/src/Botman/ConversationDefinitionList.php:
--------------------------------------------------------------------------------
1 | hears($conversationDefinition->patternToStart(), function(BotMan $bot) use ($conversationDefinition) {
24 | $bot->startConversation(new BotmanConversation($bot, $conversationDefinition->create()));
25 | });
26 | }
27 | }
28 |
29 | }
--------------------------------------------------------------------------------
/src/StateMachine/TransitionList.php:
--------------------------------------------------------------------------------
1 | triggeredAt($currentState, $context)) {
22 |
23 | return $transition->target();
24 | }
25 | }
26 |
27 | return $currentState;
28 | }
29 |
30 | public function prepend(Transition $transition) : Transitions
31 | {
32 | return new static($transition, ...$this->getArrayCopy());
33 | }
34 |
35 | }
--------------------------------------------------------------------------------
/src/Botman/BotmanActionFactory.php:
--------------------------------------------------------------------------------
1 | botman = $botman;
23 | }
24 |
25 | public function create(string $type, array $parameters) : Action
26 | {
27 | if (is_a($type, BotmanAction::class, true)) {
28 | return new $type($this->botman, ...array_values($parameters));
29 | }
30 | return new $type(...array_values($parameters));
31 | }
32 | }
--------------------------------------------------------------------------------
/src/StateMachine/FakeAction.php:
--------------------------------------------------------------------------------
1 | parameters = $parameters;
20 | }
21 |
22 | public function type() : string
23 | {
24 | return __CLASS__;
25 | }
26 |
27 | public function parameters() : array
28 | {
29 | return $this->parameters;
30 | }
31 |
32 | public function execute(ConversationContext $context)
33 | {
34 | ++$this->timesExecuted;
35 | }
36 |
37 | /**
38 | * @return int
39 | */
40 | public function timesExecuted(): int
41 | {
42 | return $this->timesExecuted;
43 | }
44 |
45 | }
--------------------------------------------------------------------------------
/tests/unit/Botman/MessageActionTest.php:
--------------------------------------------------------------------------------
1 | createMock(BotMan::class), 'Hello, world!');
15 | static::assertEquals(MessageAction::class, $action->type());
16 | static::assertEquals(['message' => 'Hello, world!'], $action->parameters());
17 | }
18 | public function testSendMessage()
19 | {
20 | $botMock = $this->createMock(BotMan::class);
21 | $botMock->expects(static::once())
22 | ->method('reply')
23 | ->with('Hello, world!');
24 | $action = new MessageAction($botMock, 'Hello, world!');
25 | $action->execute($this->createMock(ConversationContext::class));
26 | }
27 | }
--------------------------------------------------------------------------------
/src/StateMachine/ConversationContext.php:
--------------------------------------------------------------------------------
1 | mageBot = $mageBot;
18 | }
19 |
20 |
21 | public function execute()
22 | {
23 | $this->mageBot->start();
24 | return $this->emptyResult();
25 | }
26 |
27 | /**
28 | * @return \Magento\Framework\Controller\Result\Raw
29 | */
30 | private function emptyResult()
31 | {
32 | /** @var \Magento\Framework\Controller\Result\Raw $resultRaw */
33 | $resultRaw = $this->resultFactory->create(ResultFactory::TYPE_RAW);
34 | return $resultRaw;
35 | }
36 |
37 | }
--------------------------------------------------------------------------------
/tests/unit/StateMachine/TransitionListTest.php:
--------------------------------------------------------------------------------
1 | prepend($transitionBtoA))
21 | );
22 | static::assertEquals(
23 | [$transitionAtoB],
24 | $transitions->getArrayCopy()
25 | );
26 |
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Botman/MessageAction.php:
--------------------------------------------------------------------------------
1 | botman = $botman;
25 | $this->message = $message;
26 | }
27 |
28 | public function type() : string
29 | {
30 | return __CLASS__;
31 | }
32 |
33 | public function parameters() : array
34 | {
35 | return [
36 | self::PARAM_MESSAGE => $this->message,
37 | ];
38 | }
39 |
40 | public function execute(ConversationContext $context)
41 | {
42 | $this->botman->reply($this->message);
43 | }
44 |
45 | }
--------------------------------------------------------------------------------
/tests/unit/Botman/BotmanConversationContextTest.php:
--------------------------------------------------------------------------------
1 | setPersistentVar('Persist me', 'I am a variable!');
14 | $serializedContext = serialize($context);
15 | /** @var BotmanConversationContext $unserializedContext */
16 | $unserializedContext = unserialize($serializedContext, ['allowed_classes' => [BotmanConversationContext::class]]);
17 | static::assertTrue($unserializedContext->hasPersistentVar('Persist me'));
18 | static::assertEquals('I am a variable!', $unserializedContext->getPersistentVar('Persist me'));
19 | }
20 |
21 | public function testUnsetVariables()
22 | {
23 | $context = new BotmanConversationContext();
24 | static::expectException(\RuntimeException::class);
25 | $context->getPersistentVar('I do not exist');
26 | }
27 | }
--------------------------------------------------------------------------------
/tests/unit/Botman/AnswerTriggerTest.php:
--------------------------------------------------------------------------------
1 | type());
16 | static::assertEquals(['value' => 'foo'], $trigger->parameters());
17 | }
18 |
19 | public function testActivatedByBotmanMessage()
20 | {
21 | $context = new BotmanConversationContext();
22 | $context->setAnswer(Answer::create('foo'));
23 | $trigger = new AnswerTrigger('foo');
24 | static::assertTrue($trigger->activated($context));
25 | }
26 |
27 | public function testNotActivatedByNotMatchingBotmanMessage()
28 | {
29 | $context = new BotmanConversationContext();
30 | $context->setAnswer(Answer::create('fooman'));
31 | $trigger = new AnswerTrigger('foo');
32 | static::assertFalse($trigger->activated($context));
33 | }
34 | }
--------------------------------------------------------------------------------
/src/Botman/AnswerTrigger.php:
--------------------------------------------------------------------------------
1 | value = $value;
24 | }
25 |
26 | public function activated(ConversationContext $context) : bool
27 | {
28 | //TODO find out if and in which cases Answer::getValue() should be used instead
29 | return $context instanceof BotmanConversationContext && $context->getAnswer()->__toString() === $this->value;
30 | }
31 |
32 | public function type() : string
33 | {
34 | return __CLASS__;
35 | }
36 |
37 | public function parameters() : array
38 | {
39 | return [
40 | self::PARAM_VALUE => $this->value
41 | ];
42 | }
43 |
44 | }
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "magento-hackathon/magebot",
3 | "description": "A Magento 2 integration of the botman chatbot framework",
4 | "type": "magento2-module",
5 | "require": {
6 | "magento/framework": "^100.1",
7 | "mpociot/botman": "^1.4"
8 | },
9 | "require-dev": {
10 | "phpunit/phpunit": "^6.0"
11 | },
12 | "autoload": {
13 | "files": [
14 | "registration.php"
15 | ],
16 | "psr-4": {
17 | "FireGento\\MageBot\\": "src"
18 | }
19 | },
20 | "autoload-dev": {
21 | "psr-4": {
22 | "FireGento\\MageBot\\": "tests"
23 | }
24 | },
25 | "repositories": [
26 | {
27 | "type": "composer",
28 | "url": "https://repo.magento.com/"
29 | }
30 | ],
31 | "license": "OSL 3.0",
32 | "authors": [
33 | {
34 | "name": "Fabian Schmengler",
35 | "email": "fs@integer-net.de"
36 | },
37 | {
38 | "name": "Harald Deiser",
39 | "email": "h.deiser@techdivision.com"
40 | }
41 | ],
42 | "minimum-stability": "stable"
43 | }
44 |
--------------------------------------------------------------------------------
/src/StateMachine/ActionList.php:
--------------------------------------------------------------------------------
1 | execute($context);
24 | }
25 | }
26 |
27 | public function serialize()
28 | {
29 | $this->makeActionsSerializable();
30 | return parent::serialize();
31 | }
32 |
33 | public function jsonSerialize()
34 | {
35 | $this->makeActionsSerializable();
36 | return $this->getArrayCopy();
37 | }
38 |
39 | private function makeActionsSerializable()
40 | {
41 | foreach ($this as $key => $action) {
42 | $this[$key] = new SerializableAction($action);
43 | }
44 | }
45 |
46 |
47 | }
--------------------------------------------------------------------------------
/tests/unit/StateMachine/StateSetTest.php:
--------------------------------------------------------------------------------
1 | getArrayCopy(),
18 | '', 0.0, 10, true
19 | );
20 | }
21 | public function testStateSetCanBeExtended()
22 | {
23 | $stateA = ConversationState::createWithoutActions('A');
24 | $stateB = ConversationState::createWithoutActions('B');
25 | $stateSet = new StateSet($stateA);
26 | static::assertEquals(
27 | [$stateA, $stateB],
28 | \iterator_to_array($stateSet->with($stateB)),
29 | '', 0.0, 10, true
30 | );
31 | static::assertEquals(
32 | [$stateA],
33 | $stateSet->getArrayCopy()
34 | );
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/StateMachine/TriggerList.php:
--------------------------------------------------------------------------------
1 | activated($context)) {
24 | return true;
25 | }
26 | }
27 | return false;
28 | }
29 |
30 | public function serialize()
31 | {
32 | $this->makeTriggersSerializable();
33 | return parent::serialize();
34 | }
35 |
36 | public function jsonSerialize()
37 | {
38 | $this->makeTriggersSerializable();
39 | return $this->getArrayCopy();
40 | }
41 |
42 | private function makeTriggersSerializable()
43 | {
44 | foreach ($this as $key => $trigger) {
45 | $this[$key] = new SerializableTrigger($trigger);
46 | }
47 | }
48 |
49 |
50 | }
--------------------------------------------------------------------------------
/src/etc/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/tests/unit/Botman/ConversationDefinitionListTest.php:
--------------------------------------------------------------------------------
1 | createMock(BotMan::class);
14 | $botman->expects(static::exactly(2))
15 | ->method('hears')
16 | ->withConsecutive(
17 | ['pattern1', static::isInstanceOf(\Closure::class)],
18 | ['pattern2', static::isInstanceOf(\Closure::class)]
19 | )->willReturnCallback(
20 | function($pattern, $callback) use ($botman) {
21 | $callback($botman);
22 | }
23 | );
24 | $botman->expects(static::exactly(2))
25 | ->method('startConversation')
26 | ->with(static::isInstanceOf(BotmanConversation::class));
27 | $conversationDefinitionList = new ConversationDefinitionList(
28 | $this->conversationDefinitionStub('pattern1'), $this->conversationDefinitionStub('pattern2')
29 | );
30 | $conversationDefinitionList->register($botman);
31 | }
32 |
33 | private function conversationDefinitionStub(string $pattern1) : ConversationDefinition
34 | {
35 | $stub = $this->createMock(ConversationDefinition::class);
36 | $stub->method('patternToStart')->willReturn($pattern1);
37 | return $stub;
38 | }
39 | }
--------------------------------------------------------------------------------
/src/Botman/BotmanConversationContext.php:
--------------------------------------------------------------------------------
1 | answer;
22 | }
23 | public function setAnswer(Answer $answer)
24 | {
25 | $this->answer = $answer;
26 | }
27 |
28 | public function setPersistentVar(string $key, $value)
29 | {
30 | $this->persistentVars[$key] = $value;
31 | }
32 |
33 | public function hasPersistentVar(string $key) : bool
34 | {
35 | return array_key_exists($key, $this->persistentVars);
36 | }
37 |
38 | public function getPersistentVar(string $key)
39 | {
40 | if (! $this->hasPersistentVar($key)) {
41 | throw new \RuntimeException("Persistent variable {$key} is not set.");
42 | }
43 | return $this->persistentVars[$key];
44 | }
45 |
46 | public function serialize()
47 | {
48 | return json_encode($this->persistentVars);
49 | }
50 |
51 | public function unserialize($serialized)
52 | {
53 | $this->persistentVars = json_decode($serialized, true);
54 | }
55 |
56 | }
--------------------------------------------------------------------------------
/src/StateMachine/Serialization/LazyLoadingAction.php:
--------------------------------------------------------------------------------
1 | actionFactory = $actionFactory;
34 | $this->type = $type;
35 | $this->parameters = $parameters;
36 | }
37 |
38 | public function type() : string
39 | {
40 | return $this->type;
41 | }
42 |
43 | public function parameters() : array
44 | {
45 | return $this->parameters;
46 | }
47 |
48 | public function execute(ConversationContext $context)
49 | {
50 | $this->loadedAction()->execute($context);
51 | }
52 |
53 | /**
54 | * @return Action
55 | */
56 | private function loadedAction() : Action
57 | {
58 | if ($this->action === null) {
59 | $this->action = $this->actionFactory->create($this->type, $this->parameters);
60 | }
61 | return $this->action;
62 | }
63 |
64 |
65 | }
--------------------------------------------------------------------------------
/src/StateMachine/Serialization/LazyLoadingTrigger.php:
--------------------------------------------------------------------------------
1 | triggerFactory = $triggerFactory;
31 | $this->type = $type;
32 | $this->parameters = $parameters;
33 | }
34 |
35 | public function activated(ConversationContext $context) : bool
36 | {
37 | return $this->loadedTrigger()->activated($context);
38 | }
39 |
40 | public function type() : string
41 | {
42 | return $this->type;
43 | }
44 |
45 | public function parameters() : array
46 | {
47 | return $this->parameters;
48 | }
49 |
50 | /**
51 | * @return Trigger
52 | */
53 | private function loadedTrigger():Trigger
54 | {
55 | if ($this->trigger === null) {
56 | $this->trigger = $this->triggerFactory->create($this->type, $this->parameters);
57 | }
58 | return $this->trigger;
59 | }
60 | }
--------------------------------------------------------------------------------
/tests/unit/StateMachine/LazyLoadingActionTest.php:
--------------------------------------------------------------------------------
1 | actionFactoryMock = $this->getMockBuilder(ActionFactory::class)
20 | ->getMockForAbstractClass();
21 | }
22 |
23 | public function testLazyLoad()
24 | {
25 | $parameters = ['foo' => 'bar'];
26 |
27 | $lazyLoadingAction = new LazyLoadingAction($this->actionFactoryMock, FakeAction::class, $parameters);
28 |
29 | $loadedAction = new FakeAction($parameters);
30 | $this->actionFactoryMock->expects(static::once())
31 | ->method('create')
32 | ->with(FakeAction::class, $parameters)
33 | ->willReturn($loadedAction);
34 |
35 | static::assertEquals(FakeAction::class, $lazyLoadingAction->type());
36 | static::assertEquals($parameters, $lazyLoadingAction->parameters());
37 | $lazyLoadingAction->execute($this->createMock(ConversationContext::class));
38 | $lazyLoadingAction->execute($this->createMock(ConversationContext::class));
39 |
40 | static::assertEquals(2, $loadedAction->timesExecuted());
41 |
42 | }
43 | }
--------------------------------------------------------------------------------
/tests/integration/Botman/CacheTest.php:
--------------------------------------------------------------------------------
1 | objectManager = ObjectManager::getInstance();
18 | $this->cache = $this->objectManager->get(CacheInterface::class);
19 | }
20 | public function testInstantiation()
21 | {
22 | $this->assertInstanceOf(CacheBridge::class, $this->cache);
23 | }
24 | /**
25 | * @dataProvider dataSaveCache
26 | */
27 | public function testSaveCache($key, $value)
28 | {
29 | $this->cache->put($key, $value, 1);
30 | $this->assertEquals($value, $this->cache->get($key));
31 | }
32 | public static function dataSaveCache()
33 | {
34 | return [
35 | 'string' => ['foo', 'bar'],
36 | 'array' => ['foo', ['bar' => 'bar']],
37 | 'array_with_object' => [
38 | 'foo',
39 | [
40 | 'conversation' => new ConversationStub,
41 | ]
42 | ],
43 | ];
44 | }
45 | }
46 |
47 | /**
48 | * Anonymous classes cannot be serialized by design, so we need a real stub
49 | */
50 | class ConversationStub extends Conversation
51 | {
52 | public function run()
53 | {
54 | }
55 | }
--------------------------------------------------------------------------------
/src/Botman/CacheBridge.php:
--------------------------------------------------------------------------------
1 | magentoCache = $magentoCache;
23 | }
24 |
25 | public function has($key)
26 | {
27 | return $this->magentoCache->load($key) !== false;
28 | }
29 |
30 | public function get($key, $default = null)
31 | {
32 | if (! $this->has($key)) {
33 | return $default;
34 | }
35 | return $this->deserializeValue($this->magentoCache->load($key));
36 | }
37 |
38 | public function pull($key, $default = null)
39 | {
40 | $value = $this->get($key, $default);
41 | $this->magentoCache->remove($key);
42 | return $value;
43 | }
44 |
45 | public function put($key, $value, $minutes)
46 | {
47 | $this->magentoCache->save($this->serializeValue($value), $key, [self::CACHE_TAG_BOTMAN], $minutes * 60);
48 | }
49 |
50 | private function serializeValue($value) : string
51 | {
52 | return \serialize($value);
53 | }
54 |
55 | private function deserializeValue(string $value)
56 | {
57 | return \unserialize($value, ['allowed_classes' => true]);
58 | }
59 |
60 | }
--------------------------------------------------------------------------------
/src/Botman/BotmanConversation.php:
--------------------------------------------------------------------------------
1 | bot = $bot;
19 | $this->stateMachine = $stateMachine;
20 | }
21 |
22 | public function run()
23 | {
24 | $this->continue(BotMan\Answer::create());
25 | }
26 |
27 | /**
28 | * continue() is used as central "next" callback for Botman. The first parameter is always an Answer object,
29 | * the last one is the conversation itself (not needed here)
30 | *
31 | * Additional parameters are added for conditional callbacks with pattern matching, but we do not use these,
32 | * everything is routed through continue() and handled by the state machine.
33 | *
34 | * @see \Mpociot\BotMan\BotMan::loadActiveConversation()
35 | *
36 | * @param BotMan\Answer $answer
37 | */
38 | public function continue(BotMan\Answer $answer)
39 | {
40 | /** @var BotmanConversationContext $context */
41 | $context = $this->stateMachine->context();
42 | $context->setAnswer($answer);
43 | $this->stateMachine = $this->stateMachine->continue();
44 | //in PHP 7.1: Closure::fromCallable()
45 | $next = function(BotMan\Answer $answer) {
46 | $this->continue($answer);
47 | };
48 | $this->bot->storeConversation($this, $next);
49 | }
50 |
51 | }
--------------------------------------------------------------------------------
/tests/unit/StateMachine/LazyLoadingTriggerTest.php:
--------------------------------------------------------------------------------
1 | triggerFactoryMock = $this->getMockBuilder(TriggerFactory::class)
20 | ->getMockForAbstractClass();
21 | }
22 |
23 | public function testLazyLoad()
24 | {
25 | $type = 'MockTrigger';
26 | $parameters = ['foo' => 'bar'];
27 |
28 | $lazyLoadingTrigger = new LazyLoadingTrigger($this->triggerFactoryMock, $type, $parameters);
29 |
30 | $loadedTrigger = $this->getMockBuilder(Trigger::class)
31 | ->getMock();
32 | $loadedTrigger->method('type')->willReturn($type);
33 | $loadedTrigger->method('parameters')->willReturn($parameters);
34 | $loadedTrigger->expects(static::exactly(2))
35 | ->method('activated')
36 | ->willReturn(false);
37 | $this->triggerFactoryMock->expects(static::once())
38 | ->method('create')
39 | ->with($type, $parameters)
40 | ->willReturn($loadedTrigger);
41 |
42 | static::assertEquals($type, $lazyLoadingTrigger->type());
43 | static::assertEquals($parameters, $lazyLoadingTrigger->parameters());
44 | static::assertFalse($lazyLoadingTrigger->activated($this->createMock(ConversationContext::class)));
45 | static::assertFalse($lazyLoadingTrigger->activated($this->createMock(ConversationContext::class)));
46 |
47 | }
48 | }
--------------------------------------------------------------------------------
/src/StateMachine/ConversationTransition.php:
--------------------------------------------------------------------------------
1 | name = $name;
28 | $this->source = $source;
29 | $this->target = $target;
30 | $this->triggers = $triggers;
31 | }
32 | public function name() : string
33 | {
34 | return $this->name;
35 | }
36 |
37 | public function target() : State
38 | {
39 | return $this->target;
40 | }
41 |
42 | public function triggeredAt(State $currentState, ConversationContext $context) : bool
43 | {
44 | return $currentState == $this->source && $this->triggers->anyActivated($context);
45 | }
46 |
47 | /**
48 | * Array representation that can be used to store the transition definition in a database. Source and target states
49 | * still must be mapped to an ID by the repository
50 | *
51 | * @return array
52 | */
53 | public function toArray() : array
54 | {
55 | return [
56 | 'name' => $this->name,
57 | 'source' => $this->source,
58 | 'target' => $this->target,
59 | 'triggers' => json_encode($this->triggers)
60 | ];
61 | }
62 | }
--------------------------------------------------------------------------------
/src/StateMachine/ConversationState.php:
--------------------------------------------------------------------------------
1 | name = $name;
34 | $this->entryActions = $entryActions;
35 | $this->exitActions = $exitActions;
36 | }
37 | public function name() : string
38 | {
39 | return $this->name;
40 | }
41 |
42 | public function entryActions() : Actions
43 | {
44 | return $this->entryActions;
45 | }
46 |
47 | public function exitActions() : Actions
48 | {
49 | return $this->exitActions;
50 | }
51 |
52 | /**
53 | * Array representation that can be used to store the state definition in a database.
54 | *
55 | * @return string[]
56 | */
57 | public function toArray() : array
58 | {
59 | return [
60 | 'name' => $this->name,
61 | 'entry_actions' => json_encode($this->entryActions()),
62 | 'exit_actions' => json_encode($this->exitActions()),
63 | ];
64 | }
65 | }
--------------------------------------------------------------------------------
/src/Botman/FakeDriver.php:
--------------------------------------------------------------------------------
1 | matchesRequest;
27 | }
28 |
29 | public function getMessages()
30 | {
31 | return $this->messages;
32 | }
33 |
34 | public function isBot()
35 | {
36 | return $this->isBot;
37 | }
38 |
39 | public function isConfigured()
40 | {
41 | return $this->isConfigured;
42 | }
43 |
44 | public function getUser(Message $matchingMessage)
45 | {
46 | return new User($matchingMessage->getUser());
47 | }
48 |
49 | public function getConversationAnswer(Message $message)
50 | {
51 | return Answer::create($message->getMessage())->setMessage($message);
52 | }
53 |
54 | public function reply($message, $matchingMessage, $additionalParameters = [])
55 | {
56 | $this->botMessages[] = $message;
57 | return $this;
58 | }
59 |
60 | public function getName()
61 | {
62 | return 'Fake Driver';
63 | }
64 |
65 | public function types(Message $matchingMessage)
66 | {
67 | $this->botIsTyping = true;
68 | }
69 |
70 | /**
71 | * @return bool
72 | */
73 | public function isBotTyping()
74 | {
75 | return $this->botIsTyping;
76 | }
77 |
78 | /**
79 | * @return string[]|Question[]
80 | */
81 | public function getBotMessages()
82 | {
83 | return $this->botMessages;
84 | }
85 |
86 | public function resetBotMessages()
87 | {
88 | $this->botMessages = [];
89 | }
90 | }
--------------------------------------------------------------------------------
/src/MageBot.php:
--------------------------------------------------------------------------------
1 | config = $config;
40 | $this->cache = $cache;
41 | $this->customerGroupConversation = $customerGroupConversation;
42 | }
43 |
44 | public function start()
45 | {
46 | DriverManager::loadDriver(BotmanWebDriver::class);
47 |
48 | /** @var BotMan $botman */
49 | $botman = BotManFactory::create($this->config->toArray(), $this->cache);
50 | $this->config->configureBotMan($botman);
51 |
52 | // this is how it should work:
53 | // $conversations = new ConversationDefinitionList(...$this->conversationRepository->findAllActive());
54 | // $conversations->register($botman);
55 | // ---
56 |
57 | $botman->hears('hi|hello|hallo', function (BotMan $bot) {
58 | $bot->reply('Hi! We sell really great stuff. But first, I\'ll ask you a few questions to understand your need.');
59 | $bot->startConversation($this->customerGroupConversation);
60 | });
61 |
62 | $botman->listen();
63 |
64 | }
65 | }
--------------------------------------------------------------------------------
/src/Botman/ProxyDriver.php:
--------------------------------------------------------------------------------
1 | matchesRequest();
39 | }
40 |
41 | public function getMessages()
42 | {
43 | return self::instance()->getMessages();
44 | }
45 |
46 | public function isBot()
47 | {
48 | return self::instance()->isBot();
49 | }
50 |
51 | public function isConfigured()
52 | {
53 | return self::instance()->isConfigured();
54 | }
55 |
56 | public function getUser(Message $matchingMessage)
57 | {
58 | return self::instance()->getUser($matchingMessage);
59 | }
60 |
61 | public function getConversationAnswer(Message $message)
62 | {
63 | return self::instance()->getConversationAnswer($message);
64 | }
65 |
66 | public function reply($message, $matchingMessage, $additionalParameters = [])
67 | {
68 | return self::instance()->reply($message, $matchingMessage, $additionalParameters);
69 | }
70 |
71 | public function getName()
72 | {
73 | return self::instance()->getName();
74 | }
75 |
76 | public function types(Message $matchingMessage)
77 | {
78 | return self::instance()->types($matchingMessage);
79 | }
80 |
81 | }
--------------------------------------------------------------------------------
/src/Botman/BotmanWebDriver.php:
--------------------------------------------------------------------------------
1 | request = $request;
31 | }
32 |
33 | public function matchesRequest()
34 | {
35 | return $this->request->get('test');
36 | }
37 |
38 | public function getMessages()
39 | {
40 | return [
41 | new Message($this->request->get('msg'), 'dummy user', 'dummy channel')
42 | ];
43 | }
44 |
45 | public function isBot()
46 | {
47 | return false;
48 | }
49 |
50 | public function isConfigured()
51 | {
52 | return true;
53 | }
54 |
55 | public function getUser(Message $matchingMessage)
56 | {
57 | return new User();
58 | }
59 |
60 | public function getConversationAnswer(Message $message)
61 | {
62 | return Answer::create($message->getMessage())->setMessage($message);
63 | }
64 |
65 | public function reply($message, $matchingMessage, $additionalParameters = [])
66 | {
67 | $this->replies[] = [
68 | 'message' => $message,
69 | 'parameters' => $additionalParameters,
70 | ];
71 | }
72 |
73 | public function getName()
74 | {
75 | return self::DRIVER_NAME;
76 | }
77 |
78 | public function __destruct()
79 | {
80 | // cannot use DI with botman drivers or access botman driver from third party code, so just echo at the end of the request:
81 | if (!empty($this->replies)) {
82 | header('Content-type: application/json; charset=utf-8', true);
83 | echo json_encode($this->replies);
84 | }
85 | }
86 | }
--------------------------------------------------------------------------------
/tests/unit/StateMachine/SerializableTriggerTest.php:
--------------------------------------------------------------------------------
1 | triggerFactoryMock = $this->getMockBuilder(TriggerFactory::class)
21 | ->getMockForAbstractClass();
22 | SerializableTrigger::setTriggerFactory($this->triggerFactoryMock);
23 | }
24 | protected function tearDown()
25 | {
26 | SerializableTrigger::resetTriggerFactory();
27 | }
28 |
29 | public function testRecreateSerializedTrigger()
30 | {
31 | $parameters = ['foo' => 'bar', 'activated' => false];
32 | $trigger = new FakeTrigger($parameters);
33 | $this->triggerFactoryMock->expects(static::once())
34 | ->method('create')
35 | ->with(FakeTrigger::class, $parameters)
36 | ->willReturn($trigger);
37 |
38 | $serializedTrigger = serialize(new SerializableTrigger($trigger));
39 | /** @var SerializableTrigger $unserializedTrigger */
40 | $unserializedTrigger = unserialize($serializedTrigger);
41 |
42 | static::assertEquals($parameters, $unserializedTrigger->parameters());
43 | static::assertFalse($unserializedTrigger->activated($this->createMock(ConversationContext::class)));
44 | }
45 |
46 | public function testPreventMultipleDecoration()
47 | {
48 | $parameters = ['boo' => 'far', 'activated' => true];
49 | $trigger = new FakeTrigger($parameters);
50 | $this->triggerFactoryMock->expects(static::once())
51 | ->method('create')
52 | ->with(FakeTrigger::class, $parameters)
53 | ->willReturn($trigger);
54 |
55 | $serialized = serialize(new SerializableTrigger(new SerializableTrigger($trigger)));
56 | /** @var SerializableTrigger $unserializedTrigger */
57 | $unserializedTrigger = unserialize($serialized);
58 | static::assertEquals($parameters, $unserializedTrigger->parameters());
59 | static::assertTrue($unserializedTrigger->activated($this->createMock(ConversationContext::class)));
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/StateMachine/Serialization/SerializableAction.php:
--------------------------------------------------------------------------------
1 | action = $action;
48 | }
49 |
50 | public function jsonSerialize()
51 | {
52 | return [
53 | 'type' => $this->action->type(),
54 | 'parameters' => $this->action->parameters()
55 | ];
56 | }
57 |
58 | public function serialize() : string
59 | {
60 | return json_encode(
61 | array_values($this->jsonSerialize())
62 | );
63 | }
64 |
65 | public function unserialize($serialized)
66 | {
67 | list($type, $params) = json_decode($serialized, true);
68 | $this->action = static::$actionFactory->create($type, $params);
69 | }
70 |
71 | public function type() : string
72 | {
73 | return get_class($this->action);
74 | }
75 |
76 | public function parameters() : array
77 | {
78 | return $this->action->parameters();
79 | }
80 |
81 | public function execute(ConversationContext $context)
82 | {
83 | $this->action->execute($context);
84 | }
85 |
86 | }
87 | SerializableAction::resetActionFactory();
--------------------------------------------------------------------------------
/src/StateMachine/Serialization/SerializableTrigger.php:
--------------------------------------------------------------------------------
1 | trigger = $trigger;
48 | }
49 |
50 | public function jsonSerialize():array
51 | {
52 | return [
53 | 'type' => $this->trigger->type(),
54 | 'parameters' => $this->trigger->parameters()
55 | ];
56 | }
57 |
58 | public function serialize() : string
59 | {
60 | return json_encode(
61 | array_values($this->jsonSerialize())
62 | );
63 | }
64 |
65 | public function unserialize($serialized)
66 | {
67 | list($type, $params) = json_decode($serialized, true);
68 | $this->trigger = static::$triggerFactory->create($type, $params);
69 | }
70 |
71 | public function type() : string
72 | {
73 | return get_class($this->trigger);
74 | }
75 |
76 | public function parameters() : array
77 | {
78 | return $this->trigger->parameters();
79 | }
80 |
81 | public function activated(ConversationContext $context) : bool
82 | {
83 | return $this->trigger->activated($context);
84 | }
85 |
86 | }
87 | SerializableTrigger::resetTriggerFactory();
--------------------------------------------------------------------------------
/src/StateMachine/Conversation.php:
--------------------------------------------------------------------------------
1 | contains($currentState)) {
20 | throw new \DomainException('Current state must be element of known states');
21 | }
22 | $this->context = $context;
23 | $this->states = $states;
24 | $this->currentState = $currentState;
25 | $this->transitions = $transitions;
26 | }
27 |
28 | public static function createUnstarted(States $states, Transitions $transitions, State $initialState, ConversationContext $context) : Conversation
29 | {
30 | if (!$states->contains($initialState)) {
31 | throw new \DomainException('Initial state must be element of known states');
32 | }
33 | $unstarted = new UnstartedState();
34 | $initialTransition = new InitialTransition($initialState);
35 | return static::create($states->with($unstarted), $transitions->prepend($initialTransition), $unstarted, $context);
36 | }
37 |
38 | public static function create(States $states, Transitions $transitions, State $currentState, ConversationContext $context) : Conversation
39 | {
40 | return new static($states, $transitions, $currentState, $context);
41 | }
42 |
43 | public function context() : ConversationContext
44 | {
45 | return $this->context;
46 | }
47 |
48 | public function state() : State
49 | {
50 | return $this->currentState;
51 | }
52 |
53 | //TODO consider removing the states reference, it should only be needed in ConversationDefinition if at all
54 | public function states() : States
55 | {
56 | return $this->states;
57 | }
58 |
59 | public function continue() : StateMachine
60 | {
61 | $nextState = $this->transitions->nextState($this->currentState, $this->context);
62 | if ($nextState !== $this->currentState) {
63 | $this->currentState->exitActions()->executeAll($this->context);
64 | $nextState->entryActions()->executeAll($this->context);
65 | return static::create($this->states, $this->transitions, $nextState, $this->context);
66 | }
67 | return $this;
68 | }
69 | }
--------------------------------------------------------------------------------
/tests/unit/StateMachine/SerializableActionTest.php:
--------------------------------------------------------------------------------
1 | actionFactoryMock = $this->getMockBuilder(ActionFactory::class)
21 | ->getMockForAbstractClass();
22 | SerializableAction::setActionFactory($this->actionFactoryMock);
23 | }
24 | protected function tearDown()
25 | {
26 | SerializableAction::resetActionFactory();
27 | }
28 |
29 | public function testRecreateSerializedAction()
30 | {
31 | $parameters = ['foo' => 'bar'];
32 | $action = new FakeAction($parameters);
33 | $this->actionFactoryMock->expects(static::once())
34 | ->method('create')
35 | ->with(FakeAction::class, $parameters)
36 | ->willReturn($action);
37 |
38 | $serializedAction = serialize(new SerializableAction($action));
39 | /** @var SerializableAction $unserializedAction */
40 | $unserializedAction = unserialize($serializedAction);
41 |
42 | static::assertEquals($parameters, $unserializedAction->parameters());
43 | static::assertEquals(0, $action->timesExecuted());
44 | $unserializedAction->execute($this->createMock(ConversationContext::class));
45 | static::assertEquals(1, $action->timesExecuted());
46 | }
47 |
48 | public function testPreventMultipleDecoration()
49 | {
50 | $parameters = ['boo' => 'far'];
51 | $action = new FakeAction($parameters);
52 | $this->actionFactoryMock->expects(static::once())
53 | ->method('create')
54 | ->with(FakeAction::class, $parameters)
55 | ->willReturn($action);
56 |
57 | $serialized = serialize(new SerializableAction(new SerializableAction($action)));
58 | /** @var SerializableAction $deserialized */
59 | $deserialized = unserialize($serialized);
60 | static::assertEquals($parameters, $deserialized->parameters());
61 |
62 | static::assertEquals($parameters, $deserialized->parameters());
63 | static::assertEquals(0, $action->timesExecuted());
64 | $deserialized->execute($this->createMock(ConversationContext::class));
65 | static::assertEquals(1, $action->timesExecuted());
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Botman/QuestionAction.php:
--------------------------------------------------------------------------------
1 | botman = $botman;
43 | $this->message = $message;
44 | $this->answers = $answers;
45 | }
46 |
47 | private static function validateAnswer($answer)
48 | {
49 | if (! is_array($answer)) {
50 | throw new \DomainException('Answer definition must be array');
51 | }
52 | if (! array_key_exists(self::ANSWER_PARAM_TEXT, $answer)) {
53 | throw new \DomainException('Answer definition must contain "text"');
54 | }
55 | if (! array_key_exists(self::ANSWER_PARAM_VALUE, $answer)) {
56 | throw new \DomainException('Answer definition must contain "value"');
57 | }
58 | }
59 |
60 | public function type() : string
61 | {
62 | return __CLASS__;
63 | }
64 |
65 | public function parameters() : array
66 | {
67 | return [
68 | self::PARAM_MESSAGE => $this->message,
69 | self::PARAM_ANSWERS => $this->answers,
70 | ];
71 | }
72 |
73 | public function execute(ConversationContext $context)
74 | {
75 | $this->botman->reply(
76 | Question::create($this->message)->addButtons(
77 | array_map(
78 | function(array $answer) : Button {
79 | return Button::create($answer[QuestionAction::ANSWER_PARAM_TEXT])
80 | ->value($answer[QuestionAction::ANSWER_PARAM_VALUE])
81 | ->image($answer[QuestionAction::ANSWER_PARAM_IMAGE_URL] ?? '');
82 | },
83 | $this->answers
84 | )
85 | )
86 | );
87 | }
88 |
89 | }
--------------------------------------------------------------------------------
/tests/unit/Botman/QuestionActionTest.php:
--------------------------------------------------------------------------------
1 | 'yes', 'value' => 1],
19 | ['text' => 'no', 'value' => 0],
20 | ];
21 | $action = new QuestionAction(
22 | $this->createMock(BotMan::class),
23 | $message, $answers
24 | );
25 | static::assertEquals(QuestionAction::class, $action->type());
26 | static::assertEquals(
27 | [
28 | 'message' => $message,
29 | 'answers' => $answers,
30 | ],
31 | $action->parameters()
32 | );
33 | }
34 |
35 | /**
36 | * @dataProvider dataInvalidDefinitions
37 | */
38 | public function testInvalidDefinition(array $answers)
39 | {
40 | $this->expectException(\DomainException::class);
41 | new QuestionAction(
42 | $this->createMock(BotMan::class),
43 | 'what?',
44 | $answers
45 | );
46 | }
47 |
48 | public static function dataInvalidDefinitions()
49 | {
50 | return [
51 | 'answer is not array' => [
52 | ['answer!']
53 | ],
54 | 'answer does not contain text' => [
55 | [['value' => 'only-value']]
56 | ],
57 | 'answer does not contain value' => [
58 | [['text' => 'only text']]
59 | ],
60 | ];
61 | }
62 |
63 | public function testSendMessage()
64 | {
65 | $message = 'Top or flop?';
66 | $answers = [
67 | ['text' => 'top', 'value' => 1, 'image_url' => 'hop.png'],
68 | ['text' => 'flop', 'value' => -1, 'image_url' => 'flop.gif'],
69 | ['text' => 'meh', 'value' => 0]
70 | ];
71 |
72 | $botMock = $this->createMock(BotMan::class);
73 | $botMock->expects(static::once())
74 | ->method('reply')
75 | ->willReturnCallback(function(Question $question) use ($message, $answers) {
76 | static::assertEquals($message, $question->getText());
77 | $buttons = $question->getButtons();
78 | static::assertCount(\count($answers), $buttons);
79 | reset($buttons);
80 | foreach ($answers as $expectedAnswer) {
81 | static::assertArraySubset($expectedAnswer, current($buttons));
82 | next($buttons);
83 | }
84 | });
85 |
86 | $action = new QuestionAction(
87 | $botMock,
88 | $message, $answers
89 | );
90 | $action->execute($this->createMock(ConversationContext::class));
91 | }
92 | }
--------------------------------------------------------------------------------
/tests/unit/StateMachine/ConversationTransitionTest.php:
--------------------------------------------------------------------------------
1 | notSerializableTrigger(false),
32 | $this->notSerializableTrigger(true)
33 | )
34 | );
35 | $serializedTransition = serialize($transition);
36 | /** @var ConversationTransition $unserializedTransition */
37 | $unserializedTransition = unserialize($serializedTransition);
38 |
39 | static::assertTrue($unserializedTransition->triggeredAt($stateFrom, $this->createMock(ConversationContext::class)));
40 | }
41 |
42 | public function testToArray()
43 | {
44 | $stateFrom = ConversationState::createWithoutActions('denmark');
45 | $stateTo = ConversationState::createWithoutActions('germany');
46 | $transition = ConversationTransition::create(
47 | 'shopping tour',
48 | $stateFrom,
49 | $stateTo,
50 | new TriggerList(
51 | new FakeTrigger(['alcohol' => 0])
52 | )
53 | );
54 | $transitionAsArray = $transition->toArray();
55 | static::assertEquals(
56 | ['name', 'source', 'target', 'triggers'],
57 | array_keys($transitionAsArray)
58 | );
59 | static::assertEquals('shopping tour', $transitionAsArray['name']);
60 | static::assertSame($stateFrom, $transitionAsArray['source']);
61 | static::assertSame($stateTo, $transitionAsArray['target']);
62 | static::assertJsonStringEqualsJsonString(
63 | json_encode([
64 | ['type' => FakeTrigger::class, 'parameters' => ['alcohol' => 0]]
65 | ]),
66 | $transitionAsArray['triggers']
67 | );
68 | }
69 |
70 | private function notSerializableTrigger($activated) : Trigger
71 | {
72 | $trigger = new FakeTrigger(['activated' => $activated]);
73 | $trigger->evilProperty = new class
74 | {
75 | // in case that serialization of anonymous classes is allowed in future PHP versions:
76 | public function __sleep()
77 | {
78 | throw new \Exception('I should not be serialized');
79 | }
80 | };
81 | return $trigger;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/tests/unit/StateMachine/ConversationStateTest.php:
--------------------------------------------------------------------------------
1 | name());
26 | static::assertEquals(new ActionList, $state->entryActions());
27 | static::assertEquals(new ActionList, $state->exitActions());
28 | }
29 | public function testCreateWithActions()
30 | {
31 | $entryActions = $this->dummyActionList();
32 | $exitActions = $this->dummyActionList();
33 | $state = ConversationState::createWithActions('both', $entryActions, $exitActions);
34 | static::assertEquals('both', $state->name());
35 | static::assertSame($entryActions, $state->entryActions());
36 | static::assertSame($exitActions, $state->exitActions());
37 | }
38 | public function testCreateWithEntryActions()
39 | {
40 | $entryActions = $this->dummyActionList();
41 | $state = ConversationState::createWithEntryActions('entry', $entryActions);
42 | static::assertEquals('entry', $state->name());
43 | static::assertSame($entryActions, $state->entryActions());
44 | static::assertEquals(new ActionList, $state->exitActions());
45 | }
46 | public function testCreateWithExitActions()
47 | {
48 | $exitActions = $this->dummyActionList();
49 | $state = ConversationState::createWithExitActions('exit', $exitActions);
50 | static::assertEquals('exit', $state->name());
51 | static::assertEquals(new ActionList, $state->entryActions());
52 | static::assertSame($exitActions, $state->exitActions());
53 | }
54 |
55 | public function testCanBeSerializedWithActions()
56 | {
57 | $state = ConversationState::createWithEntryActions(
58 | 'state-1',
59 | new ActionList(
60 | $this->notSerializableAction(),
61 | $this->notSerializableAction()
62 | )
63 | );
64 | $serializedState = serialize($state);
65 | /** @var ConversationState $unserializedState */
66 | $unserializedState = unserialize($serializedState);
67 | static::assertCount(2, $unserializedState->entryActions(), 'Unserialized state should still have two entry actions');
68 | $unserializedState->entryActions()->executeAll($this->createMock(ConversationContext::class));
69 | }
70 |
71 | public function testToArray()
72 | {
73 | $state = ConversationState::createWithActions(
74 | 'state name',
75 | new ActionList(
76 | new FakeAction(['bot' => 'bender'])
77 | ),
78 | new ActionList(
79 | new FakeAction(['bot' => 'marvin', 'mood' => 'depressed'])
80 | )
81 | );
82 | $stateAsArray = $state->toArray();
83 | static::assertEquals(
84 | ['name', 'entry_actions', 'exit_actions'],
85 | array_keys($stateAsArray)
86 | );
87 | static::assertEquals('state name', $stateAsArray['name']);
88 | static::assertJsonStringEqualsJsonString(
89 | \json_encode([
90 | ['type' => FakeAction::class, 'parameters' => ['bot' => 'bender']],
91 | ]), $stateAsArray['entry_actions']
92 | );
93 | static::assertJsonStringEqualsJsonString(
94 | \json_encode([
95 | ['type' => FakeAction::class, 'parameters' => ['bot' => 'marvin', 'mood' => 'depressed']],
96 | ]), $stateAsArray['exit_actions']
97 | );
98 | }
99 |
100 | /**
101 | * @return ActionList
102 | */
103 | private function dummyActionList() : ActionList
104 | {
105 | static $counter = 0;
106 | ++$counter;
107 | return new ActionList(
108 | new CallbackAction(
109 | function () use ($counter) {
110 | return $counter;
111 | }
112 | )
113 | );
114 | }
115 |
116 | /**
117 | * @return Action
118 | */
119 | private function notSerializableAction() : Action
120 | {
121 | $action = new FakeAction([]);
122 | $action->evilProperty = new class
123 | {
124 | // in case that serialization of anonymous classes is allowed in future PHP versions:
125 | public function __sleep()
126 | {
127 | throw new \Exception('I should not be serialized');
128 | }
129 | };
130 | return $action;
131 | }
132 | }
--------------------------------------------------------------------------------
/src/etc/adminhtml/system.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | service
7 | FireGento_MageBot::config_magebot
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | Magento\Config\Model\Config\Backend\Encrypted
18 |
19 |
20 |
21 | Magento\Config\Model\Config\Backend\Encrypted
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | Magento\Config\Model\Config\Backend\Encrypted
35 |
36 |
37 |
38 |
39 |
40 |
41 | Magento\Config\Model\Config\Backend\Encrypted
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | Magento\Config\Model\Config\Backend\Encrypted
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | Magento\Config\Model\Config\Backend\Encrypted
63 |
64 |
65 |
66 | Magento\Config\Model\Config\Backend\Encrypted
67 |
68 |
69 |
70 | Magento\Config\Model\Config\Backend\Encrypted
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | Magento\Config\Model\Config\Backend\Encrypted
81 |
82 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/tests/unit/Botman/BotmanConversationTest.php:
--------------------------------------------------------------------------------
1 | fakeDriver = new FakeDriver();
54 | ProxyDriver::setInstance($this->fakeDriver);
55 | $this->botman = BotManFactory::create([]);
56 | $conversationDefinitions = new ConversationDefinitionList(
57 | $this->mittagessenConversationDefinition($this->botman)
58 | );
59 | $conversationDefinitions->register($this->botman);
60 |
61 | SerializableAction::setActionFactory(new BotmanActionFactory($this->botman));
62 | SerializableTrigger::setTriggerFactory(new NewTriggerFactory);
63 | }
64 |
65 | protected function tearDown()
66 | {
67 | ProxyDriver::setInstance(new NullDriver(new Request, [], new Curl));
68 | SerializableAction::resetActionFactory();
69 | SerializableTrigger::resetTriggerFactory();
70 | }
71 |
72 | public function testConversationWithQuestionAndAnswer()
73 | {
74 | $this->username = 'user123';
75 | $this->channel = '#mittagessen';
76 |
77 | $this->userWrites('Mittagessen');
78 | $this->botman->listen();
79 | $this->assertBotReplies(
80 | [
81 | 'Mittagessenplan',
82 | Question::create('Ihr Kind isst')->addButtons(
83 | [
84 | Button::create('Fisch')->value('fisch'),
85 | Button::create('Fleisch')->value('fleisch')
86 | ]
87 | )
88 | ]
89 | );
90 | $this->userWrites('fisch');
91 | $this->assertBotReplies(['Ihr Kind isst kein Fleisch']);
92 | }
93 |
94 | private function mittagessenConversationDefinition(BotMan $botman) : ConversationDefinition
95 | {
96 | $initialState = ConversationState::createWithEntryActions(
97 | 'state-fisch', new ActionList(
98 | new MessageAction($botman, 'Mittagessenplan'),
99 | new QuestionAction($botman, 'Ihr Kind isst', [['text' => 'Fisch', 'value' => 'fisch'], ['text' => 'Fleisch', 'value' => 'fleisch']])
100 | )
101 | );
102 | $fischState = ConversationState::createWithEntryActions(
103 | 'state-fisch',
104 | new ActionList(new MessageAction($botman, 'Ihr Kind isst kein Fleisch'))
105 | );
106 | $fleischState = ConversationState::createWithEntryActions(
107 | 'state-fleisch',
108 | new ActionList(new MessageAction($botman, 'Ihr Kind isst kein Fisch'))
109 | );
110 | $conversation = Conversation::createUnstarted(
111 | new StateSet($initialState, $fischState, $fleischState),
112 | new TransitionList(
113 | ConversationTransition::createAnonymous(
114 | $initialState, $fischState, new TriggerList(
115 | new AnswerTrigger('fisch')
116 | )
117 | ),
118 | ConversationTransition::createAnonymous(
119 | $initialState, $fleischState, new TriggerList(
120 | new AnswerTrigger('fleisch')
121 | )
122 | )
123 | ),
124 | $initialState, new BotmanConversationContext()
125 | );
126 | return new class($conversation) implements ConversationDefinition {
127 | /** @var Conversation */
128 | private $conversation;
129 |
130 | public function __construct(Conversation $conversation)
131 | {
132 | $this->conversation = $conversation;
133 | }
134 |
135 | public function patternToStart() : string
136 | {
137 | return 'Mittagessen';
138 | }
139 |
140 | public function create() : Conversation
141 | {
142 | return $this->conversation;
143 | }
144 | };
145 | }
146 |
147 | private function userWrites(string $text)
148 | {
149 | $this->fakeDriver->resetBotMessages();
150 | $this->fakeDriver->messages = [new Message($text, $this->username, $this->channel)];
151 | $this->botman->loadActiveConversation();
152 | }
153 |
154 | private function assertBotReplies($replies)
155 | {
156 | static::assertEquals($replies, $this->fakeDriver->getBotMessages());
157 | }
158 | }
--------------------------------------------------------------------------------
/src/Conversations/CustomerGroupConversation.php:
--------------------------------------------------------------------------------
1 | productRepository = $productRepository;
34 | $this->searchCriteriaBuilder = $searchCriteriaBuilder;
35 | $this->imageBuilder = $imageBuilder;
36 | }
37 |
38 | public function askForMainGroup()
39 | {
40 | $botResponse = ButtonTemplate::create('What is your goal?')
41 | ->addButton(ElementButton::create('Loose Weight')->type('postback')->payload('weight'))
42 | ->addButton(ElementButton::create('Eat Healthy')->type('postback')->payload('eat'))
43 | ->addButton(ElementButton::create('Become Fit')->type('postback')->payload('fit'));
44 |
45 | $this->ask($botResponse, function (Answer $answer) {
46 |
47 | $this->group = $answer->getText();
48 |
49 | if($answer->getText() === "weight") {
50 | $this->askForWeight();
51 | } else if ($answer->getText() === "eat") {
52 | // Cookbooks
53 |
54 | $this->say('Ok, members who want to keep their weight and eat healthy like our cookbooks:');
55 |
56 | $products = $this->productRepository->getList($this->searchCriteriaBuilder->create())->getItems();
57 | $this->returnProducts($products);
58 |
59 | $this->askFinalQuestion();
60 | } else {
61 | // Fitness Products
62 |
63 | $this->say('Ok, sounds great. Take a look at these fitness products:');
64 |
65 | $products = $this->productRepository->getList($this->searchCriteriaBuilder->create())->getItems();
66 | $this->returnProducts($products);
67 |
68 | $this->askFinalQuestion();
69 | }
70 | });
71 | }
72 |
73 | private function askForWeight()
74 | {
75 | $question = Question::create('Do you want to lose more than 10kg?')
76 | ->fallback('Unable to ask question')
77 | ->callbackId('ask_approval')
78 | ->addButtons([
79 | Button::create('Yes')->value('yes'),
80 | Button::create('No')->value('no')
81 | ]);
82 |
83 | $this->ask($question, function (Answer $answer) {
84 |
85 | if($answer->getValue() === "yes") {
86 |
87 | // ask for member status
88 |
89 | $this->askForMemberStatus();
90 |
91 | } else {
92 |
93 | $this->say('Excellent, take a look at these products:');
94 |
95 | //$products = $this->productRepository->getList($this->searchCriteriaBuilder->create())->getItems();
96 | //$this->returnProducts($products);
97 |
98 | //$this->askFinalQuestion();
99 | }
100 |
101 | });
102 | }
103 |
104 | public function run()
105 | {
106 | $this->askForMainGroup();
107 | }
108 |
109 | private function askForMemberStatus()
110 | {
111 | $this->say('Loosing much Weight works best in a group.');
112 |
113 | $question = Question::create('Are you currently a Weight Watchers member?')
114 | ->fallback('Unable to ask question')
115 | ->callbackId('ask_approval')
116 | ->addButtons([
117 | Button::create('Yes, I am')->value('yes'),
118 | Button::create('Not yet')->value('no')
119 | ]);
120 |
121 | $this->ask($question, function (Answer $answer) {
122 |
123 | if($answer->getValue() === "yes") {
124 |
125 | $this->say('Nice, you can try our cookbooks for healthier eating to speed up your progress or take alook at our fitness stuff');
126 |
127 | $products = $this->productRepository->getList($this->searchCriteriaBuilder->create())->getItems();
128 | $this->returnProducts($products);
129 |
130 | $this->askFinalQuestion();
131 |
132 | } else {
133 |
134 | $this->say('We like to support you with our best program:');
135 |
136 | $this->say(GenericTemplate::create()
137 | ->addElement(
138 | Element::create('Feel Good')
139 | ->subtitle('Das Weight Watchers Programm')
140 | ->image('https://www.weightwatchers.com/de/sites/de/files/styles/wwvs_bts_image_thumbnail_large/public/welcher-sporttyp-bist-du_thumbnail.png?itok=oUIUqiuY')
141 | ->addButton(ElementButton::create('Visit')
142 | ->url('https://www.weightwatchers.com/de/programm')
143 | ))
144 | );
145 |
146 | $this->askFinalQuestion();
147 |
148 | }
149 |
150 | });
151 | }
152 |
153 | private function askFinalQuestion()
154 | {
155 | $question = Question::create('Anything else I can help you with?')
156 | ->fallback('Unable to ask question')
157 | ->callbackId('ask_approval')
158 | ->addButtons([
159 | Button::create('Yes')->value('yes'),
160 | Button::create('No, thanks')->value('no')
161 | ]);
162 |
163 | $this->ask($question, function (Answer $answer) {
164 |
165 | if($answer->getValue() === "yes") {
166 | $this->askForMainGroup();
167 | } else {
168 | $this->say('Ok, goodbye!');
169 | }
170 | });
171 | }
172 |
173 | private function returnProducts($products)
174 | {
175 | $list = GenericTemplate::create();
176 |
177 | /** @var Product $product */
178 | foreach ($products as $product) {
179 | $list->addElement(
180 | Element::create($product->getName())
181 | ->subtitle(strip_tags(html_entity_decode($product->getShortDescription())))
182 | ->image($this->imageBuilder->setProduct($product)
183 | ->setImageId('product_page_image_large')
184 | ->create()->getImageUrl())
185 | ->addButton(ElementButton::create('Visit')
186 | ->url($product->getProductUrl())
187 | ));
188 | }
189 |
190 | $this->say($list);
191 | }
192 | }
--------------------------------------------------------------------------------
/tests/unit/StateMachine/ConversationTest.php:
--------------------------------------------------------------------------------
1 | context = $this->createMock(ConversationContext::class);
15 | }
16 |
17 | public function testConversationCanBeCreatedWithState()
18 | {
19 | $initialState = ConversationState::createWithoutActions('initial');
20 | $states = new StateSet($initialState);
21 | $conversation = Conversation::create($states, new TransitionList(), $initialState, $this->context);
22 | static::assertEquals($initialState, $conversation->state());
23 | }
24 | public function testConversationCanReturnStateList()
25 | {
26 | $initialState = ConversationState::createWithoutActions('initial');
27 | $states = new StateSet($initialState, ConversationState::createWithoutActions('state-1'), ConversationState::createWithoutActions('state-2'));
28 | $conversation = Conversation::create($states, new TransitionList(), $initialState, $this->context);
29 | static::assertEquals($states, $conversation->states());
30 | }
31 | public function testConversationCannotBeCreatedWithUnknownState()
32 | {
33 | $initialState = ConversationState::createWithoutActions('initial');
34 | $states = new StateSet(ConversationState::createWithoutActions('only-something-else'));
35 |
36 | $this->expectException(\DomainException::class);
37 | Conversation::create($states, new TransitionList(), $initialState, $this->context);
38 | }
39 |
40 | public function testConversationCanBeCreatedInUnstartedState()
41 | {
42 | $initialState = ConversationState::createWithEntryActions('initial', new ActionList($this->actionExpectedNotToBeCalled()));
43 | $states = new StateSet($initialState);
44 | $conversation = Conversation::createUnstarted($states, new TransitionList(), $initialState, $this->context);
45 | static::assertInstanceOf(UnstartedState::class, $conversation->state());
46 | }
47 |
48 | public function testUnstartedConversationCanBeStarted()
49 | {
50 | $initialState = ConversationState::createWithEntryActions('initial', new ActionList($this->actionExpectedToBeCalled()));
51 | $states = new StateSet($initialState);
52 | $conversation = Conversation::createUnstarted($states, new TransitionList(), $initialState, $this->context);
53 | static::assertSame($initialState, $conversation->continue()->state());
54 | }
55 |
56 | public function testUnstartedConversationCannotBeCreatedWithUnknownInitialState()
57 | {
58 | $initialState = ConversationState::createWithEntryActions('initial', new ActionList($this->actionExpectedNotToBeCalled()));
59 | $states = new StateSet(ConversationState::createWithoutActions('only-something-else'));
60 |
61 | $this->expectException(\DomainException::class);
62 | Conversation::createUnstarted($states, new TransitionList(), $initialState, $this->context);
63 | }
64 |
65 | public function testTransitionBasedOnTriggers()
66 | {
67 | $initialState = ConversationState::createWithoutActions('initial');
68 | $alternateState = ConversationState::createWithoutActions('final');
69 | $unreachableState = ConversationState::createWithoutActions('unreachable');
70 | /** @var Conversation $conversation */
71 | $conversation = Conversation::create(
72 | new StateSet($initialState, $alternateState),
73 | new TransitionList(
74 | ConversationTransition::createAnonymous($initialState, $unreachableState, new TriggerList(new FixedTrigger(false))),
75 | ConversationTransition::createAnonymous($initialState, $alternateState, new TriggerList(new FixedTrigger(true))),
76 | ConversationTransition::createAnonymous($alternateState, $initialState, new TriggerList(new FixedTrigger(true)))
77 | ),
78 | $initialState,
79 | $this->context
80 | );
81 | static::assertEquals($initialState, $conversation->state());
82 | $conversation = $conversation->continue();
83 | static::assertEquals($alternateState, $conversation->state());
84 | $conversation = $conversation->continue();
85 | static::assertEquals($initialState, $conversation->state());
86 | }
87 | public function testNewObjectIfStateChanged()
88 | {
89 | $initialState = ConversationState::createWithoutActions('initial');
90 | $nextState = ConversationState::createWithoutActions('final');
91 | $conversation = Conversation::create(
92 | new StateSet($initialState, $nextState),
93 | new TransitionList(
94 | ConversationTransition::createAnonymous($initialState, $nextState, new TriggerList(new FixedTrigger(true)))
95 | ),
96 | $initialState,
97 | $this->context
98 | );
99 | static::assertNotSame($conversation, $conversation->continue(), 'continue() should return new object if state changed');
100 | static::assertEquals($initialState, $conversation->state(), 'Conversation should be immutable');
101 | }
102 | public function testNoNewObjectIfStateUnchanged()
103 | {
104 | $initialState = ConversationState::createWithoutActions('initial');
105 | $conversation = Conversation::create(
106 | new StateSet($initialState),
107 | new TransitionList(
108 | ),
109 | $initialState,
110 | $this->context
111 | );
112 | static::assertSame($conversation, $conversation->continue(), 'continue() should return new object if state changed');
113 | }
114 | public function testEntryAndExitActions()
115 | {
116 | $initialState = ConversationState::createWithExitActions('initial', new ActionList($this->actionExpectedToBeCalled()));
117 | $finalState = ConversationState::createWithEntryActions('final', new ActionList($this->actionExpectedToBeCalled()));
118 | $conversation = Conversation::create(
119 | new StateSet($initialState, $finalState),
120 | new TransitionList(
121 | ConversationTransition::createAnonymous($initialState, $finalState, new TriggerList(new FixedTrigger(true)))
122 | ),
123 | $initialState,
124 | $this->context
125 | );
126 | $conversation->continue()->continue();
127 | }
128 |
129 | /**
130 | * @return CallbackAction
131 | */
132 | private function actionExpectedToBeCalled() : CallbackAction
133 | {
134 | $callbackMock = $this->getMockBuilder(\stdClass::class)
135 | ->setMethods(['__invoke'])
136 | ->getMock();
137 | $callbackMock->expects(static::once())->method('__invoke');
138 | return new CallbackAction($callbackMock);
139 | }
140 |
141 | /**
142 | * @return CallbackAction
143 | */
144 | private function actionExpectedNotToBeCalled() : CallbackAction
145 | {
146 | $callbackMock = $this->getMockBuilder(\stdClass::class)
147 | ->setMethods(['__invoke'])
148 | ->getMock();
149 | $callbackMock->expects(static::never())->method('__invoke');
150 | return new CallbackAction($callbackMock);
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/src/Helper/Config.php:
--------------------------------------------------------------------------------
1 | scopeConfig = $scopeConfig;
76 | $this->logger = $logger;
77 | }
78 |
79 | /**
80 | * Returns the configured value for the config value magebot/hipchat/urls
81 | *
82 | * @return array
83 | */
84 | public function getMageBotHipChatUrls()
85 | {
86 | $config = $this->scopeConfig->getValue(Config::CONFIG_PATH_HIPCHAT_URLS, ScopeInterface::SCOPE_STORE);
87 | return explode(",", $config);
88 | }
89 |
90 | /**
91 | * Returns the configured value for the config value magebot/nexmo/key
92 | *
93 | * @return string
94 | */
95 | public function getMageBotNexmoKey()
96 | {
97 | $this->logger->alert(__METHOD__);
98 | $this->logger->alert($this->scopeConfig->getValue(Config::CONFIG_PATH_NEXMO_KEY, ScopeInterface::SCOPE_STORE));
99 | return $this->scopeConfig->getValue(Config::CONFIG_PATH_NEXMO_KEY, ScopeInterface::SCOPE_STORE);
100 | }
101 |
102 | /**
103 | * Returns the configured value for the config value magebot/nexmo/secret
104 | *
105 | * @return string
106 | */
107 | public function getMageBotNexmoSecret()
108 | {
109 | return $this->scopeConfig->getValue(Config::CONFIG_PATH_NEXMO_SECRET, ScopeInterface::SCOPE_STORE);
110 | }
111 |
112 | /**
113 | * Returns the configured value for the config value magebot/nexmo/secret
114 | *
115 | * @return string | null
116 | */
117 | public function getMageBotMicrosoftBotHandle()
118 | {
119 | return $this->scopeConfig->getValue(Config::CONFIG_PATH_MICROSOFT_BOT_HANDLE, ScopeInterface::SCOPE_STORE);
120 | }
121 |
122 | /**
123 | * Returns the configured value for the config value magebot/microsoft/app_id
124 | *
125 | * @return string | null
126 | */
127 | public function getMageBotMicrosoftAppId()
128 | {
129 | return $this->scopeConfig->getValue(Config::CONFIG_PATH_MICROSOFT_ADD_ID, ScopeInterface::SCOPE_STORE);
130 | }
131 |
132 | /**
133 | * Returns the configured value for the config value magebot/microsoft/app_key
134 | *
135 | * @return string | null
136 | */
137 | public function getMageBotMicrosoftAppKey()
138 | {
139 | return $this->scopeConfig->getValue(Config::CONFIG_PATH_MICROSOFT_ADD_KEY, ScopeInterface::SCOPE_STORE);
140 | }
141 |
142 | /**
143 | * Returns the configured value for the config value magebot/slack_real_time_api/token
144 | *
145 | * @return string | null
146 | */
147 | public function getMageBotSlackToken()
148 | {
149 | return $this->scopeConfig->getValue(Config::CONFIG_PATH_SLACK_REAL_TIME_API_TOKEN, ScopeInterface::SCOPE_STORE);
150 | }
151 |
152 | /**
153 | * Returns the configured value for the config value magebot/slack_real_time_api/token
154 | *
155 | * @return string | null
156 | */
157 | public function getMageBotTelegramToken()
158 | {
159 | return $this->scopeConfig->getValue(Config::CONFIG_PATH_TELEGRAM_API_TOKEN, ScopeInterface::SCOPE_STORE);
160 | }
161 |
162 | /**
163 | * Returns the configured value for the config value magebot/facebook/app_secret
164 | *
165 | * @return string | null
166 | */
167 | public function getMageBotFacebookToken()
168 | {
169 | return $this->scopeConfig->getValue(Config::CONFIG_PATH_FACEBOOK_TOKEN, ScopeInterface::SCOPE_STORE);
170 | }
171 |
172 | /**
173 | * Returns the configured value for the config value magebot/facebook/app_secret
174 | *
175 | * @return string | null
176 | */
177 | public function getMageBotFacebookAppSecret()
178 | {
179 | return $this->scopeConfig->getValue(Config::CONFIG_PATH_FACEBOOK_APP_SECRET, ScopeInterface::SCOPE_STORE);
180 | }
181 |
182 | /**
183 | * Returns the configured value for the config value magebot/facebook/webhook_token
184 | *
185 | * @return string | null
186 | */
187 | private function getMageBotFacebookWebhookToken()
188 | {
189 | return $this->scopeConfig->getValue(Config::CONFIG_PATH_FACEBOOK_WEBHOOK_TOKEN, ScopeInterface::SCOPE_STORE);
190 | }
191 |
192 | /**
193 | * Returns the configured value for the config value magebot/facebook/app_secret
194 | *
195 | * @return string | null
196 | */
197 | public function getMageBotWechatAppId()
198 | {
199 | return $this->scopeConfig->getValue(Config::CONFIG_PATH_WECHAT_APP_ID, ScopeInterface::SCOPE_STORE);
200 | }
201 |
202 | /**
203 | * Returns the configured value for the config value magebot/facebook/app_secret
204 | *
205 | * @return string | null
206 | */
207 | public function getMageBotWechatAppKey()
208 | {
209 | return $this->scopeConfig->getValue(Config::CONFIG_PATH_WECHAT_APP_KEY, ScopeInterface::SCOPE_STORE);
210 | }
211 |
212 | /**
213 | * @return array
214 | */
215 | public function toArray() : array
216 | {
217 | return $config = [
218 | 'hipchat_urls' => $this->getMageBotHipChatUrls(),
219 | 'nexmo_key' => $this->getMageBotNexmoKey(),
220 | 'nexmo_secret' => $this->getMageBotNexmoSecret(),
221 | 'microsoft_bot_handle' => $this->getMageBotMicrosoftBotHandle(),
222 | 'microsoft_app_id' => $this->getMageBotMicrosoftAppId(),
223 | 'microsoft_app_key' => $this->getMageBotMicrosoftAppKey(),
224 | 'slack_token' => $this->getMageBotSlackToken(),
225 | 'telegram_token' => $this->getMageBotTelegramToken(),
226 | 'facebook_token' => $this->getMageBotFacebookToken(),
227 | 'facebook_app_secret' => $this->getMageBotFacebookAppSecret(),
228 | 'wechat_app_id' => $this->getMageBotWechatAppId(),
229 | 'wechat_app_key' => $this->getMageBotWechatAppKey()
230 | ];
231 | }
232 |
233 | /**
234 | * @param BotMan $botman
235 | */
236 | public function configureBotMan(BotMan $botman)
237 | {
238 | if($this->getMageBotFacebookWebhookToken()) {
239 | $botman->verifyServices($this->getMageBotFacebookWebhookToken());
240 | }
241 | }
242 | }
243 |
--------------------------------------------------------------------------------