├── .php_cs
├── .scrutinizer.yml
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── composer.json
├── phpunit.xml
├── resources
└── stubs
│ ├── Activator.stub
│ ├── Intent.stub
│ └── Interaction.stub
├── src
├── Channels
│ ├── Channel.php
│ ├── ChannelManager.php
│ ├── Chat.php
│ ├── Driver.php
│ ├── Exceptions
│ │ ├── ChannelNotFound.php
│ │ ├── DriverException.php
│ │ ├── DriverNotFound.php
│ │ ├── InvalidArgument.php
│ │ ├── InvalidConfiguration.php
│ │ └── InvalidRequest.php
│ └── User.php
├── Contracts
│ ├── Channels
│ │ ├── Driver.php
│ │ ├── Manager.php
│ │ └── WebhookVerification.php
│ ├── Conversation
│ │ ├── Activator.php
│ │ ├── Conversable.php
│ │ └── Manager.php
│ ├── Event.php
│ └── Template.php
├── Conversation
│ ├── Activators
│ │ ├── Attachment.php
│ │ ├── Contains.php
│ │ ├── Exact.php
│ │ ├── In.php
│ │ ├── Payload.php
│ │ └── Regex.php
│ ├── Concerns
│ │ ├── Authorization.php
│ │ ├── InteractsWithContext.php
│ │ └── SendsMessages.php
│ ├── Context.php
│ ├── ConversationManager.php
│ ├── FallbackIntent.php
│ ├── Intent.php
│ └── Interaction.php
├── Drivers
│ ├── PendingReply.php
│ └── TemplateCompiler.php
├── Events
│ ├── Event.php
│ ├── MessageReceived.php
│ └── Unknown.php
├── Foundation
│ ├── API.php
│ ├── Commands
│ │ ├── SendAttachment.php
│ │ └── SendMessage.php
│ ├── Composer.php
│ ├── Controller.php
│ ├── Kernel.php
│ ├── Listeners
│ │ └── HandleConversation.php
│ ├── Middleware
│ │ └── InitializeKernel.php
│ ├── Providers
│ │ ├── ChannelServiceProvider.php
│ │ ├── ConversationServiceProvider.php
│ │ ├── EventServiceProvider.php
│ │ ├── FoundationServiceProvider.php
│ │ └── RouteServiceProvider.php
│ └── ServiceProvider.php
├── Framework
│ ├── Application.php
│ ├── Console
│ │ ├── Application.php
│ │ └── Kernel.php
│ ├── Exceptions
│ │ └── Handler.php
│ └── Http
│ │ └── Kernel.php
├── Templates
│ ├── Attachment.php
│ ├── Keyboard.php
│ ├── Keyboard
│ │ ├── Button.php
│ │ ├── PayloadButton.php
│ │ ├── ReplyButton.php
│ │ └── UrlButton.php
│ └── Location.php
├── Toolbelt
│ ├── InstallDriverCommand.php
│ ├── ListChannelsCommand.php
│ ├── ListDriversCommand.php
│ ├── ListIntentsCommand.php
│ ├── MakeActivatorCommand.php
│ ├── MakeIntentCommand.php
│ ├── MakeInteractionCommand.php
│ └── ToolbeltServiceProvider.php
└── helpers.php
└── tests
├── Mocks
├── FakeChannel.php
├── FakeDriver.php
├── FakeIntent.php
├── FakeIntentWithClosureActivator.php
└── FakeInteraction.php
├── TestCase.php
└── Unit
├── Channels
├── ChannelManagerTest.php
├── ChatTest.php
├── DriverTest.php
└── UserTest.php
├── Conversation
├── Activators
│ ├── AttachmentTest.php
│ ├── ContainsTest.php
│ ├── ExactTest.php
│ ├── InArrayTest.php
│ ├── PayloadTest.php
│ └── RegexTest.php
├── Concerns
│ ├── AuthorizationTest.php
│ ├── InteractsWithContextTest.php
│ └── SendsMessagesTest.php
├── ContextTest.php
├── ConversationManagerTest.php
├── FallbackIntentTest.php
├── IntentTest.php
└── InteractionTest.php
├── Drivers
└── TemplateCompilerTest.php
├── Foundation
├── Commands
│ ├── SendAttachmentTest.php
│ └── SendMessageTest.php
└── KernelTest.php
├── HelpersTest.php
└── Templates
├── AttachmentTest.php
├── Keyboard
├── PayloadButtonTest.php
├── ReplyButtonTest.php
└── UrlButtonTest.php
├── KeyboardTest.php
└── LocationTest.php
/.php_cs:
--------------------------------------------------------------------------------
1 | exclude('vendor')
5 | ->in(__DIR__.'/src')
6 | ->in(__DIR__.'/tests')
7 | ->name('*.php')
8 | ->ignoreDotFiles(true)
9 | ->ignoreVCS(true);
10 |
11 | return PhpCsFixer\Config::create()
12 | ->setRules([
13 | '@PSR1' => false,
14 | '@PSR2' => true,
15 | 'strict_param' => true,
16 | 'declare_strict_types' => true,
17 | 'array_syntax' => ['syntax' => 'short'],
18 | 'binary_operator_spaces' => [
19 | 'align_equals' => false,
20 | 'align_double_arrow' => null,
21 | ],
22 | 'blank_line_after_opening_tag' => true,
23 | 'blank_line_before_return' => true,
24 | 'cast_spaces' => true,
25 | 'concat_space' => ['spacing' => 'none'],
26 | 'declare_equal_normalize' => true,
27 | 'function_typehint_space' => true,
28 | 'general_phpdoc_annotation_remove' => ['access', 'package', 'subpackage'],
29 | 'hash_to_slash_comment' => true,
30 | 'heredoc_to_nowdoc' => true,
31 | 'include' => true,
32 | 'lowercase_cast' => true,
33 | 'method_separation' => true,
34 | 'native_function_casing' => true,
35 | 'no_alias_functions' => true,
36 | 'no_blank_lines_after_class_opening' => true,
37 | 'no_blank_lines_after_phpdoc' => true,
38 | 'no_empty_phpdoc' => true,
39 | 'no_empty_statement' => true,
40 | 'no_extra_consecutive_blank_lines' => ['throw', 'use', 'useTrait', 'extra'],
41 | 'no_leading_import_slash' => true,
42 | 'no_leading_namespace_whitespace' => true,
43 | 'no_mixed_echo_print' => ['use' => 'echo'],
44 | 'no_multiline_whitespace_around_double_arrow' => true,
45 | 'no_multiline_whitespace_before_semicolons' => true,
46 | 'no_short_bool_cast' => true,
47 | 'no_singleline_whitespace_before_semicolons' => true,
48 | 'no_spaces_around_offset' => ['inside'],
49 | 'no_trailing_comma_in_list_call' => true,
50 | 'no_trailing_comma_in_singleline_array' => true,
51 | 'no_unneeded_control_parentheses' => true,
52 | 'no_unreachable_default_argument_value' => true,
53 | 'no_unused_imports' => true,
54 | 'no_useless_return' => true,
55 | 'no_whitespace_before_comma_in_array' => true,
56 | 'no_whitespace_in_blank_line' => true,
57 | 'normalize_index_brace' => true,
58 | 'object_operator_without_whitespace' => true,
59 | 'ordered_imports' => ['sortAlgorithm' => 'length'],
60 | 'phpdoc_indent' => true,
61 | 'phpdoc_inline_tag' => true,
62 | 'phpdoc_no_alias_tag' => ['type' => 'var'],
63 | 'phpdoc_no_useless_inheritdoc' => true,
64 | 'phpdoc_scalar' => true,
65 | 'phpdoc_single_line_var_spacing' => true,
66 | 'phpdoc_summary' => true,
67 | 'phpdoc_to_comment' => true,
68 | 'phpdoc_trim' => true,
69 | 'phpdoc_types' => true,
70 | 'phpdoc_var_without_name' => true,
71 | 'psr4' => true,
72 | 'self_accessor' => false,
73 | 'short_scalar_cast' => true,
74 | 'single_blank_line_before_namespace' => true,
75 | 'single_quote' => true,
76 | 'space_after_semicolon' => true,
77 | 'standardize_not_equals' => true,
78 | 'ternary_operator_spaces' => true,
79 | 'trailing_comma_in_multiline_array' => true,
80 | 'trim_array_spaces' => true,
81 | 'unary_operator_spaces' => true,
82 | 'visibility_required' => ['method', 'property'],
83 | 'whitespace_after_comma_in_array' => true,
84 | ])
85 | ->setFinder($finder)
86 | ->setUsingCache(false);
87 |
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | build:
2 | nodes:
3 | analysis:
4 | tests:
5 | override:
6 | - php-scrutinizer-run
7 | filter:
8 | excluded_paths:
9 | - tests/*
10 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at vladimir@fondbot.io. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at [http://contributor-covenant.org/version/1/4][version]
72 |
73 | [homepage]: http://contributor-covenant.org
74 | [version]: http://contributor-covenant.org/version/1/4/
75 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Vladimir Yuldashev
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [](https://packagist.org/packages/fondbot/framework)
4 | [](https://packagist.org/packages/fondbot/framework)
5 | [](https://scrutinizer-ci.com/g/fondbot/framework)
6 | [](https://packagist.org/packages/fondbot/framework)
7 | [](https://slack.fondbot.io/)
8 |
9 | > **Note:** This repository contains the core code of the FondBot framework. If you want to to build chatbot using FondBot framework, visit the main [FondBot repository](https://github.com/fondbot/fondbot).
10 |
11 | ## About FondBot
12 | FondBot is a framework for building chat bots.
13 |
14 | The main goal of this project is to provide elegant and flexible architecture to develop and maintain chatbot projects from small to the big ones.
15 |
16 | ## Installation And Usage
17 |
18 | You can find all installation instructions and other documentation at https://fondbot.io
19 |
20 | ## Security Vulnerabilities
21 |
22 | If you discover a security vulnerability within FondBot, please send an e-mail to Vladimir Yuldashev at vladimir@fondbot.io. All security vulnerabilities will be promptly addressed.
23 |
24 | ## Community
25 |
26 | If you have questions or suggestions you are welcome to our Slack channel:
27 | [https://slack.fondbot.io](https://slack.fondbot.io)
28 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fondbot/framework",
3 | "description": "FondBot framework.",
4 | "type": "library",
5 | "keywords": [
6 | "fondbot",
7 | "bot",
8 | "bots",
9 | "chatbots",
10 | "telegram",
11 | "facebook messenger",
12 | "vk"
13 | ],
14 | "license": "MIT",
15 | "homepage": "https://fondbot.io",
16 | "authors": [
17 | {
18 | "name": "Vladimir Yuldashev",
19 | "email": "vladimir@fondbot.io"
20 | }
21 | ],
22 | "require": {
23 | "php": "^7.1",
24 | "ext-json": "*",
25 | "guzzlehttp/guzzle": "^6.3",
26 | "laravel/framework": "5.6.*"
27 | },
28 | "require-dev": {
29 | "fzaninotto/faker": "^1.7",
30 | "league/flysystem-memory": "^1.0",
31 | "mockery/mockery": "^1.1",
32 | "orchestra/testbench": "^3.6",
33 | "phpunit/phpunit": "^7.2"
34 | },
35 | "autoload": {
36 | "psr-4": {
37 | "FondBot\\": "src/"
38 | },
39 | "files": [
40 | "src/helpers.php"
41 | ]
42 | },
43 | "autoload-dev": {
44 | "psr-4": {
45 | "FondBot\\Tests\\": "tests/"
46 | }
47 | },
48 | "scripts": {
49 | "test": "vendor/bin/phpunit"
50 | },
51 | "extra": {
52 | "branch-alias": {
53 | "dev-master": "3.0-dev"
54 | }
55 | },
56 | "config": {
57 | "preferred-install": "dist",
58 | "sort-packages": true
59 | },
60 | "minimum-stability": "dev",
61 | "prefer-stable": true
62 | }
63 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 | ./tests/Unit
14 |
15 |
16 |
17 |
18 | ./src
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/resources/stubs/Activator.stub:
--------------------------------------------------------------------------------
1 | name = $name;
16 | $this->driver = $driver;
17 | $this->secret = $secret;
18 | }
19 |
20 | public function getName(): string
21 | {
22 | return $this->name;
23 | }
24 |
25 | public function getDriver(): Driver
26 | {
27 | return $this->driver;
28 | }
29 |
30 | public function getSecret(): ?string
31 | {
32 | return $this->secret;
33 | }
34 |
35 | public function getWebhookUrl(): string
36 | {
37 | return route('fondbot.webhook', [$this->name, $this->secret]);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Channels/ChannelManager.php:
--------------------------------------------------------------------------------
1 | channels = collect([]);
28 | }
29 |
30 | /**
31 | * Register channels.
32 | *
33 | * @param array $channels
34 | */
35 | public function register(array $channels): void
36 | {
37 | $this->channels = collect($channels);
38 | }
39 |
40 | /**
41 | * Get all channels.
42 | *
43 | * @return Collection
44 | */
45 | public function all(): Collection
46 | {
47 | return $this->channels;
48 | }
49 |
50 | /**
51 | * Get channels by driver.
52 | *
53 | * @param string $driver
54 | *
55 | * @return Collection
56 | */
57 | public function getByDriver(string $driver): Collection
58 | {
59 | return $this->channels->filter(function (array $channel) use ($driver) {
60 | return $channel['driver'] === $driver;
61 | });
62 | }
63 |
64 | /**
65 | * Create channel.
66 | *
67 | * @param string $name
68 | *
69 | * @return Channel
70 | * @throws ChannelNotFound
71 | */
72 | public function create(string $name): Channel
73 | {
74 | if (!array_has($this->channels, $name)) {
75 | throw new ChannelNotFound('Channel `'.$name.'` not found.');
76 | }
77 |
78 | $parameters = $this->channels[$name];
79 |
80 | // Create driver and initialize it with channel parameters
81 | $driver = $this->createDriver($parameters['driver']);
82 | $driver->initialize(collect($parameters)->except('driver'));
83 |
84 | return new Channel($name, $driver, $parameters['webhook-secret'] ?? null);
85 | }
86 |
87 | /**
88 | * Get the default driver name.
89 | *
90 | * @return string
91 | */
92 | public function getDefaultDriver(): ?string
93 | {
94 | return null;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/Channels/Chat.php:
--------------------------------------------------------------------------------
1 | id = $id;
19 | $this->title = $title;
20 | $this->type = $type;
21 | }
22 |
23 | public function getId(): string
24 | {
25 | return $this->id;
26 | }
27 |
28 | public function getTitle(): ?string
29 | {
30 | return $this->title;
31 | }
32 |
33 | public function getType(): string
34 | {
35 | return $this->type;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Channels/Driver.php:
--------------------------------------------------------------------------------
1 | templateCompiler = $templateCompiler;
22 | }
23 |
24 | /**
25 | * Get driver short name.
26 | *
27 | * This name is used as an alias for configuration.
28 | *
29 | * @return string
30 | */
31 | public function getShortName(): string
32 | {
33 | return class_basename($this);
34 | }
35 |
36 | /**
37 | * Initialize driver.
38 | *
39 | * @param Collection $parameters
40 | *
41 | * @return Driver|DriverContract|static
42 | */
43 | public function initialize(Collection $parameters): DriverContract
44 | {
45 | $parameters->each(function ($value, $key) {
46 | $key = Str::camel($key);
47 | $this->$key = $value;
48 | });
49 |
50 | $this->client = $this->createClient();
51 |
52 | return $this;
53 | }
54 |
55 | /**
56 | * Get API client.
57 | *
58 | * @return mixed
59 | */
60 | public function getClient()
61 | {
62 | return $this->client;
63 | }
64 |
65 | /**
66 | * Create HTTP response.
67 | *
68 | * @param Request $request
69 | * @param Event $event
70 | *
71 | * @return mixed
72 | */
73 | public function createResponse(Request $request, Event $event)
74 | {
75 | return [];
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Channels/Exceptions/ChannelNotFound.php:
--------------------------------------------------------------------------------
1 | $this->getMessage()]);
14 | }
15 |
16 | public function render()
17 | {
18 | return response();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Channels/Exceptions/DriverNotFound.php:
--------------------------------------------------------------------------------
1 | id = $id;
19 | $this->name = $name;
20 | $this->username = $username;
21 | $this->data = collect($data);
22 | }
23 |
24 | public function getId(): string
25 | {
26 | return $this->id;
27 | }
28 |
29 | public function getName(): ?string
30 | {
31 | return $this->name;
32 | }
33 |
34 | public function getUsername(): ?string
35 | {
36 | return $this->username;
37 | }
38 |
39 | /**
40 | * Additional user information.
41 | *
42 | * @return Collection
43 | */
44 | public function getData(): Collection
45 | {
46 | return $this->data;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Contracts/Channels/Driver.php:
--------------------------------------------------------------------------------
1 | type = $type;
18 | }
19 |
20 | public static function make(string $type = null)
21 | {
22 | return new static($type);
23 | }
24 |
25 | public function file(): self
26 | {
27 | $this->type = Template::TYPE_FILE;
28 |
29 | return $this;
30 | }
31 |
32 | public function image(): self
33 | {
34 | $this->type = Template::TYPE_IMAGE;
35 |
36 | return $this;
37 | }
38 |
39 | public function audio(): self
40 | {
41 | $this->type = Template::TYPE_AUDIO;
42 |
43 | return $this;
44 | }
45 |
46 | public function video(): self
47 | {
48 | $this->type = Template::TYPE_VIDEO;
49 |
50 | return $this;
51 | }
52 |
53 | /**
54 | * Result of matching activator.
55 | *
56 | * @param MessageReceived $message
57 | *
58 | * @return bool
59 | */
60 | public function matches(MessageReceived $message): bool
61 | {
62 | if ($this->type === null) {
63 | return $message->getAttachment() !== null;
64 | }
65 |
66 | return hash_equals($message->getAttachment()->getType(), $this->type);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Conversation/Activators/Contains.php:
--------------------------------------------------------------------------------
1 | toArray();
22 | }
23 |
24 | $this->needles = $needles;
25 | }
26 |
27 | /**
28 | * @param array|string $needles
29 | *
30 | * @return static
31 | */
32 | public static function make($needles)
33 | {
34 | return new static($needles);
35 | }
36 |
37 | /**
38 | * Result of matching activator.
39 | *
40 | * @param MessageReceived $message
41 | *
42 | * @return bool
43 | */
44 | public function matches(MessageReceived $message): bool
45 | {
46 | $text = $message->getText();
47 | if ($text === null) {
48 | return false;
49 | }
50 |
51 | return str_contains($text, (array) $this->needles);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Conversation/Activators/Exact.php:
--------------------------------------------------------------------------------
1 | value = $value;
19 | }
20 |
21 | public static function make(string $value)
22 | {
23 | return new static($value);
24 | }
25 |
26 | public function caseSensitive(): self
27 | {
28 | $this->caseSensitive = true;
29 |
30 | return $this;
31 | }
32 |
33 | /**
34 | * Result of matching activator.
35 | *
36 | * @param MessageReceived $message
37 | *
38 | * @return bool
39 | */
40 | public function matches(MessageReceived $message): bool
41 | {
42 | $text = $message->getText();
43 |
44 | if ($text === null) {
45 | return false;
46 | }
47 |
48 | if (!$this->caseSensitive) {
49 | $text = Str::lower($text);
50 | $this->value = Str::lower($this->value);
51 | }
52 |
53 | return hash_equals($this->value, $text);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Conversation/Activators/In.php:
--------------------------------------------------------------------------------
1 | toArray();
24 | }
25 |
26 | $this->values = $values;
27 | }
28 |
29 | /**
30 | * @param array|Collection $values
31 | *
32 | * @return static
33 | */
34 | public static function make($values)
35 | {
36 | return new static($values);
37 | }
38 |
39 | /**
40 | * Result of matching activator.
41 | *
42 | * @param MessageReceived $message
43 | *
44 | * @return bool
45 | */
46 | public function matches(MessageReceived $message): bool
47 | {
48 | $haystack = $this->values;
49 |
50 | if ($haystack instanceof Collection) {
51 | $haystack = $haystack->toArray();
52 | }
53 |
54 | return in_array($message->getText(), $haystack, false);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Conversation/Activators/Payload.php:
--------------------------------------------------------------------------------
1 | value = $value;
17 | }
18 |
19 | public static function make(string $value)
20 | {
21 | return new static($value);
22 | }
23 |
24 | /**
25 | * Result of matching activator.
26 | *
27 | * @param MessageReceived $message
28 | *
29 | * @return bool
30 | */
31 | public function matches(MessageReceived $message): bool
32 | {
33 | return $message->getData() ? hash_equals($this->value, $message->getData()) : false;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Conversation/Activators/Regex.php:
--------------------------------------------------------------------------------
1 | toArray();
20 | }
21 |
22 | $this->patterns = $patterns;
23 | }
24 |
25 | public static function make($patterns)
26 | {
27 | return new static($patterns);
28 | }
29 |
30 | /**
31 | * Result of matching activator.
32 | *
33 | * @param MessageReceived $message
34 | *
35 | * @return bool
36 | */
37 | public function matches(MessageReceived $message): bool
38 | {
39 | return str_is($this->patterns, $message->getText());
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Conversation/Concerns/Authorization.php:
--------------------------------------------------------------------------------
1 | authorize($message);
22 | }
23 |
24 | return true;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Conversation/Concerns/InteractsWithContext.php:
--------------------------------------------------------------------------------
1 | context()->getChannel();
22 | }
23 |
24 | /**
25 | * Get chat.
26 | *
27 | * @return Chat
28 | */
29 | protected function getChat(): Chat
30 | {
31 | return $this->context()->getChat();
32 | }
33 |
34 | /**
35 | * Get user.
36 | *
37 | * @return User
38 | */
39 | protected function getUser(): User
40 | {
41 | return $this->context()->getUser();
42 | }
43 |
44 | /**
45 | * Get the whole context or a single value.
46 | *
47 | * @param string|null $key
48 | * @param mixed $default
49 | *
50 | * @return Context|mixed
51 | */
52 | protected function context(string $key = null, $default = null)
53 | {
54 | return context($key, $default);
55 | }
56 |
57 | /**
58 | * Remember value in context.
59 | *
60 | * @param string $key
61 | * @param mixed $value
62 | */
63 | protected function remember(string $key, $value): void
64 | {
65 | context()->setItem($key, $value);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Conversation/Concerns/SendsMessages.php:
--------------------------------------------------------------------------------
1 | getChannel(),
23 | context()->getChat(),
24 | context()->getUser()
25 | ))->text($text);
26 | }
27 |
28 | /**
29 | * Send attachment to user.
30 | *
31 | * @param Attachment $attachment
32 | *
33 | * @return PendingReply
34 | */
35 | protected function sendAttachment(Attachment $attachment): PendingReply
36 | {
37 | return (new PendingReply(
38 | context()->getChannel(),
39 | context()->getChat(),
40 | context()->getUser()
41 | ))->attachment($attachment);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Conversation/Context.php:
--------------------------------------------------------------------------------
1 | channel = $channel;
25 | $this->chat = $chat;
26 | $this->user = $user;
27 | $this->items = collect($items);
28 | }
29 |
30 | public function getChannel(): Channel
31 | {
32 | return $this->channel;
33 | }
34 |
35 | public function getChat(): Chat
36 | {
37 | return $this->chat;
38 | }
39 |
40 | public function getUser(): User
41 | {
42 | return $this->user;
43 | }
44 |
45 | public function getIntent(): ?Intent
46 | {
47 | return $this->intent;
48 | }
49 |
50 | public function setIntent(Intent $intent): Context
51 | {
52 | $this->intent = $intent;
53 |
54 | return $this;
55 | }
56 |
57 | public function getInteraction(): ?Interaction
58 | {
59 | return $this->interaction;
60 | }
61 |
62 | public function setInteraction(?Interaction $interaction): Context
63 | {
64 | $this->interaction = $interaction;
65 |
66 | return $this;
67 | }
68 |
69 | public function getItem(string $key, $default = null)
70 | {
71 | return $this->items->get($key, $default);
72 | }
73 |
74 | public function setItem(string $key, $value): Context
75 | {
76 | $this->items->put($key, $value);
77 |
78 | return $this;
79 | }
80 |
81 | public function incrementAttempts(): Context
82 | {
83 | $this->attempts++;
84 |
85 | return $this;
86 | }
87 |
88 | public function attempts(): int
89 | {
90 | return $this->attempts;
91 | }
92 |
93 | public function toArray(): array
94 | {
95 | return [
96 | 'intent' => $this->intent ? get_class($this->intent) : null,
97 | 'interaction' => $this->interaction ? get_class($this->interaction) : null,
98 | 'items' => $this->items->toArray(),
99 | 'attempts' => $this->attempts,
100 | ];
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/Conversation/ConversationManager.php:
--------------------------------------------------------------------------------
1 | application = $application;
33 | $this->cache = $cache;
34 | }
35 |
36 | /** {@inheritdoc} */
37 | public function registerIntent(string $class): void
38 | {
39 | $this->intents[] = $class;
40 | }
41 |
42 | /** {@inheritdoc} */
43 | public function registerFallbackIntent(string $class): void
44 | {
45 | $this->fallbackIntent = $class;
46 | }
47 |
48 | /** {@inheritdoc} */
49 | public function getIntents(): array
50 | {
51 | return $this->intents;
52 | }
53 |
54 | /** {@inheritdoc} */
55 | public function matchIntent(MessageReceived $messageReceived): ?Intent
56 | {
57 | foreach ($this->intents as $intent) {
58 | /** @var Intent $intent */
59 | $intent = resolve($intent);
60 |
61 | foreach ($intent->activators() as $activator) {
62 | if (!$intent->passesAuthorization($messageReceived)) {
63 | continue;
64 | }
65 |
66 | if ($activator instanceof Closure && value($activator($messageReceived)) === true) {
67 | return $intent;
68 | }
69 |
70 | if ($activator instanceof Activator && $activator->matches($messageReceived)) {
71 | return $intent;
72 | }
73 | }
74 | }
75 |
76 | // Otherwise, return fallback intent
77 | return resolve($this->fallbackIntent);
78 | }
79 |
80 | /** {@inheritdoc} */
81 | public function resolveContext(Channel $channel, Chat $chat, User $user): Context
82 | {
83 | $value = $this->cache->get($this->getCacheKeyForContext($channel, $chat, $user), [
84 | 'chat' => $chat,
85 | 'user' => $user,
86 | 'intent' => null,
87 | 'interaction' => null,
88 | 'items' => [],
89 | ]);
90 |
91 | $context = new Context($channel, $chat, $user, $value['items'] ?? []);
92 |
93 | if (isset($value['intent'])) {
94 | $context->setIntent(resolve($value['intent']));
95 | }
96 |
97 | if (isset($value['interaction'])) {
98 | $context->setInteraction(resolve($value['interaction']));
99 | }
100 |
101 | // Bind resolved instance to the container
102 | $this->application->instance('fondbot.conversation.context', $context);
103 |
104 | return $context;
105 | }
106 |
107 | /** {@inheritdoc} */
108 | public function saveContext(Context $context): void
109 | {
110 | $this->cache->forever(
111 | $this->getCacheKeyForContext($context->getChannel(), $context->getChat(), $context->getUser()),
112 | $context->toArray()
113 | );
114 | }
115 |
116 | /** {@inheritdoc} */
117 | public function flushContext(Context $context): void
118 | {
119 | $this->cache->forget(
120 | $this->getCacheKeyForContext($context->getChannel(), $context->getChat(), $context->getUser())
121 | );
122 | }
123 |
124 | /** {@inheritdoc} */
125 | public function getContext(): ?Context
126 | {
127 | if (!$this->application->has('fondbot.conversation.context')) {
128 | return null;
129 | }
130 |
131 | return $this->application->get('fondbot.conversation.context');
132 | }
133 |
134 | /** {@inheritdoc} */
135 | public function setReceivedMessage(MessageReceived $messageReceived): void
136 | {
137 | $this->messageReceived = $messageReceived;
138 | }
139 |
140 | /** {@inheritdoc} */
141 | public function markAsTransitioned(): void
142 | {
143 | $this->transitioned = true;
144 | }
145 |
146 | /** {@inheritdoc} */
147 | public function transitioned(): bool
148 | {
149 | return $this->transitioned;
150 | }
151 |
152 | /** {@inheritdoc} */
153 | public function converse(Conversable $conversable): void
154 | {
155 | context()->incrementAttempts();
156 |
157 | if ($conversable instanceof Intent) {
158 | context()->setIntent($conversable)->setInteraction(null);
159 | }
160 |
161 | $conversable->handle($this->messageReceived);
162 | }
163 |
164 | /** {@inheritdoc} */
165 | public function restartInteraction(Interaction $interaction): void
166 | {
167 | context()->setInteraction(null);
168 |
169 | $this->converse($interaction);
170 |
171 | $this->markAsTransitioned();
172 | }
173 |
174 | public function __destruct()
175 | {
176 | $context = $this->getContext();
177 |
178 | if ($context === null) {
179 | return;
180 | }
181 |
182 | // Close session if conversation has not been transitioned
183 | if (!$this->transitioned()) {
184 | $this->flushContext($context);
185 | }
186 |
187 | // Save context if exists
188 | if ($this->transitioned() && $context = context()) {
189 | $this->saveContext($context);
190 | }
191 | }
192 |
193 | private function getCacheKeyForContext(Channel $channel, Chat $chat, User $user): string
194 | {
195 | return implode('.', ['context', $channel->getName(), $chat->getId(), $user->getId()]);
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/src/Conversation/FallbackIntent.php:
--------------------------------------------------------------------------------
1 | random();
29 |
30 | $this->reply($text);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Conversation/Intent.php:
--------------------------------------------------------------------------------
1 | run($message);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Conversation/Interaction.php:
--------------------------------------------------------------------------------
1 | converse(resolve(static::class));
42 | $conversation->markAsTransitioned();
43 | }
44 |
45 | /**
46 | * Restart current interaction.
47 | */
48 | protected function restart(): void
49 | {
50 | /** @var Manager $conversation */
51 | $conversation = resolve(Manager::class);
52 | $conversation->restartInteraction($this);
53 | }
54 |
55 | /**
56 | * Handle interaction.
57 | *
58 | * @param MessageReceived $message
59 | */
60 | public function handle(MessageReceived $message): void
61 | {
62 | $context = context();
63 |
64 | if ($context->getInteraction() instanceof $this) {
65 | $this->process($message);
66 | } else {
67 | $context->setInteraction($this);
68 | $this->run($message);
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/Drivers/PendingReply.php:
--------------------------------------------------------------------------------
1 | channel = $channel;
28 | $this->chat = $chat;
29 | $this->user = $user;
30 | }
31 |
32 | /**
33 | * Set reply text.
34 | *
35 | * @param null|string $text
36 | *
37 | * @return static
38 | */
39 | public function text(?string $text)
40 | {
41 | $this->text = $text;
42 |
43 | return $this;
44 | }
45 |
46 | /**
47 | * Set template for reply.
48 | *
49 | * @param Template|null $template
50 | *
51 | * @return static
52 | */
53 | public function template(?Template $template)
54 | {
55 | $this->template = $template;
56 |
57 | return $this;
58 | }
59 |
60 | /**
61 | * Set attachment for send.
62 | *
63 | * @param Attachment $attachment
64 | *
65 | * @return static
66 | */
67 | public function attachment(Attachment $attachment)
68 | {
69 | $this->attachment = $attachment;
70 |
71 | return $this;
72 | }
73 |
74 | /**
75 | * Set the desired delay for the job.
76 | *
77 | * @param \DateTime|int|null $delay
78 | *
79 | * @return static
80 | */
81 | public function delay($delay)
82 | {
83 | $this->delay = $delay;
84 |
85 | return $this;
86 | }
87 |
88 | public function __destruct()
89 | {
90 | if ($this->text) {
91 | SendMessage::dispatch(
92 | $this->channel,
93 | $this->chat,
94 | $this->user,
95 | $this->text,
96 | $this->template
97 | )->delay($this->delay);
98 | }
99 |
100 | if ($this->attachment) {
101 | SendAttachment::dispatch(
102 | $this->channel,
103 | $this->chat,
104 | $this->user,
105 | $this->attachment
106 | )->delay($this->delay);
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/Drivers/TemplateCompiler.php:
--------------------------------------------------------------------------------
1 | getName());
32 | if (!method_exists($this, $method)) {
33 | return null;
34 | }
35 |
36 | return $this->$method($template);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Events/Event.php:
--------------------------------------------------------------------------------
1 | chat = $chat;
32 | $this->from = $from;
33 | $this->text = $text;
34 | $this->location = $location;
35 | $this->attachment = $attachment;
36 | $this->data = $data;
37 | $this->raw = $raw;
38 | }
39 |
40 | public function getChat(): Chat
41 | {
42 | return $this->chat;
43 | }
44 |
45 | public function getFrom(): User
46 | {
47 | return $this->from;
48 | }
49 |
50 | public function getText(): string
51 | {
52 | return $this->text;
53 | }
54 |
55 | public function getLocation(): Location
56 | {
57 | return $this->location;
58 | }
59 |
60 | public function getAttachment(): ?Attachment
61 | {
62 | return $this->attachment;
63 | }
64 |
65 | public function getData(): ?string
66 | {
67 | return $this->data;
68 | }
69 |
70 | public function getRaw()
71 | {
72 | return $this->raw;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Events/Unknown.php:
--------------------------------------------------------------------------------
1 | client = $client;
19 | }
20 |
21 | /**
22 | * Get all available drivers.
23 | *
24 | * @return Collection
25 | */
26 | public function getDrivers(): Collection
27 | {
28 | $response = $this->client->get(self::URL.'/drivers', ['json' => ['version' => Kernel::VERSION]]);
29 |
30 | return collect(json_decode((string) $response->getBody(), true));
31 | }
32 |
33 | /**
34 | * Find driver by name.
35 | *
36 | * @param string $name
37 | *
38 | * @return array|null
39 | */
40 | public function findDriver(string $name): ?array
41 | {
42 | return $this->getDrivers()->first(function ($item) use ($name) {
43 | return $item['name'] === $name;
44 | });
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Foundation/Commands/SendAttachment.php:
--------------------------------------------------------------------------------
1 | channel = $channel;
28 | $this->chat = $chat;
29 | $this->recipient = $recipient;
30 | $this->attachment = $attachment;
31 | }
32 |
33 | public function handle(): void
34 | {
35 | $driver = $this->channel->getDriver();
36 | $driver->sendAttachment($this->chat, $this->recipient, $this->attachment);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Foundation/Commands/SendMessage.php:
--------------------------------------------------------------------------------
1 | channel = $channel;
39 | $this->chat = $chat;
40 | $this->recipient = $recipient;
41 | $this->text = $text;
42 | $this->template = $template;
43 | }
44 |
45 | public function handle(): void
46 | {
47 | $driver = $this->channel->getDriver();
48 | $driver->sendMessage($this->chat, $this->recipient, $this->text, $this->template);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Foundation/Composer.php:
--------------------------------------------------------------------------------
1 | getProcess()
22 | ->setCommandLine($this->findComposer().' require '.$package)
23 | ->run($callback);
24 | }
25 |
26 | /**
27 | * Determine if package already installed.
28 | *
29 | * @param string $package
30 | *
31 | * @return bool
32 | * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
33 | */
34 | public function installed(string $package): bool
35 | {
36 | $manifest = $this->files->get('composer.json');
37 | $manifest = json_decode($manifest, true);
38 |
39 | return collect($manifest['require'])
40 | ->merge($manifest['require-dev'])
41 | ->keys()
42 | ->contains($package);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Foundation/Controller.php:
--------------------------------------------------------------------------------
1 | getChannel()->getDriver();
21 |
22 | // If driver supports webhook verification
23 | // We need to check if current request belongs to verification process
24 | if ($driver instanceof WebhookVerification && $driver->isVerificationRequest($request)) {
25 | return $driver->verifyWebhook($request);
26 | }
27 |
28 | // Resolve event from driver and dispatch it
29 | $events->dispatch(
30 | $event = $driver->createEvent($request)
31 | );
32 |
33 | return $driver->createResponse($request, $event);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Foundation/Kernel.php:
--------------------------------------------------------------------------------
1 | channel = $channel;
24 | }
25 |
26 | /**
27 | * Get current channel.
28 | *
29 | * @return Channel|null
30 | */
31 | public function getChannel(): ?Channel
32 | {
33 | return $this->channel;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Foundation/Listeners/HandleConversation.php:
--------------------------------------------------------------------------------
1 | kernel = $kernel;
23 | $this->conversation = $conversation;
24 | }
25 |
26 | public function handle(MessageReceived $messageReceived): void
27 | {
28 | /** @var Context $context */
29 | $context = $this->conversation->resolveContext(
30 | $this->kernel->getChannel(),
31 | $messageReceived->getChat(),
32 | $messageReceived->getFrom()
33 | );
34 |
35 | // If there is no interaction in session
36 | // Try to match intent and run it
37 | // Otherwise, run interaction
38 | if (!$this->isInConversation($context)) {
39 | $conversable = $this->conversation->matchIntent($messageReceived);
40 | } else {
41 | $conversable = $context->getInteraction();
42 | }
43 |
44 | $this->conversation->setReceivedMessage($messageReceived);
45 | $this->conversation->converse($conversable);
46 | }
47 |
48 | /**
49 | * Determine if conversation started.
50 | *
51 | * @param Context $context
52 | *
53 | * @return bool
54 | */
55 | private function isInConversation(Context $context): bool
56 | {
57 | return $context->getInteraction() !== null;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Foundation/Middleware/InitializeKernel.php:
--------------------------------------------------------------------------------
1 | kernel = $kernel;
21 | $this->channelManager = $channelManager;
22 | }
23 |
24 | public function handle(Request $request, Closure $next)
25 | {
26 | $channel = $this->resolveChannel($request->route('channel'));
27 |
28 | if ($channel->getSecret() !== null && $request->route('secret') !== $channel->getSecret()) {
29 | abort(403);
30 | }
31 |
32 | $this->kernel->initialize($channel);
33 |
34 | return $next($request);
35 | }
36 |
37 | private function resolveChannel($value): Channel
38 | {
39 | if (is_string($value)) {
40 | $value = $this->channelManager->create($value);
41 | }
42 |
43 | return $value;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Foundation/Providers/ChannelServiceProvider.php:
--------------------------------------------------------------------------------
1 | registerManager();
23 | }
24 |
25 | /**
26 | * Boot application services.
27 | */
28 | public function boot(): void
29 | {
30 | /** @var ChannelManager $manager */
31 | $manager = $this->app[Manager::class];
32 |
33 | $manager->register(
34 | collect($this->channels())
35 | ->mapWithKeys(function (array $parameters, string $name) {
36 | return [$name => $parameters];
37 | })
38 | ->toArray()
39 | );
40 | }
41 |
42 | /**
43 | * Define bot channels.
44 | *
45 | * @return array
46 | */
47 | protected function channels(): array
48 | {
49 | return [];
50 | }
51 |
52 | private function registerManager(): void
53 | {
54 | $this->app->singleton(Manager::class, function () {
55 | return new ChannelManager($this->app);
56 | });
57 |
58 | $this->app->alias(Manager::class, ChannelManager::class);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Foundation/Providers/ConversationServiceProvider.php:
--------------------------------------------------------------------------------
1 | registerManager();
33 | }
34 |
35 | /**
36 | * Boot application services.
37 | */
38 | public function boot(): void
39 | {
40 | /** @var Manager $manager */
41 | $manager = $this->app['conversation'];
42 |
43 | foreach ($this->intents as $intent) {
44 | $manager->registerIntent($intent);
45 | }
46 |
47 | $manager->registerFallbackIntent($this->fallbackIntent);
48 |
49 | if (method_exists($this, 'configure')) {
50 | $this->configure();
51 | }
52 | }
53 |
54 | /**
55 | * Load intents from path.
56 | *
57 | * @param string $path
58 | */
59 | protected function load(string $path): void
60 | {
61 | $namespace = $this->app->getNamespace();
62 |
63 | /** @var SplFileInfo[] $files */
64 | $files = (new Finder)->in($path)->files();
65 |
66 | foreach ($files as $file) {
67 | $file = $namespace.str_replace(
68 | ['/', '.php'],
69 | ['\\', ''],
70 | Str::after($file->getPathname(), app_path().DIRECTORY_SEPARATOR)
71 | );
72 |
73 | if (is_subclass_of($file, Intent::class) && !(new ReflectionClass($file))->isAbstract()) {
74 | $this->app['conversation']->registerIntent($file);
75 | }
76 | }
77 | }
78 |
79 | private function registerManager(): void
80 | {
81 | $this->app->singleton('conversation', function () {
82 | return new ConversationManager($this->app, $this->app[Cache::class]);
83 | });
84 |
85 | $this->app->alias('conversation', Manager::class);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/Foundation/Providers/EventServiceProvider.php:
--------------------------------------------------------------------------------
1 | app['events'];
25 |
26 | $events->listen(MessageReceived::class, HandleConversation::class);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Foundation/Providers/FoundationServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->singleton(Kernel::class, function () {
15 | return new Kernel;
16 | });
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Foundation/Providers/RouteServiceProvider.php:
--------------------------------------------------------------------------------
1 | 'fondbot.webhook'], function () {
19 | Route::get('webhook/{channel}/{secret?}', 'FondBot\Foundation\Controller@webhook')->name('fondbot.webhook');
20 | Route::post('webhook/{channel}/{secret?}', 'FondBot\Foundation\Controller@webhook')->name('fondbot.webhook');
21 | });
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Foundation/ServiceProvider.php:
--------------------------------------------------------------------------------
1 | setName('FondBot Framework');
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Framework/Console/Kernel.php:
--------------------------------------------------------------------------------
1 | artisan === null) {
15 | return $this->artisan = (new Application($this->app, $this->events, $this->app->version()))
16 | ->resolveCommands($this->commands);
17 | }
18 |
19 | return $this->artisan;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Framework/Exceptions/Handler.php:
--------------------------------------------------------------------------------
1 | routeIs('fondbot.webhook')) {
23 | return 'Something went wrong.';
24 | }
25 |
26 | return parent::render($request, $e);
27 | }
28 |
29 | /** {@inheritdoc} */
30 | protected function renderHttpException(HttpException $e): Response
31 | {
32 | return $this->convertExceptionToResponse($e);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Framework/Http/Kernel.php:
--------------------------------------------------------------------------------
1 | [
14 | InitializeKernel::class,
15 | ],
16 | ];
17 | }
18 |
--------------------------------------------------------------------------------
/src/Templates/Attachment.php:
--------------------------------------------------------------------------------
1 | type = $type;
23 | $this->path = $path;
24 | $this->parameters = collect($parameters);
25 | }
26 |
27 | public static function make(string $type, string $path, array $parameters = [])
28 | {
29 | return new static($type, $path, $parameters);
30 | }
31 |
32 | public static function file(string $path, array $parameters = [])
33 | {
34 | return new static(self::TYPE_FILE, $path, $parameters);
35 | }
36 |
37 | public static function image(string $path, array $parameters = [])
38 | {
39 | return new static(self::TYPE_IMAGE, $path, $parameters);
40 | }
41 |
42 | public static function audio(string $path, array $parameters = [])
43 | {
44 | return new static(self::TYPE_AUDIO, $path, $parameters);
45 | }
46 |
47 | public static function video(string $path, array $parameters = [])
48 | {
49 | return new static(self::TYPE_VIDEO, $path, $parameters);
50 | }
51 |
52 | public function getType(): string
53 | {
54 | return $this->type;
55 | }
56 |
57 | public function setType(string $type): Attachment
58 | {
59 | $this->type = $type;
60 |
61 | return $this;
62 | }
63 |
64 | public function getPath(): string
65 | {
66 | return $this->path;
67 | }
68 |
69 | public function setPath(string $path): Attachment
70 | {
71 | $this->path = $path;
72 |
73 | return $this;
74 | }
75 |
76 | public function getParameters(): Collection
77 | {
78 | return $this->parameters;
79 | }
80 |
81 | public function setParameters(array $parameters): Attachment
82 | {
83 | $this->parameters = collect($parameters);
84 |
85 | return $this;
86 | }
87 |
88 | public static function possibleTypes(): array
89 | {
90 | return [
91 | static::TYPE_FILE,
92 | static::TYPE_IMAGE,
93 | static::TYPE_AUDIO,
94 | static::TYPE_VIDEO,
95 | ];
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/Templates/Keyboard.php:
--------------------------------------------------------------------------------
1 | buttons = $buttons;
24 | $this->parameters = collect($parameters);
25 | }
26 |
27 | /**
28 | * @param Button[] $buttons
29 | * @param array $parameters
30 | *
31 | * @return static
32 | */
33 | public static function make(array $buttons = [], array $parameters = [])
34 | {
35 | return new static($buttons, $parameters);
36 | }
37 |
38 | public function getName(): string
39 | {
40 | return 'Keyboard';
41 | }
42 |
43 | /**
44 | * @return Button[]
45 | */
46 | public function getButtons(): array
47 | {
48 | return $this->buttons;
49 | }
50 |
51 | public function addButton(Button $button): Keyboard
52 | {
53 | $this->buttons[] = $button;
54 |
55 | return $this;
56 | }
57 |
58 | public function getParameters(): Collection
59 | {
60 | return $this->parameters;
61 | }
62 |
63 | public function setParameters(array $parameters): Keyboard
64 | {
65 | $this->parameters = $parameters;
66 |
67 | return $this;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Templates/Keyboard/Button.php:
--------------------------------------------------------------------------------
1 | label = $label;
18 | $this->parameters = collect($parameters);
19 | }
20 |
21 | /**
22 | * Get name.
23 | *
24 | * @return string
25 | */
26 | public function getName(): string
27 | {
28 | return class_basename($this);
29 | }
30 |
31 | /**
32 | * Get label.
33 | *
34 | * @return string
35 | */
36 | public function getLabel(): ?string
37 | {
38 | return $this->label;
39 | }
40 |
41 | /**
42 | * Set label.
43 | *
44 | * @param string $label
45 | *
46 | * @return static
47 | */
48 | public function setLabel(string $label): Button
49 | {
50 | $this->label = $label;
51 |
52 | return $this;
53 | }
54 |
55 | /**
56 | * Get button parameters.
57 | *
58 | * @return Collection
59 | */
60 | public function getParameters(): Collection
61 | {
62 | return $this->parameters;
63 | }
64 |
65 | /**
66 | * Set parameters.
67 | *
68 | * @param array $parameters
69 | *
70 | * @return static
71 | */
72 | public function setParameters(array $parameters): Button
73 | {
74 | $this->parameters = collect($parameters);
75 |
76 | return $this;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/Templates/Keyboard/PayloadButton.php:
--------------------------------------------------------------------------------
1 | payload = $payload;
16 | }
17 |
18 | /**
19 | * Make a new payload button instance.
20 | *
21 | * @param string $label
22 | * @param mixed $payload
23 | * @param array $parameters
24 | *
25 | * @return static
26 | */
27 | public static function make(string $label, $payload, array $parameters = [])
28 | {
29 | return new static($label, $payload, $parameters);
30 | }
31 |
32 | /**
33 | * Get payload.
34 | *
35 | * @return mixed
36 | */
37 | public function getPayload()
38 | {
39 | return $this->payload;
40 | }
41 |
42 | /**
43 | * Set payload.
44 | *
45 | * @param mixed $payload
46 | *
47 | * @return PayloadButton
48 | */
49 | public function setPayload($payload): PayloadButton
50 | {
51 | $this->payload = $payload;
52 |
53 | return $this;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Templates/Keyboard/ReplyButton.php:
--------------------------------------------------------------------------------
1 | url = $url;
16 | }
17 |
18 | public static function make(string $label, string $url, array $parameters = [])
19 | {
20 | return new static($label, $url, $parameters);
21 | }
22 |
23 | /**
24 | * Get URL.
25 | *
26 | * @return string
27 | */
28 | public function getUrl(): string
29 | {
30 | return $this->url;
31 | }
32 |
33 | /**
34 | * Set URL.
35 | *
36 | * @param mixed $url
37 | *
38 | * @return UrlButton
39 | */
40 | public function setUrl($url): UrlButton
41 | {
42 | $this->url = $url;
43 |
44 | return $this;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Templates/Location.php:
--------------------------------------------------------------------------------
1 | latitude = $latitude;
18 | $this->longitude = $longitude;
19 | $this->parameters = collect($parameters);
20 | }
21 |
22 | public static function make(float $latitude, float $longitude, array $parameters = [])
23 | {
24 | return new static($latitude, $longitude, $parameters);
25 | }
26 |
27 | public function getLatitude(): float
28 | {
29 | return $this->latitude;
30 | }
31 |
32 | public function setLatitude(float $latitude): Location
33 | {
34 | $this->latitude = $latitude;
35 |
36 | return $this;
37 | }
38 |
39 | public function getLongitude(): float
40 | {
41 | return $this->longitude;
42 | }
43 |
44 | public function setLongitude(float $longitude): Location
45 | {
46 | $this->longitude = $longitude;
47 |
48 | return $this;
49 | }
50 |
51 | public function getParameters(): Collection
52 | {
53 | return $this->parameters;
54 | }
55 |
56 | public function setParameters(array $parameters): Location
57 | {
58 | $this->parameters = collect($parameters);
59 |
60 | return $this;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Toolbelt/InstallDriverCommand.php:
--------------------------------------------------------------------------------
1 | argument('name');
23 | $driver = $api->findDriver($this->argument('name'));
24 |
25 | if ($driver === null) {
26 | $this->error('"'.$name.'" is not found in the available drivers list or is not yet supported by current FondBot version ('.Kernel::VERSION.').');
27 |
28 | exit(0);
29 | }
30 |
31 | if ($composer->installed($driver['package'])) {
32 | $this->error('Driver is already installed.');
33 |
34 | return;
35 | }
36 |
37 | // Install driver
38 | $this->info('Installing driver...');
39 |
40 | $result = $composer->install($driver['package'], function ($_, $line) use (&$output) {
41 | $output .= $line;
42 | });
43 |
44 | if ($result !== 0) {
45 | $this->error($output);
46 | exit($result);
47 | }
48 |
49 | $this->info('Driver installed. ✔');
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Toolbelt/ListChannelsCommand.php:
--------------------------------------------------------------------------------
1 | all())
18 | ->transform(function ($item, $name) use ($manager) {
19 | return [$name, $manager->driver($item['driver'])->getName(), $manager->create($name)->getWebhookUrl()];
20 | })
21 | ->toArray();
22 |
23 | $this->table(['Name', 'Driver', 'Webhook URL'], $rows);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Toolbelt/ListDriversCommand.php:
--------------------------------------------------------------------------------
1 | getDrivers())->keys()->toArray();
21 | $availableDrivers = $api->getDrivers();
22 |
23 | $rows = collect($availableDrivers)
24 | ->transform(function ($item) use ($installedDrivers) {
25 | return [
26 | $item['name'],
27 | $item['package'],
28 | $item['official'] ? '✅' : '❌',
29 | in_array($item['name'], $installedDrivers, true) ? '✅' : '❌',
30 | ];
31 | })
32 | ->toArray();
33 |
34 | $this->table(['Name', 'Package', 'Official', 'Installed'], $rows);
35 | } catch (ClientException $exception) {
36 | $this->error('Connection to FondBot API failed. Please check your internet connection and try again.');
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Toolbelt/ListIntentsCommand.php:
--------------------------------------------------------------------------------
1 | getIntents())
18 | ->transform(function ($item) {
19 | return [$item];
20 | })
21 | ->toArray();
22 |
23 | $this->table(['Class'], $rows);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Toolbelt/MakeActivatorCommand.php:
--------------------------------------------------------------------------------
1 | laravel['path'].'/Activators/'.$name.'.php';
45 | }
46 |
47 | /**
48 | * Get the full namespace for a given class, without the class name.
49 | *
50 | * @param string $name
51 | * @return string
52 | */
53 | protected function getNamespace($name): string
54 | {
55 | return 'Activators';
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Toolbelt/MakeIntentCommand.php:
--------------------------------------------------------------------------------
1 | laravel['path'].'/Intents/'.$name.'.php';
31 | }
32 |
33 | /** {@inheritdoc} */
34 | protected function getNamespace($name): string
35 | {
36 | return 'Intents';
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Toolbelt/MakeInteractionCommand.php:
--------------------------------------------------------------------------------
1 | laravel['path'].'/Interactions/'.$name.'.php';
31 | }
32 |
33 | /** {@inheritdoc} */
34 | protected function getNamespace($name): string
35 | {
36 | return 'Interactions';
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Toolbelt/ToolbeltServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->runningInConsole()) {
17 | $this->commands([
18 | MakeIntentCommand::class,
19 | MakeInteractionCommand::class,
20 | MakeActivatorCommand::class,
21 | ListDriversCommand::class,
22 | InstallDriverCommand::class,
23 | ListChannelsCommand::class,
24 | ListIntentsCommand::class,
25 | ]);
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/helpers.php:
--------------------------------------------------------------------------------
1 | getContext();
35 |
36 | if ($key === null) {
37 | return $context;
38 | }
39 |
40 | return optional($context)->getItem($key, $default);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/Mocks/FakeChannel.php:
--------------------------------------------------------------------------------
1 | getText() === 'foo';
22 | },
23 | ];
24 | }
25 |
26 | /**
27 | * Run intent.
28 | *
29 | * @param MessageReceived $message
30 | */
31 | public function run(MessageReceived $message): void
32 | {
33 | // TODO: Implement run() method.
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/Mocks/FakeInteraction.php:
--------------------------------------------------------------------------------
1 | kernel = $this->mock(Kernel::class);
28 | }
29 |
30 | protected function getPackageProviders($app): array
31 | {
32 | return [
33 | ServiceProvider::class,
34 | ChannelServiceProvider::class,
35 | ConversationServiceProvider::class,
36 | ];
37 | }
38 |
39 | protected function setContext(Context $context)
40 | {
41 | $this->app->instance('fondbot.conversation.context', $context);
42 |
43 | return $this;
44 | }
45 |
46 | /**
47 | * @param string $class
48 | *
49 | * @param array $args
50 | *
51 | * @return mixed|Mockery\Mock
52 | */
53 | protected function mock(string $class, array $args = null)
54 | {
55 | if ($args !== null) {
56 | $instance = Mockery::mock($class, $args);
57 | } else {
58 | $instance = Mockery::mock($class);
59 | }
60 |
61 | $this->app->instance($class, $instance);
62 |
63 | return $instance;
64 | }
65 |
66 | protected function faker(): Generator
67 | {
68 | return Factory::create();
69 | }
70 |
71 | protected function fakeChat(): Chat
72 | {
73 | return new Chat($this->faker()->uuid, $this->faker()->word);
74 | }
75 |
76 | protected function fakeUser(): User
77 | {
78 | return new User($this->faker()->uuid, $this->faker()->name, $this->faker()->userName);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/tests/Unit/Channels/ChannelManagerTest.php:
--------------------------------------------------------------------------------
1 | 'fake',
19 | 'token' => $this->faker()->sha1,
20 | ];
21 | $driver = new FakeDriver;
22 |
23 | $manager = new ChannelManager($this->app);
24 | $manager->extend('fake', function () use (&$driver) {
25 | return $driver;
26 | });
27 | $manager->register([$name => $parameters]);
28 |
29 | $result = $manager->create($name);
30 |
31 | $this->assertInstanceOf(Channel::class, $result);
32 | $this->assertSame($name, $result->getName());
33 | $this->assertSame($driver, $result->getDriver());
34 | }
35 |
36 | public function testAll(): void
37 | {
38 | $manager = new ChannelManager($this->app);
39 | $manager->register(['foo' => ['foo' => 'bar']]);
40 |
41 | $this->assertEquals(collect(['foo' => ['foo' => 'bar']]), $manager->all());
42 | }
43 |
44 | public function testGetByDriver(): void
45 | {
46 | $manager = new ChannelManager($this->app);
47 | $manager->register(['foo' => ['driver' => 'foo'], 'bar' => ['driver' => FakeDriver::class]]);
48 |
49 | $this->assertEquals(collect(['foo' => ['driver' => 'foo']]), $manager->getByDriver('foo'));
50 | $this->assertEquals(collect(['bar' => ['driver' => FakeDriver::class]]), $manager->getByDriver(FakeDriver::class));
51 | }
52 |
53 | /**
54 | * @expectedException \FondBot\Channels\Exceptions\ChannelNotFound
55 | * @expectedExceptionMessage Channel `fake` not found.
56 | */
57 | public function testCreateException(): void
58 | {
59 | $manager = new ChannelManager($this->app);
60 |
61 | $manager->create('fake');
62 | }
63 |
64 | public function testNoDefaultDriver(): void
65 | {
66 | $manager = new ChannelManager($this->app);
67 |
68 | $this->assertNull($manager->getDefaultDriver());
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/tests/Unit/Channels/ChatTest.php:
--------------------------------------------------------------------------------
1 | faker()->uuid, $title = $this->faker()->title, 'foo');
15 |
16 | $this->assertSame($id, $chat->getId());
17 | $this->assertSame($title, $chat->getTitle());
18 | $this->assertSame('foo', $chat->getType());
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Unit/Channels/DriverTest.php:
--------------------------------------------------------------------------------
1 | guzzle = $this->mock(Client::class);
22 | }
23 |
24 | public function testInitialize(): void
25 | {
26 | $driver = new FakeDriver;
27 | $parameters = collect([
28 | 'token' => str_random(),
29 | ]);
30 |
31 | $driver = $driver->initialize($parameters);
32 |
33 | $this->assertAttributeSame($parameters->get('token'), 'token', $driver);
34 | }
35 |
36 | public function testGetShortName() : void
37 | {
38 | $driver = new FakeDriver;
39 | $this->assertSame('FakeDriver', $driver->getShortName());
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tests/Unit/Channels/UserTest.php:
--------------------------------------------------------------------------------
1 | faker()->uuid,
16 | $name = $this->faker()->name,
17 | $username = $this->faker()->userName,
18 | $data = ['foo' => 'bar']
19 | );
20 |
21 | $this->assertSame($id, $user->getId());
22 | $this->assertSame($name, $user->getName());
23 | $this->assertSame($username, $user->getUsername());
24 | $this->assertSame($data, $user->getData()->toArray());
25 | }
26 |
27 | public function testAcceptsNullsForNameAndUsername(): void
28 | {
29 | $user = new User($id = $this->faker()->uuid, null, null);
30 |
31 | $this->assertSame($id, $user->getId());
32 | $this->assertNull($user->getName());
33 | $this->assertNull($user->getUsername());
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/Unit/Conversation/Activators/AttachmentTest.php:
--------------------------------------------------------------------------------
1 | fakeChat(), $this->fakeUser(), '/start', null, Template::make('foo', 'bar'));
17 |
18 | $activator = Attachment::make();
19 |
20 | $this->assertTrue($activator->matches($message));
21 | }
22 |
23 | public function testDoesNotMatchWithoutType(): void
24 | {
25 | $message = new MessageReceived($this->fakeChat(), $this->fakeUser(), '/start', null, null);
26 |
27 | $activator = Attachment::make();
28 |
29 | $this->assertFalse($activator->matches($message));
30 | }
31 |
32 | /**
33 | * @dataProvider types
34 | *
35 | * @param string $type
36 | */
37 | public function testMatchesWithType(string $type): void
38 | {
39 | $activator = Attachment::make($type);
40 | $attachment = Template::make($type, 'bar');
41 |
42 | $message = new MessageReceived($this->fakeChat(), $this->fakeUser(), '/start', null, $attachment);
43 |
44 | $this->assertTrue($activator->matches($message));
45 | }
46 |
47 | /**
48 | * @dataProvider types
49 | *
50 | * @param string $type
51 | */
52 | public function testDoesNotMatchWithType(string $type): void
53 | {
54 | $activator = Attachment::make($type);
55 |
56 | /** @var string $otherType */
57 | $otherType = collect(Template::possibleTypes())
58 | ->filter(function ($item) use ($type) {
59 | return $item !== $type;
60 | })
61 | ->random();
62 |
63 | $attachment = Template::make($otherType, 'bar');
64 |
65 | $message = new MessageReceived($this->fakeChat(), $this->fakeUser(), '/start', null, $attachment);
66 |
67 | $this->assertFalse($activator->matches($message));
68 | }
69 |
70 | public function types(): array
71 | {
72 | return collect(Template::possibleTypes())
73 | ->map(function ($item) {
74 | return [$item];
75 | })
76 | ->toArray();
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/tests/Unit/Conversation/Activators/ContainsTest.php:
--------------------------------------------------------------------------------
1 | assertAttributeEquals(['foo', 'bar'], 'needles', $activator);
17 | }
18 |
19 | public function testMatches(): void
20 | {
21 | $message = new MessageReceived($this->fakeChat(), $this->fakeUser(), 'this is foo');
22 |
23 | $activator = Contains::make('foo');
24 |
25 | $this->assertTrue($activator->matches($message));
26 | }
27 |
28 | public function testDoesNotMatch(): void
29 | {
30 | $message = new MessageReceived($this->fakeChat(), $this->fakeUser(), 'this is bar');
31 |
32 | $activator = Contains::make('foo');
33 |
34 | $this->assertFalse($activator->matches($message));
35 | }
36 |
37 | public function testMessageDoesNotHaveText(): void
38 | {
39 | $message = new MessageReceived($this->fakeChat(), $this->fakeUser(), '');
40 |
41 | $activator = Contains::make('foo');
42 |
43 | $this->assertFalse($activator->matches($message));
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/tests/Unit/Conversation/Activators/ExactTest.php:
--------------------------------------------------------------------------------
1 | fakeChat(), $this->fakeUser(), '/start');
16 |
17 | $activator = Exact::make('/start');
18 |
19 | $this->assertTrue($activator->matches($message));
20 | }
21 |
22 | public function testDoesNotMatchCaseSensitive(): void
23 | {
24 | $message = new MessageReceived($this->fakeChat(), $this->fakeUser(), '/Start');
25 |
26 | $activator = Exact::make('/start')->caseSensitive();
27 |
28 | $this->assertFalse($activator->matches($message));
29 | }
30 |
31 | public function testMatchesCaseInsensitive(): void
32 | {
33 | $message = new MessageReceived($this->fakeChat(), $this->fakeUser(), '/Start');
34 |
35 | $activator = Exact::make('/start');
36 |
37 | $this->assertTrue($activator->matches($message));
38 | }
39 |
40 | public function testDoesNotMatchCaseInsensitive(): void
41 | {
42 | $message = new MessageReceived($this->fakeChat(), $this->fakeUser(), '/Start');
43 |
44 | $activator = Exact::make('/stop');
45 |
46 | $this->assertFalse($activator->matches($message));
47 | }
48 |
49 | public function testEmptyMessage(): void
50 | {
51 | $message = new MessageReceived($this->fakeChat(), $this->fakeUser(), '');
52 |
53 | $activator = Exact::make('/start');
54 |
55 | $this->assertFalse($activator->matches($message));
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tests/Unit/Conversation/Activators/InArrayTest.php:
--------------------------------------------------------------------------------
1 | assertAttributeEquals(['foo', 'bar'], 'values', $activator);
18 | }
19 |
20 | public function testArrayMatches(): void
21 | {
22 | $message = new MessageReceived($this->fakeChat(), $this->fakeUser(), '/start');
23 |
24 | $activator = In::make(['/bye', '/start', '/test']);
25 | $this->assertTrue(
26 | $activator->matches($message)
27 | );
28 | }
29 |
30 | public function testArrayDoesNotMatch(): void
31 | {
32 | $message = new MessageReceived($this->fakeChat(), $this->fakeUser(), '/stop');
33 |
34 | $activator = In::make(['/bye', '/start', '/test']);
35 |
36 | $this->assertFalse($activator->matches($message));
37 | }
38 |
39 | public function testCollectionMatches(): void
40 | {
41 | $message = new MessageReceived($this->fakeChat(), $this->fakeUser(), '/start');
42 |
43 | $activator = In::make(collect(['/bye', '/start', '/test']));
44 |
45 | $this->assertTrue($activator->matches($message));
46 | }
47 |
48 | public function testCollectionDoesNotMatch(): void
49 | {
50 | $message = new MessageReceived($this->fakeChat(), $this->fakeUser(), '/stop');
51 |
52 | $activator = In::make(collect(['/bye', '/start', '/test']));
53 |
54 | $this->assertFalse($activator->matches($message));
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/tests/Unit/Conversation/Activators/PayloadTest.php:
--------------------------------------------------------------------------------
1 | fakeChat(), $this->fakeUser(), '/start', null, null, 'foo');
16 |
17 | $activator = Payload::make('foo');
18 |
19 | $this->assertTrue($activator->matches($message));
20 | }
21 |
22 | public function testDoesNotMatch(): void
23 | {
24 | $message = new MessageReceived($this->fakeChat(), $this->fakeUser(), '/start', null, null, 'foo');
25 |
26 | $activator = Payload::make('bar');
27 |
28 | $this->assertFalse($activator->matches($message));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/Unit/Conversation/Activators/RegexTest.php:
--------------------------------------------------------------------------------
1 | fakeChat(), $this->fakeUser(), 'abc');
16 |
17 | $activator = Regex::make('abc');
18 |
19 | $this->assertTrue($activator->matches($message));
20 | }
21 |
22 | public function testStringDoesNotMatch(): void
23 | {
24 | $message = new MessageReceived($this->fakeChat(), $this->fakeUser(), 'ab');
25 |
26 | $activator = Regex::make('abc');
27 |
28 | $this->assertFalse($activator->matches($message));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/Unit/Conversation/Concerns/AuthorizationTest.php:
--------------------------------------------------------------------------------
1 | assertFalse($class->passesAuthorization($this->mock(MessageReceived::class)));
18 | }
19 |
20 | public function testWithoutMethod()
21 | {
22 | $class = new AuthorizationTraitTestClassWithoutMethod();
23 |
24 | $this->assertTrue($class->passesAuthorization($this->mock(MessageReceived::class)));
25 | }
26 | }
27 |
28 | class AuthorizationTraitTestClassWithMethod
29 | {
30 | use Authorization;
31 |
32 | /**
33 | * Determine if passes the authorization check.
34 | *
35 | * @param MessageReceived $message
36 | *
37 | * @return bool
38 | */
39 | public function authorize(MessageReceived $message): bool
40 | {
41 | return false;
42 | }
43 | }
44 |
45 | class AuthorizationTraitTestClassWithoutMethod
46 | {
47 | use Authorization;
48 | }
49 |
--------------------------------------------------------------------------------
/tests/Unit/Conversation/Concerns/InteractsWithContextTest.php:
--------------------------------------------------------------------------------
1 | mock(Channel::class);
21 |
22 | $this->setContext(
23 | new Context(
24 | $channel,
25 | $this->mock(Chat::class),
26 | $this->mock(User::class),
27 | ['foo' => 'bar']
28 | )
29 | );
30 |
31 | $this->assertSame($channel, $this->getChannel());
32 | }
33 |
34 | public function testGetChat()
35 | {
36 | $chat = $this->mock(Chat::class);
37 |
38 | $this->setContext(
39 | new Context(
40 | $this->mock(Channel::class),
41 | $chat,
42 | $this->mock(User::class),
43 | ['foo' => 'bar']
44 | )
45 | );
46 |
47 | $this->assertSame($chat, $this->getChat());
48 | }
49 |
50 | public function testGetUser()
51 | {
52 | $user = $this->mock(User::class);
53 |
54 | $this->setContext(
55 | new Context(
56 | $this->mock(Channel::class),
57 | $this->mock(Chat::class),
58 | $user,
59 | ['foo' => 'bar']
60 | )
61 | );
62 |
63 | $this->assertSame($user, $this->getUser());
64 | }
65 |
66 | public function testContext(): void
67 | {
68 | $this->setContext(
69 | new Context(
70 | $this->mock(Channel::class),
71 | $this->mock(Chat::class),
72 | $this->mock(User::class),
73 | ['foo' => 'bar']
74 | )
75 | );
76 |
77 | $this->assertSame('bar', $this->context('foo'));
78 | $this->assertNull($this->context('bar'));
79 | $this->assertSame('foo', $this->context('bar', 'foo'));
80 | $this->assertInstanceOf(Context::class, $this->context());
81 | }
82 |
83 | public function testRemember(): void
84 | {
85 | $this->setContext(
86 | new Context(
87 | $this->mock(Channel::class),
88 | $this->mock(Chat::class),
89 | $this->mock(User::class),
90 | ['foo' => 'bar']
91 | )
92 | );
93 |
94 | $this->remember('some', 'value');
95 |
96 | $this->assertSame('value', $this->context('some'));
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/tests/Unit/Conversation/Concerns/SendsMessagesTest.php:
--------------------------------------------------------------------------------
1 | mock(Context::class);
25 | $context->shouldReceive('getChannel')->atLeast()->once();
26 | $context->shouldReceive('getChat')->atLeast()->once();
27 | $context->shouldReceive('getUser')->atLeast()->once();
28 |
29 | $this->setContext($context);
30 | }
31 |
32 | public function testReply(): void
33 | {
34 | Bus::fake();
35 |
36 | $this->reply($this->faker()->text)->template($this->mock(Template::class));
37 |
38 | Bus::assertDispatched(SendMessage::class);
39 | }
40 |
41 | public function testReplyWithDelay(): void
42 | {
43 | Bus::fake();
44 |
45 | $this->reply($this->faker()->text)
46 | ->template($this->mock(Template::class))
47 | ->delay(5);
48 |
49 | Bus::assertDispatched(SendMessage::class, function (SendMessage $job) {
50 | return $job->delay === 5;
51 | });
52 | }
53 |
54 | public function testSendAttachment(): void
55 | {
56 | Bus::fake();
57 |
58 | $this->sendAttachment($this->mock(Attachment::class));
59 |
60 | Bus::assertDispatched(SendAttachment::class);
61 | }
62 |
63 | public function testSendAttachmentWithDelay(): void
64 | {
65 | Bus::fake();
66 |
67 | $this->sendAttachment($this->mock(Attachment::class))->delay(7);
68 |
69 | Bus::assertDispatched(SendAttachment::class, function (SendAttachment $job) {
70 | return $job->delay === 7;
71 | });
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/tests/Unit/Conversation/ContextTest.php:
--------------------------------------------------------------------------------
1 | mock(Channel::class);
18 | $chat = $this->mock(Chat::class);
19 | $user = $this->mock(User::class);
20 | $items = ['foo' => 'bar'];
21 |
22 | $context = new Context($channel, $chat, $user, $items);
23 |
24 | $this->assertSame($channel, $context->getChannel());
25 | $this->assertSame($chat, $context->getChat());
26 | $this->assertSame($user, $context->getUser());
27 | $this->assertNull($context->getIntent());
28 | $this->assertNull($context->getInteraction());
29 |
30 | $this->assertSame('bar', $context->getItem('foo'));
31 | $this->assertNull($context->getItem('bar'));
32 |
33 | $context->setItem('bar', 'foo');
34 | $this->assertSame('foo', $context->getItem('bar'));
35 |
36 | $payload = [
37 | 'intent' => null,
38 | 'interaction' => null,
39 | 'items' => [
40 | 'foo' => 'bar',
41 | 'bar' => 'foo',
42 | ],
43 | 'attempts' => 0,
44 | ];
45 |
46 | $this->assertSame($payload, $context->toArray());
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/tests/Unit/Conversation/ConversationManagerTest.php:
--------------------------------------------------------------------------------
1 | manager = resolve(ConversationManager::class);
30 | }
31 |
32 | public function testRegisterIntent(): void
33 | {
34 | $this->manager->registerIntent('foo');
35 | $this->manager->registerIntent('bar');
36 |
37 | $this->assertAttributeEquals(['foo', 'bar'], 'intents', $this->manager);
38 |
39 | $this->assertSame(['foo', 'bar'], $this->manager->getIntents());
40 | }
41 |
42 | public function testRegisterFallbackIntent(): void
43 | {
44 | $this->manager->registerFallbackIntent('foo');
45 |
46 | $this->assertAttributeEquals('foo', 'fallbackIntent', $this->manager);
47 | }
48 |
49 | public function testMatchIntent(): void
50 | {
51 | $this->manager->registerIntent(FakeIntent::class);
52 | $this->manager->registerFallbackIntent(FallbackIntent::class);
53 |
54 | $messageReceived = new MessageReceived(new Chat('1'), new User('2'), 'foo');
55 |
56 | $result = $this->manager->matchIntent($messageReceived);
57 |
58 | $this->assertInstanceOf(FakeIntent::class, $result);
59 |
60 | $messageReceived = new MessageReceived(new Chat('1'), new User('2'), 'bar');
61 |
62 | $result = $this->manager->matchIntent($messageReceived);
63 |
64 | $this->assertInstanceOf(FallbackIntent::class, $result);
65 | }
66 |
67 | public function testMatchIntentWithClosureActivator(): void
68 | {
69 | $this->manager->registerIntent(FakeIntentWithClosureActivator::class);
70 | $this->manager->registerFallbackIntent(FallbackIntent::class);
71 |
72 | $messageReceived = new MessageReceived(new Chat('1'), new User('2'), 'foo');
73 |
74 | $result = $this->manager->matchIntent($messageReceived);
75 |
76 | $this->assertInstanceOf(FakeIntentWithClosureActivator::class, $result);
77 |
78 | $messageReceived = new MessageReceived(new Chat('1'), new User('2'), 'bar');
79 |
80 | $result = $this->manager->matchIntent($messageReceived);
81 |
82 | $this->assertInstanceOf(FallbackIntent::class, $result);
83 | }
84 |
85 | public function testResolveContext(): void
86 | {
87 | $key = 'context.foo-channel.foo-chat.foo-user';
88 |
89 | cache([$key => [
90 | 'intent' => FakeIntent::class,
91 | 'interaction' => FakeInteraction::class,
92 | 'items' => ['foo' => 'bar'],
93 | ]], 100);
94 |
95 | $result = $this->manager->resolveContext(
96 | new Channel('foo-channel', new FakeDriver),
97 | new Chat('foo-chat'),
98 | new User('foo-user')
99 | );
100 |
101 | $this->assertSame('foo-channel', $result->getChannel()->getName());
102 | $this->assertSame('foo-chat', $result->getChat()->getId());
103 | $this->assertSame('foo-user', $result->getUser()->getId());
104 | $this->assertInstanceOf(FakeIntent::class, $result->getIntent());
105 | $this->assertInstanceOf(FakeInteraction::class, $result->getInteraction());
106 | $this->assertSame('bar', $result->getItem('foo'));
107 | $this->assertTrue($this->app->has('fondbot.conversation.context'));
108 | $this->assertSame($result, resolve('fondbot.conversation.context'));
109 | }
110 |
111 | public function testSaveContext(): void
112 | {
113 | $key = 'context.foo-channel.foo-chat.foo-user';
114 |
115 | $context = new Context(
116 | new Channel('foo-channel', new FakeDriver),
117 | new Chat('foo-chat'),
118 | new User('foo-user'),
119 | ['foo' => 'bar']
120 | );
121 | $context->setIntent(new FakeIntent)->setInteraction(new FakeInteraction);
122 |
123 | $this->manager->saveContext($context);
124 |
125 | $this->assertTrue(cache()->has($key));
126 | $this->assertSame(FakeIntent::class, array_get(cache($key), 'intent'));
127 | $this->assertSame(FakeInteraction::class, array_get(cache($key), 'interaction'));
128 | $this->assertSame(['foo' => 'bar'], array_get(cache($key), 'items'));
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/tests/Unit/Conversation/FallbackIntentTest.php:
--------------------------------------------------------------------------------
1 | assertSame([], (new FallbackIntent)->activators());
22 | }
23 |
24 | public function testRun(): void
25 | {
26 | Bus::fake();
27 |
28 | $context = $this->mock(Context::class);
29 | $message = $this->mock(MessageReceived::class);
30 | $channel = $this->mock(Channel::class);
31 | $chat = $this->mock(Chat::class);
32 | $user = $this->mock(User::class);
33 |
34 | $this->setContext($context);
35 |
36 | $context->shouldReceive('getChannel')->andReturn($channel)->atLeast()->once();
37 | $context->shouldReceive('getChat')->andReturn($chat)->atLeast()->once();
38 | $context->shouldReceive('getUser')->andReturn($user)->atLeast()->once();
39 |
40 | (new FallbackIntent)->handle($message);
41 |
42 | Bus::assertDispatched(SendMessage::class);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tests/Unit/Conversation/IntentTest.php:
--------------------------------------------------------------------------------
1 | mock(Intent::class)->shouldIgnoreMissing();
17 |
18 | $intent->handle($this->mock(MessageReceived::class));
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Unit/Conversation/InteractionTest.php:
--------------------------------------------------------------------------------
1 | mock(Context::class);
18 | /** @var Interaction|MockInterface $interaction */
19 | $interaction = $this->mock(Interaction::class)->makePartial();
20 |
21 | $this->setContext($context);
22 |
23 | $message = $this->mock(MessageReceived::class);
24 |
25 | $context->shouldReceive('getInteraction')->andReturn($interaction)->once();
26 |
27 | $interaction->shouldReceive('process')->with($message)->once();
28 |
29 | $interaction->handle($message);
30 | }
31 |
32 | public function testRunCurrentInteractionNotInSession(): void
33 | {
34 | $context = $this->mock(Context::class);
35 | /** @var Interaction|MockInterface $interaction */
36 | $interaction = $this->mock(Interaction::class)->makePartial();
37 |
38 | $this->setContext($context);
39 |
40 | $message = $this->mock(MessageReceived::class);
41 |
42 | $context->shouldReceive('getInteraction')->andReturn(null)->once();
43 | $context->shouldReceive('setInteraction')->with($interaction)->once();
44 |
45 | $interaction->shouldReceive('run')->with($message)->once();
46 |
47 | $interaction->handle($message);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/Unit/Drivers/TemplateCompilerTest.php:
--------------------------------------------------------------------------------
1 | mock(TemplateCompiler::class)->shouldAllowMockingProtectedMethods()->makePartial();
21 |
22 | $compiler->shouldReceive('compileKeyboard')->with($template)->andReturn(['buttons' => ['foo', 'bar']])->once();
23 |
24 | $result = $compiler->compile($template);
25 | $this->assertSame(['buttons' => ['foo', 'bar']], $result);
26 | }
27 |
28 | public function testCompileUsingMethodButMethodDoesNotExist(): void
29 | {
30 | $template = $this->mock(Template::class);
31 |
32 | /** @var TemplateCompiler|Mock $compiler */
33 | $compiler = $this->mock(TemplateCompiler::class)->shouldAllowMockingProtectedMethods()->makePartial();
34 |
35 | $template->shouldReceive('getName')->andReturn('foo')->atLeast()->once();
36 |
37 | $this->assertNull($compiler->compile($template));
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/Unit/Foundation/Commands/SendAttachmentTest.php:
--------------------------------------------------------------------------------
1 | mock(Channel::class);
19 | $chat = $this->mock(Chat::class);
20 | $recipient = $this->mock(User::class);
21 | $attachment = $this->mock(Attachment::class);
22 |
23 | new SendAttachment($channel, $chat, $recipient, $attachment);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/Unit/Foundation/Commands/SendMessageTest.php:
--------------------------------------------------------------------------------
1 | mock(Channel::class);
20 | $chat = $this->mock(Chat::class);
21 | $recipient = $this->mock(User::class);
22 | $text = $this->faker()->text;
23 | $template = $this->mock(Template::class);
24 |
25 | new SendMessage($channel, $chat, $recipient, $text, $template);
26 | }
27 |
28 | /**
29 | * @expectedException InvalidArgumentException
30 | * @expectedExceptionMessage Either text or template should be set.
31 | */
32 | public function testTextAndTemplateNull(): void
33 | {
34 | new SendMessage(
35 | $this->mock(Channel::class),
36 | $this->mock(Chat::class),
37 | $this->mock(User::class)
38 | );
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/Unit/Foundation/KernelTest.php:
--------------------------------------------------------------------------------
1 | assertSame($this->kernel, kernel());
19 | }
20 |
21 | public function testContext(): void
22 | {
23 | $this->assertNull(context());
24 |
25 | $context = new Context(
26 | new Channel('foo', new FakeDriver()),
27 | new Chat('1'),
28 | new User('2')
29 | );
30 |
31 | $context->setItem('bar', 'baz');
32 |
33 | $this->app->bind('fondbot.conversation.context', function () use (&$context) {
34 | return $context;
35 | });
36 |
37 | $this->assertSame($context, context());
38 | $this->assertNull(context('foo'));
39 | $this->assertSame('baz', context('bar'));
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tests/Unit/Templates/AttachmentTest.php:
--------------------------------------------------------------------------------
1 | faker()->url, $parameters = ['foo' => 'bar']);
20 |
21 | $this->assertSame($type, $attachment->getType());
22 | $this->assertSame($path, $attachment->getPath());
23 | $this->assertSame($parameters, $attachment->getParameters()->toArray());
24 | }
25 |
26 | public function testPossibleTypes()
27 | {
28 | $this->assertSame(collect($this->types())->flatten()->toArray(), Attachment::possibleTypes());
29 | }
30 |
31 | public function types()
32 | {
33 | return [
34 | ['file'],
35 | ['image'],
36 | ['audio'],
37 | ['video'],
38 | ];
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/Unit/Templates/Keyboard/PayloadButtonTest.php:
--------------------------------------------------------------------------------
1 | faker()->word;
15 | $payload = $this->faker()->text;
16 |
17 | $button = PayloadButton::make($label, $payload);
18 |
19 | $this->assertSame('PayloadButton', $button->getName());
20 | $this->assertSame($label, $button->getLabel());
21 | $this->assertSame($payload, $button->getPayload());
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tests/Unit/Templates/Keyboard/ReplyButtonTest.php:
--------------------------------------------------------------------------------
1 | faker()->word;
15 |
16 | $button = ReplyButton::make($label);
17 |
18 | $this->assertSame('ReplyButton', $button->getName());
19 | $this->assertSame($label, $button->getLabel());
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/Unit/Templates/Keyboard/UrlButtonTest.php:
--------------------------------------------------------------------------------
1 | faker()->word;
15 | $url = $this->faker()->url;
16 | $parameters = ['foo' => 'bar'];
17 |
18 | $button = UrlButton::make($label, $url, $parameters);
19 |
20 | $this->assertSame('UrlButton', $button->getName());
21 | $this->assertSame($label, $button->getLabel());
22 | $this->assertSame($url, $button->getUrl());
23 | $this->assertEquals(collect($parameters), $button->getParameters());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/Unit/Templates/KeyboardTest.php:
--------------------------------------------------------------------------------
1 | mock(Button::class),
17 | $this->mock(Button::class),
18 | ];
19 |
20 | $keyboard = Keyboard::make($buttons, ['foo' => 'bar']);
21 |
22 | $this->assertInstanceOf(Keyboard::class, $keyboard);
23 | $this->assertSame('Keyboard', $keyboard->getName());
24 | $this->assertSame($buttons, $keyboard->getButtons());
25 | $this->assertEquals(collect(['foo' => 'bar']), $keyboard->getParameters());
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/Unit/Templates/LocationTest.php:
--------------------------------------------------------------------------------
1 | faker()->latitude, $longitude = $this->faker()->longitude);
15 |
16 | $this->assertSame($latitude, $location->getLatitude());
17 | $this->assertSame($longitude, $location->getLongitude());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------