├── .codeclimate.yml ├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── phpmd.xml ├── phpunit.xml ├── src └── Botonomous │ ├── AbstractAccessList.php │ ├── AbstractBaseSlack.php │ ├── AbstractBot.php │ ├── AbstractCommandContainer.php │ ├── AbstractConfig.php │ ├── AbstractSender.php │ ├── AbstractSlackEntity.php │ ├── Action.php │ ├── BlackList.php │ ├── BotonomousException.php │ ├── Channel.php │ ├── Command.php │ ├── CommandContainer.php │ ├── CommandExtractor.php │ ├── Config.php │ ├── Dictionary.php │ ├── Event.php │ ├── ImChannel.php │ ├── MessageAction.php │ ├── OAuth.php │ ├── Sender.php │ ├── Slackbot.php │ ├── Team.php │ ├── User.php │ ├── WhiteList.php │ ├── client │ ├── AbstractClient.php │ └── ApiClient.php │ ├── dictionary │ ├── access-control.json │ ├── generic-messages.json │ ├── punctuations.json │ ├── question-answer.json │ ├── stopwords-en.json │ ├── test-key-value.json │ └── test.json │ ├── listener │ ├── AbstractBaseListener.php │ ├── EventListener.php │ └── SlashCommandListener.php │ ├── plugin │ ├── AbstractPlugin.php │ ├── PluginInterface.php │ ├── help │ │ ├── Help.php │ │ └── HelpConfig.php │ ├── ping │ │ └── Ping.php │ └── qa │ │ └── QA.php │ ├── public │ ├── .htaccess │ ├── favicon.png │ └── index.php │ └── utility │ ├── AbstractUtility.php │ ├── ArrayUtility.php │ ├── ClassUtility.php │ ├── FileUtility.php │ ├── FormattingUtility.php │ ├── LanguageProcessingUtility.php │ ├── LoggerUtility.php │ ├── MessageUtility.php │ ├── RequestUtility.php │ ├── SecurityUtility.php │ ├── SessionUtility.php │ └── StringUtility.php └── tests ├── Botonomous ├── ActionTest.php ├── BlackListTest.php ├── CommandContainerTest.php ├── CommandExtractorTest.php ├── CommandTest.php ├── ConfigTest.php ├── DictionaryTest.php ├── EventTest.php ├── ImChannelTest.php ├── MessageActionTest.php ├── OAuthTest.php ├── PhpunitHelper.php ├── SenderTest.php ├── SlackbotTest.php ├── TeamTest.php ├── UserTest.php ├── WhiteListTest.php ├── client │ └── ApiClientTest.php ├── listener │ ├── EventListenerTest.php │ └── SlashCommandListenerTest.php ├── plugin │ ├── help │ │ ├── HelpConfigTest.php │ │ └── HelpTest.php │ ├── ping │ │ └── PingTest.php │ └── qa │ │ └── QATest.php └── utility │ ├── ArrayUtilityTest.php │ ├── ClassUtilityTest.php │ ├── FileUtilityTest.php │ ├── LanguageProcessingUtilityTest.php │ ├── LoggerUtilityTest.php │ ├── MessageUtilityTest.php │ ├── RequestUtilityTest.php │ ├── SecurityUtilityTest.php │ ├── SessionUtilityTest.php │ └── StringUtilityTest.php └── bootstrap.php /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | phpcodesniffer: 3 | enabled: true 4 | config: 5 | file_extensions: "php" 6 | standard: "PSR2" 7 | fixme: 8 | enabled: true 9 | phpmd: 10 | enabled: true 11 | config: 12 | rulesets: "phpmd.xml" 13 | duplication: 14 | enabled: true 15 | config: 16 | languages: 17 | php: 18 | mass_threshold: 53 19 | sonar-php: 20 | enabled: true 21 | checks: 22 | php:S138: 23 | enabled: false 24 | php:S1117: 25 | enabled: false 26 | php:S1448: 27 | enabled: false 28 | php:S1142: 29 | enabled: false 30 | ratings: 31 | paths: 32 | - "src/**/*" 33 | checks: 34 | argument-count: 35 | enabled: false 36 | complex-logic: 37 | enabled: false 38 | file-lines: 39 | enabled: false 40 | method-complexity: 41 | enabled: false 42 | method-lines: 43 | enabled: false 44 | method-count: 45 | enabled: false 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/* 2 | composer.phar 3 | src/Botonomous/tmp/* 4 | .idea/* -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | excluded_paths: 3 | - 'tests/*' 4 | checks: 5 | php: 6 | use_self_instead_of_fqcn: true 7 | uppercase_constants: true 8 | simplify_boolean_return: true 9 | return_doc_comments: true 10 | remove_extra_empty_lines: true 11 | prefer_while_loop_over_for_loop: true 12 | phpunit_assertions: true 13 | no_short_variable_names: 14 | minimum: '3' 15 | no_short_method_names: 16 | minimum: '3' 17 | no_long_variable_names: 18 | maximum: '20' 19 | no_goto: true 20 | newline_at_end_of_file: true 21 | avoid_fixme_comments: true 22 | avoid_multiple_statements_on_same_line: true 23 | avoid_perl_style_comments: true 24 | avoid_todo_comments: true 25 | check_method_contracts: 26 | verify_interface_like_constraints: true 27 | verify_documented_constraints: true 28 | verify_parent_constraints: true 29 | classes_in_camel_caps: true 30 | encourage_postdec_operator: true 31 | fix_line_ending: true 32 | fix_use_statements: 33 | remove_unused: true 34 | preserve_multiple: false 35 | preserve_blanklines: false 36 | order_alphabetically: false 37 | function_in_camel_caps: true 38 | line_length: 39 | max_length: '120' 40 | instanceof_class_exists: true 41 | function_in_camel_caps: true 42 | foreach_usable_as_reference: true 43 | foreach_traversable: true 44 | fix_line_ending: true 45 | fix_doc_comments: true 46 | encourage_shallow_comparison: true 47 | encourage_postdec_operator: true 48 | duplication: true 49 | deprecated_code_usage: true 50 | deadlock_detection_in_loops: true 51 | comparison_always_same_result: true 52 | code_rating: true 53 | closure_use_not_conflicting: true 54 | closure_use_modifiable: true 55 | classes_in_camel_caps: true 56 | catch_class_exists: true 57 | call_to_parent_method: true 58 | blank_line_after_namespace_declaration: true 59 | avoid_useless_overridden_methods: true 60 | avoid_usage_of_logical_operators: true 61 | avoid_todo_comments: true 62 | avoid_superglobals: true 63 | avoid_perl_style_comments: true 64 | avoid_multiple_statements_on_same_line: true 65 | avoid_length_functions_in_loops: true 66 | avoid_fixme_comments: true 67 | avoid_entity_manager_injection: true 68 | avoid_duplicate_types: true 69 | avoid_corrupting_byteorder_marks: true 70 | avoid_conflicting_incrementers: true 71 | avoid_closing_tag: true 72 | avoid_aliased_php_functions: true 73 | assignment_of_null_return: true 74 | argument_type_checks: true 75 | 76 | coding_style: 77 | php: 78 | indentation: 79 | general: 80 | use_tabs: false 81 | size: 4 82 | switch: 83 | indent_case: true 84 | spaces: 85 | general: 86 | linefeed_character: newline 87 | before_parentheses: 88 | function_declaration: false 89 | closure_definition: false 90 | function_call: false 91 | if: true 92 | for: true 93 | while: true 94 | switch: true 95 | catch: true 96 | array_initializer: false 97 | around_operators: 98 | assignment: true 99 | logical: true 100 | equality: true 101 | relational: true 102 | bitwise: true 103 | additive: true 104 | multiplicative: true 105 | shift: true 106 | unary_additive: false 107 | negation: false 108 | before_left_brace: 109 | class: true 110 | function: true 111 | if: true 112 | else: true 113 | for: true 114 | while: true 115 | do: true 116 | switch: true 117 | try: true 118 | catch: true 119 | finally: true 120 | before_keywords: 121 | else: true 122 | while: true 123 | catch: true 124 | finally: true 125 | within: 126 | brackets: false 127 | array_initializer: false 128 | grouping: false 129 | function_call: false 130 | function_declaration: false 131 | if: false 132 | for: false 133 | while: false 134 | switch: false 135 | catch: false 136 | type_cast: false 137 | ternary_operator: 138 | before_condition: true 139 | after_condition: true 140 | before_alternative: true 141 | after_alternative: true 142 | in_short_version: false 143 | other: 144 | before_comma: false 145 | after_comma: true 146 | before_semicolon: false 147 | after_semicolon: true 148 | after_type_cast: true 149 | braces: 150 | classes_functions: 151 | class: undefined 152 | function: undefined 153 | closure: undefined 154 | if: 155 | opening: undefined 156 | always: true 157 | else_on_new_line: false 158 | for: 159 | opening: undefined 160 | always: true 161 | while: 162 | opening: undefined 163 | always: true 164 | do_while: 165 | opening: undefined 166 | always: true 167 | while_on_new_line: false 168 | switch: 169 | opening: undefined 170 | try: 171 | opening: undefined 172 | catch_on_new_line: false 173 | finally_on_new_line: false 174 | upper_lower_casing: 175 | keywords: 176 | general: undefined 177 | constants: 178 | true_false_null: undefined 179 | 180 | build: 181 | tests: 182 | override: 183 | - 184 | command: 'vendor/bin/phpunit --coverage-clover=coverage-file' 185 | coverage: 186 | file: 'coverage-file' 187 | format: 'clover' 188 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | env: 2 | global: 3 | - coverage=false 4 | - CC_TEST_REPORTER_ID=e3f592753a2a1c938f2fc8597a5344c91f3e1baa6e44ac5fdb88970c3a94265d 5 | 6 | language: php 7 | 8 | matrix: 9 | include: 10 | - php: 7.1 11 | env: coverage=true 12 | - php: 7.2 13 | 14 | sudo: false 15 | 16 | install: 17 | - composer install 18 | 19 | before_script: 20 | - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter 21 | - chmod +x ./cc-test-reporter 22 | - if [[ $coverage = 'true' ]]; then ./cc-test-reporter before-build; fi 23 | 24 | script: 25 | - vendor/bin/phpunit --coverage-clover build/logs/clover.xml --configuration phpunit.xml 26 | 27 | after_success: 28 | - if [[ $coverage = 'true' ]]; then ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT; fi 29 | 30 | notifications: 31 | slack: 32 | secure: j0e4qbj2/fHlyyAg/CuTKYMaTtpmhiSzgFQLdPLP7/WPX6tfHYm732K58CMEbHc4MlDmiUelBWhWR39EgBBoX4QBLc+l8EdgT3gYOfwJ7LQWtdMbP2tdXyAPoTe2WOnUr5hG2LMIsM4P6JjGKKAJrlLoRRpkfIz1h1Z70VKR9+4qMjOP2s2NCd82E6g/wDHJbMEXbE/tvh1/r1w7eb23Emkv59or3hD4LA969YqMN+ycPjwGP4vLsiZ8kRdcSS/B9AZLmVvbc8SU4onGDQboiDVJvM8t0XxUGM8yceh2kWSVLkQv+I/BrtNDCiAa3WNVjWEVpkJ6Rn4k/FDNN3AWnFhpy38S+EwDgGS3CioYQmMAa1OgekciMIxsxdwRsCeQhgbnlYDvTNtcAoNHwC3wBBOX2qGjMLoupkk+akATmAAHlBs/n9ap2IUT1+SPOtJQ4COb36rK3Lq+jCYdlRR5O+dbbyFhMt+RijGwER3no0D8QW1Q23c9yj5VQ/NA2VAMCcD04w+AlnT3vni/Gs5UIjNus63YQbfY1GS7sRC/LSuSOslBXTWCr7/w/dNrVLUy3ouBNuep0OAJejAi7o7/JF9gEY9j88TkU6BNb+6D0qgZbHF1N1NuPiR4STh3LpKxXPY+FMNaT8yjy5xvdpANbSe8fJEwcjUUA1Iupp2W/cw= 33 | -------------------------------------------------------------------------------- /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 contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at ehsan.ann@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Botonomous 2 | You are more than welcome for any contributions to this project. The rules are: 3 | * The framework follows `PSR-2` coding standard and the `PSR-4` autoloading standard. 4 | * Make sure all the existing `PHPUnit` tests are passed. 5 | * For any new function write a test and keep the [code coverage](https://codeclimate.com/github/iranianpep/botonomous/coverage) >= `95%`. 6 | * Keep [Code Climate GPA](https://codeclimate.com/github/iranianpep/botonomous) at `4` and make sure there is no issue. 7 | * Keep [Codacy](https://www.codacy.com/app/iranianpep/botonomous/dashboard) rate at `A`. 8 | * Keep [Scrutinizer](https://scrutinizer-ci.com/g/iranianpep/botonomous) rate >= `8.5`. 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Ehsan Abbasi 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Botonomous 2 |

3 | Latest Stable Version 4 | GitHub license 5 | Packagist 6 |

7 | 8 | ## Code Status 9 | 10 | [![Build Status](https://travis-ci.org/iranianpep/botonomous.svg?branch=master)](https://travis-ci.org/iranianpep/botonomous) 11 | [![Build Status](https://scrutinizer-ci.com/g/iranianpep/botonomous/badges/build.png?b=master)](https://scrutinizer-ci.com/g/iranianpep/botonomous/build-status/master) 12 | [![Code Climate](https://codeclimate.com/github/iranianpep/botonomous/badges/gpa.svg)](https://codeclimate.com/github/iranianpep/botonomous) 13 | [![Test Coverage](https://codeclimate.com/github/iranianpep/botonomous/badges/coverage.svg)](https://codeclimate.com/github/iranianpep/botonomous/coverage) 14 | [![Code Coverage](https://scrutinizer-ci.com/g/iranianpep/botonomous/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/iranianpep/botonomous/?branch=master) 15 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/iranianpep/botonomous/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/iranianpep/botonomous/?branch=master) 16 | [![StyleCI](https://styleci.io/repos/73189365/shield?branch=master)](https://styleci.io/repos/73189365) 17 | [![SensioLabsInsight](https://insight.sensiolabs.com/projects/d9b77f1a-3d4a-423f-b473-30a25496f9a0/mini.png)](https://insight.sensiolabs.com/projects/d9b77f1a-3d4a-423f-b473-30a25496f9a0) 18 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/039ffa789e6a4040b9b8d596ede07db4)](https://www.codacy.com/app/iranianpep/botonomous) 19 | [![Code consistency](https://squizlabs.github.io/PHP_CodeSniffer/analysis/iranianpep/botonomous/grade.svg)](https://squizlabs.github.io/PHP_CodeSniffer/analysis/iranianpep/botonomous) 20 | [![BCH compliance](https://bettercodehub.com/edge/badge/iranianpep/botonomous?branch=master)](https://bettercodehub.com/) 21 | [![Issue Count](https://codeclimate.com/github/iranianpep/botonomous/badges/issue_count.svg)](https://codeclimate.com/github/iranianpep/botonomous) 22 | 23 | Botonomous is a PHP framework for creating autonomous [Slack bots](https://api.slack.com/bot-users). It is specifically designed for [Slack](https://slack.com) and supports [Events API](https://api.slack.com/events-api) and [Slash commands](https://api.slack.com/slash-commands). Botonomous is unique because of: 24 | * Quality Code: Modern, high quality and fully unit tested code base 25 | * Modular System: Pluggable architecture for enhanced management of commands 26 | * OAuth 2.0 Support: Built-in [Add to Slack](https://api.slack.com/docs/slack-button) button using OAuth 2.0 27 | * Utility Classes: A handful of standalone utility classes to make everyone's life (even your partner's!) easier 28 | 29 | ## Get Started 30 | 31 | Please check the [wiki](https://github.com/iranianpep/botonomous/wiki) for the documentation and installation guide or you can go straight to [Getting Started](https://github.com/iranianpep/botonomous/wiki/Getting-Started) section. 32 | 33 | ## Get help 34 | 35 | * [![Join Botonomous Team](https://img.shields.io/badge/Slack-Join%20Team-green.svg)](http://botonomous.herokuapp.com/) 36 | * [Issues](https://github.com/iranianpep/botonomous/issues) - Got issues? We are happy to help! However to report any security vulnerabilities, please drop us an email at ehsan.abb@gmail.com and please do not report it on GitHub. 37 | * [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/iranianpep/botonomous.svg)](http://isitmaintained.com/project/iranianpep/botonomous "Average time to resolve an issue") 38 | [![Percentage of issues still open](http://isitmaintained.com/badge/open/iranianpep/botonomous.svg)](http://isitmaintained.com/project/iranianpep/botonomous "Percentage of issues still open") 39 | 40 | ## Get involved 41 | 42 | Botonomous is free forever but we need your help to make it even better. Let's make it happen by contributing to it. You can check out the [contributing guide](https://github.com/iranianpep/botonomous/blob/master/CONTRIBUTING.md) for guidelines about how to proceed. 43 | 44 | [![Code Triagers Badge](https://www.codetriage.com/iranianpep/botonomous/badges/users.svg)](https://www.codetriage.com/iranianpep/botonomous) 45 | 46 | ## Built with Botonomous 47 | 48 | If you have built any awesome Slack bot with Botonomous let us know to list it here. You can also add the following badge: 49 | 50 | [![Built with Botonomous](https://img.shields.io/badge/Built%20with-Botonomous-green.svg)](https://github.com/iranianpep/botonomous) 51 | 52 | ``` 53 | [![Built with Botonomous](https://img.shields.io/badge/Built%20with-Botonomous-green.svg)](https://github.com/iranianpep/botonomous) 54 | ``` 55 | 56 | ## Community 57 | 58 | * [Twitter](https://twitter.com/botonomous) 59 | * [Facebook](https://www.facebook.com/botonomous) 60 | 61 | ## Support 62 | 63 | * [Jet Brains](https://www.jetbrains.com) - Without [PhpStorm](https://www.jetbrains.com/phpstorm) this project was in the middle of nowhere! 64 | * [Sara Salim Pour](http://sarasalimpour.com) - The outstanding artwork is because of her talent. 65 | * [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=BXMKEZ23PX8K2) 66 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "botonomous/botonomous", 3 | "description": "Simple Slackbot that can listen to Slack messages and send back appropriate responses to a channel(s).", 4 | "homepage": "https://github.com/iranianpep/botonomous", 5 | "license": "MIT", 6 | "authors": [ 7 | { "name": "Ehsan Abbasi", "email": "ehsan.abb@gmail.com" } 8 | ], 9 | "require": { 10 | "php": ">=7.1", 11 | "nlp-tools/nlp-tools": "^0.1.3", 12 | "guzzlehttp/guzzle": "6.3.*", 13 | "monolog/monolog": "^1.22" 14 | }, 15 | "require-dev": { 16 | "phpunit/phpunit": "^6.2 || ^7 || ^8 || ^9" 17 | }, 18 | "autoload": { 19 | "psr-4": { 20 | "Botonomous\\": "src/Botonomous" 21 | } 22 | }, 23 | "autoload-dev": { 24 | "psr-4": { 25 | "Botonomous\\": "tests/Botonomous" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /phpmd.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | Custom rules for checking my project 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | tests/Botonomous/ 6 | 7 | 8 | 9 | 10 | 11 | src/Botonomous/ 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/Botonomous/AbstractAccessList.php: -------------------------------------------------------------------------------- 1 | getDictionary()->get('access-control'); 26 | } 27 | 28 | /** 29 | * @param $sublistKey 30 | * 31 | * @throws \Exception 32 | * 33 | * @return mixed 34 | */ 35 | protected function getSubAccessControlList($sublistKey) 36 | { 37 | $list = $this->getAccessControlList(); 38 | 39 | if (!isset($list[$sublistKey])) { 40 | /* @noinspection PhpInconsistentReturnPointsInspection */ 41 | return; 42 | } 43 | 44 | return $list[$sublistKey]; 45 | } 46 | 47 | /** 48 | * @param array $list 49 | * 50 | * @throws \Exception 51 | * 52 | * @return bool 53 | */ 54 | protected function isEmailInList(array $list): bool 55 | { 56 | // get user info 57 | $userInfo = $this->getSlackUserInfo(); 58 | 59 | return !empty($userInfo) && in_array($userInfo['profile']['email'], $list['userEmail']); 60 | } 61 | 62 | /** 63 | * Check if email is white listed or black listed 64 | * If userEmail list is not set, return true for whitelist and false for blacklist. 65 | * 66 | * @throws \Exception 67 | * 68 | * @return bool 69 | */ 70 | protected function checkEmail(): bool 71 | { 72 | // load the relevant list based on the class name e.g. BlackList or WhiteList 73 | $list = $this->getSubAccessControlList($this->getShortClassName()); 74 | 75 | if (!isset($list['userEmail'])) { 76 | // if list is not set do not check it 77 | return $this->getShortClassName() === 'whitelist' ? true : false; 78 | } 79 | 80 | return $this->isEmailInList($list); 81 | } 82 | 83 | /** 84 | * @param string $requestKey 85 | * @param string $listKey 86 | * @param string $subListKey 87 | * 88 | * @throws \Exception 89 | * 90 | * @return bool|null 91 | */ 92 | protected function findInListByRequestKey(string $requestKey, string $listKey, string $subListKey) 93 | { 94 | /** 95 | * load the relevant list to start checking 96 | * The list name is the called class name e.g. WhiteList in lowercase. 97 | */ 98 | $list = $this->getSubAccessControlList($listKey); 99 | 100 | // currently if list key is not set we do not check it 101 | if ($list === null || !isset($list[$subListKey])) { 102 | /* @noinspection PhpInconsistentReturnPointsInspection */ 103 | return; 104 | } 105 | 106 | return in_array($this->getRequest()[$requestKey], $list[$subListKey]); 107 | } 108 | 109 | /** 110 | * @return mixed 111 | */ 112 | protected function getShortClassName() 113 | { 114 | return $this->getClassUtility()->extractClassNameFromFullName(strtolower(get_called_class())); 115 | } 116 | 117 | /** 118 | * @return mixed 119 | */ 120 | public function getRequest() 121 | { 122 | return $this->request; 123 | } 124 | 125 | /** 126 | * @param mixed $request 127 | */ 128 | public function setRequest($request) 129 | { 130 | $this->request = $request; 131 | } 132 | 133 | /** 134 | * @return Dictionary 135 | */ 136 | public function getDictionary(): Dictionary 137 | { 138 | if (!isset($this->dictionary)) { 139 | $this->setDictionary(new Dictionary()); 140 | } 141 | 142 | return $this->dictionary; 143 | } 144 | 145 | /** 146 | * @param Dictionary $dictionary 147 | */ 148 | public function setDictionary(Dictionary $dictionary) 149 | { 150 | $this->dictionary = $dictionary; 151 | } 152 | 153 | /** 154 | * @return ApiClient 155 | */ 156 | public function getApiClient(): ApiClient 157 | { 158 | if (!isset($this->apiClient)) { 159 | $this->setApiClient(new ApiClient()); 160 | } 161 | 162 | return $this->apiClient; 163 | } 164 | 165 | /** 166 | * @param ApiClient $apiClient 167 | */ 168 | public function setApiClient(ApiClient $apiClient) 169 | { 170 | $this->apiClient = $apiClient; 171 | } 172 | 173 | /** 174 | * @throws \Exception 175 | * 176 | * @return array|bool 177 | */ 178 | public function getSlackUserInfo() 179 | { 180 | // get user id in the request 181 | $request = $this->getRequest(); 182 | 183 | // currently if user_id is not set we do not check it 184 | if (!isset($request['user_id'])) { 185 | return false; 186 | } 187 | 188 | /** 189 | * email normally does not exist in the request. 190 | * Get it by user_id. For this users:read and users:read.email are needed. 191 | */ 192 | $userInfo = $this->getApiClient()->userInfo(['user' => $request['user_id']]); 193 | if (empty($userInfo)) { 194 | /* 195 | * Could not find the user in the team 196 | * Probably there might be some issue with Access token and reading user info but block the access 197 | */ 198 | return false; 199 | } 200 | 201 | return $userInfo; 202 | } 203 | 204 | /** 205 | * @return ClassUtility 206 | */ 207 | public function getClassUtility(): ClassUtility 208 | { 209 | if (!isset($this->classUtility)) { 210 | $this->setClassUtility(new ClassUtility()); 211 | } 212 | 213 | return $this->classUtility; 214 | } 215 | 216 | /** 217 | * @param ClassUtility $classUtility 218 | */ 219 | public function setClassUtility(ClassUtility $classUtility) 220 | { 221 | $this->classUtility = $classUtility; 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/Botonomous/AbstractBaseSlack.php: -------------------------------------------------------------------------------- 1 | loadAttributes($this, $info); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Botonomous/AbstractBot.php: -------------------------------------------------------------------------------- 1 | config === null) { 45 | $this->config = (new Config()); 46 | } 47 | 48 | return $this->config; 49 | } 50 | 51 | /** 52 | * @param Config $config 53 | */ 54 | public function setConfig(Config $config) 55 | { 56 | $this->config = $config; 57 | } 58 | 59 | /** 60 | * @throws \Exception 61 | * 62 | * @return AbstractBaseListener 63 | */ 64 | public function getListener(): AbstractBaseListener 65 | { 66 | if (!isset($this->listener)) { 67 | $listenerClass = __NAMESPACE__.'\\listener\\'.ucwords($this->getConfig()->get('listener')).'Listener'; 68 | $this->setListener(new $listenerClass()); 69 | } 70 | 71 | return $this->listener; 72 | } 73 | 74 | public function setListener(AbstractBaseListener $listener) 75 | { 76 | $this->listener = $listener; 77 | } 78 | 79 | /** 80 | * @return MessageUtility 81 | */ 82 | public function getMessageUtility(): MessageUtility 83 | { 84 | if (!isset($this->messageUtility)) { 85 | $this->setMessageUtility(new MessageUtility()); 86 | } 87 | 88 | return $this->messageUtility; 89 | } 90 | 91 | /** 92 | * @param MessageUtility $messageUtility 93 | */ 94 | public function setMessageUtility(MessageUtility $messageUtility) 95 | { 96 | $this->messageUtility = $messageUtility; 97 | } 98 | 99 | /** 100 | * @return CommandContainer 101 | */ 102 | public function getCommandContainer(): CommandContainer 103 | { 104 | if (!isset($this->commandContainer)) { 105 | $this->setCommandContainer(new CommandContainer()); 106 | } 107 | 108 | return $this->commandContainer; 109 | } 110 | 111 | /** 112 | * @param CommandContainer $commandContainer 113 | */ 114 | public function setCommandContainer(CommandContainer $commandContainer) 115 | { 116 | $this->commandContainer = $commandContainer; 117 | } 118 | 119 | /** 120 | * @return FormattingUtility 121 | */ 122 | public function getFormattingUtility(): FormattingUtility 123 | { 124 | if (!isset($this->formattingUtility)) { 125 | $this->setFormattingUtility(new FormattingUtility()); 126 | } 127 | 128 | return $this->formattingUtility; 129 | } 130 | 131 | /** 132 | * @param FormattingUtility $formattingUtility 133 | */ 134 | public function setFormattingUtility(FormattingUtility $formattingUtility) 135 | { 136 | $this->formattingUtility = $formattingUtility; 137 | } 138 | 139 | /** 140 | * @return LoggerUtility 141 | */ 142 | public function getLoggerUtility(): LoggerUtility 143 | { 144 | if (!isset($this->loggerUtility)) { 145 | $this->setLoggerUtility(new LoggerUtility()); 146 | } 147 | 148 | return $this->loggerUtility; 149 | } 150 | 151 | /** 152 | * @param LoggerUtility $loggerUtility 153 | */ 154 | public function setLoggerUtility(LoggerUtility $loggerUtility) 155 | { 156 | $this->loggerUtility = $loggerUtility; 157 | } 158 | 159 | /** 160 | * @return OAuth 161 | */ 162 | public function getOauth(): OAuth 163 | { 164 | if (!isset($this->oauth)) { 165 | $this->setOauth(new OAuth()); 166 | } 167 | 168 | return $this->oauth; 169 | } 170 | 171 | /** 172 | * @param OAuth $oauth 173 | */ 174 | public function setOauth(OAuth $oauth) 175 | { 176 | $this->oauth = $oauth; 177 | } 178 | 179 | /** 180 | * @throws \Exception 181 | * 182 | * @return RequestUtility 183 | */ 184 | public function getRequestUtility(): RequestUtility 185 | { 186 | return $this->getListener()->getRequestUtility(); 187 | } 188 | 189 | /** 190 | * @param RequestUtility $requestUtility 191 | * 192 | * @throws \Exception 193 | */ 194 | public function setRequestUtility(RequestUtility $requestUtility) 195 | { 196 | $this->getListener()->setRequestUtility($requestUtility); 197 | } 198 | 199 | /** 200 | * @throws \Exception 201 | * 202 | * @return BlackList 203 | */ 204 | public function getBlackList(): BlackList 205 | { 206 | if (!isset($this->blackList)) { 207 | $this->setBlackList(new BlackList($this->getListener()->getRequest())); 208 | } 209 | 210 | return $this->blackList; 211 | } 212 | 213 | /** 214 | * @param BlackList $blackList 215 | */ 216 | public function setBlackList(BlackList $blackList) 217 | { 218 | $this->blackList = $blackList; 219 | } 220 | 221 | /** 222 | * @throws \Exception 223 | * 224 | * @return WhiteList 225 | */ 226 | public function getWhiteList(): WhiteList 227 | { 228 | if (!isset($this->whiteList)) { 229 | $this->setWhiteList(new WhiteList($this->getListener()->getRequest())); 230 | } 231 | 232 | return $this->whiteList; 233 | } 234 | 235 | /** 236 | * @param WhiteList $whiteList 237 | */ 238 | public function setWhiteList(WhiteList $whiteList) 239 | { 240 | $this->whiteList = $whiteList; 241 | } 242 | 243 | /** 244 | * @return Sender 245 | */ 246 | public function getSender(): Sender 247 | { 248 | if (!isset($this->sender)) { 249 | $this->setSender(new Sender($this)); 250 | } 251 | 252 | return $this->sender; 253 | } 254 | 255 | /** 256 | * @param Sender $sender 257 | */ 258 | public function setSender(Sender $sender) 259 | { 260 | $this->sender = $sender; 261 | } 262 | 263 | /** 264 | * @return Dictionary 265 | */ 266 | public function getDictionary(): Dictionary 267 | { 268 | if (!isset($this->dictionary)) { 269 | $this->setDictionary(new Dictionary()); 270 | } 271 | 272 | return $this->dictionary; 273 | } 274 | 275 | /** 276 | * @param Dictionary $dictionary 277 | */ 278 | public function setDictionary(Dictionary $dictionary) 279 | { 280 | $this->dictionary = $dictionary; 281 | } 282 | 283 | /** 284 | * @return CommandExtractor 285 | */ 286 | public function getCommandExtractor(): CommandExtractor 287 | { 288 | if (!isset($this->commandExtractor)) { 289 | $this->setCommandExtractor(new CommandExtractor()); 290 | } 291 | 292 | return $this->commandExtractor; 293 | } 294 | 295 | /** 296 | * @param CommandExtractor $commandExtractor 297 | */ 298 | public function setCommandExtractor(CommandExtractor $commandExtractor) 299 | { 300 | $this->commandExtractor = $commandExtractor; 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /src/Botonomous/AbstractCommandContainer.php: -------------------------------------------------------------------------------- 1 | getAllAsObject($key); 22 | 23 | if (!array_key_exists($key, $commands)) { 24 | /* @noinspection PhpInconsistentReturnPointsInspection */ 25 | return; 26 | } 27 | 28 | return $commands[$key]; 29 | } 30 | 31 | /** 32 | * @param $commands 33 | */ 34 | public function setAll($commands) 35 | { 36 | static::$commands = $commands; 37 | } 38 | 39 | /** 40 | * @return array 41 | */ 42 | public function getAll() 43 | { 44 | return static::$commands; 45 | } 46 | 47 | /** 48 | * @param string $key 49 | * 50 | * @throws \Exception 51 | * 52 | * @return mixed 53 | */ 54 | public function getAllAsObject(string $key = null) 55 | { 56 | $commands = $this->getAll(); 57 | if (!empty($commands)) { 58 | foreach ($commands as $commandKey => $commandDetails) { 59 | if (!empty($key) && $commandKey !== $key) { 60 | continue; 61 | } 62 | 63 | $commandDetails['key'] = $commandKey; 64 | $commands[$commandKey] = $this->mapToCommandObject($commandDetails); 65 | } 66 | } 67 | 68 | return $commands; 69 | } 70 | 71 | /** 72 | * @param array $row 73 | * 74 | * @throws \Exception 75 | * 76 | * @return Command 77 | */ 78 | private function mapToCommandObject(array $row) 79 | { 80 | $mappedObject = new Command($row['key']); 81 | 82 | unset($row['key']); 83 | 84 | if (!empty($row)) { 85 | foreach ($row as $key => $value) { 86 | $methodName = 'set'.ucwords($key); 87 | 88 | // check setter exists 89 | if (!method_exists($mappedObject, $methodName)) { 90 | continue; 91 | } 92 | 93 | $mappedObject->$methodName($value); 94 | } 95 | } 96 | 97 | return $mappedObject; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Botonomous/AbstractConfig.php: -------------------------------------------------------------------------------- 1 | getPluginConfigs($plugin); 32 | } catch (\Exception $e) { 33 | throw $e; 34 | } 35 | } 36 | 37 | if (!array_key_exists($key, $configs)) { 38 | throw new BotonomousException("Key: '{$key}' does not exist in configs"); 39 | } 40 | 41 | $found = $configs[$key]; 42 | 43 | return (new StringUtility())->applyReplacements($found, $replacements); 44 | } 45 | 46 | /** 47 | * @param $plugin 48 | * 49 | * @throws \Exception 50 | * 51 | * @return self 52 | */ 53 | private function getPluginConfigObject($plugin): self 54 | { 55 | $pluginConfigClass = __NAMESPACE__.'\\plugin\\'.strtolower($plugin) 56 | .'\\'.ucfirst($plugin).'Config'; 57 | 58 | if (!class_exists($pluginConfigClass)) { 59 | throw new BotonomousException("Config file: '{$pluginConfigClass}.php' does not exist"); 60 | } 61 | 62 | return new $pluginConfigClass(); 63 | } 64 | 65 | /** 66 | * @param $plugin 67 | * 68 | * @throws \Exception 69 | * 70 | * @return array 71 | */ 72 | public function getPluginConfigs($plugin) 73 | { 74 | try { 75 | return $this->getPluginConfigObject($plugin)->getConfigs(); 76 | } catch (\Exception $e) { 77 | throw $e; 78 | } 79 | } 80 | 81 | /** 82 | * @return array 83 | */ 84 | public function getConfigs(): array 85 | { 86 | return static::$configs; 87 | } 88 | 89 | /** 90 | * @param $key 91 | * @param $value 92 | * @param null $plugin 93 | * 94 | * @throws \Exception 95 | */ 96 | public function set($key, $value, $plugin = null) 97 | { 98 | if ($plugin !== null) { 99 | try { 100 | $this->getPluginConfigObject($plugin)->set($key, $value); 101 | } catch (\Exception $e) { 102 | throw $e; 103 | } 104 | } 105 | 106 | is_array($key) ? (new ArrayUtility())->setNestedArrayValue(static::$configs, $key, $value) 107 | : static::$configs[$key] = $value; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/Botonomous/AbstractSender.php: -------------------------------------------------------------------------------- 1 | loggerUtility)) { 25 | $this->setLoggerUtility(new LoggerUtility()); 26 | } 27 | 28 | return $this->loggerUtility; 29 | } 30 | 31 | /** 32 | * @param LoggerUtility $loggerUtility 33 | */ 34 | public function setLoggerUtility(LoggerUtility $loggerUtility) 35 | { 36 | $this->loggerUtility = $loggerUtility; 37 | } 38 | 39 | /** 40 | * @return Config 41 | */ 42 | public function getConfig(): Config 43 | { 44 | if (!isset($this->config)) { 45 | $this->config = (new Config()); 46 | } 47 | 48 | return $this->config; 49 | } 50 | 51 | /** 52 | * @param Config $config 53 | */ 54 | public function setConfig(Config $config) 55 | { 56 | $this->config = $config; 57 | } 58 | 59 | /** 60 | * @return ApiClient 61 | */ 62 | public function getApiClient(): ApiClient 63 | { 64 | if (!isset($this->apiClient)) { 65 | $this->setApiClient(new ApiClient()); 66 | } 67 | 68 | return $this->apiClient; 69 | } 70 | 71 | /** 72 | * @param ApiClient $apiClient 73 | */ 74 | public function setApiClient(ApiClient $apiClient) 75 | { 76 | $this->apiClient = $apiClient; 77 | } 78 | 79 | /** 80 | * @return Client 81 | */ 82 | public function getClient(): Client 83 | { 84 | if (!isset($this->client)) { 85 | $this->setClient(new Client()); 86 | } 87 | 88 | return $this->client; 89 | } 90 | 91 | /** 92 | * @param Client $client 93 | */ 94 | public function setClient(Client $client) 95 | { 96 | $this->client = $client; 97 | } 98 | 99 | /** 100 | * @return MessageUtility 101 | */ 102 | public function getMessageUtility(): MessageUtility 103 | { 104 | if (!isset($this->messageUtility)) { 105 | $this->setMessageUtility(new MessageUtility()); 106 | } 107 | 108 | return $this->messageUtility; 109 | } 110 | 111 | /** 112 | * @param MessageUtility $messageUtility 113 | */ 114 | public function setMessageUtility(MessageUtility $messageUtility) 115 | { 116 | $this->messageUtility = $messageUtility; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/Botonomous/AbstractSlackEntity.php: -------------------------------------------------------------------------------- 1 | slackId; 18 | } 19 | 20 | /** 21 | * @param string $slackId 22 | */ 23 | public function setSlackId(string $slackId) 24 | { 25 | $this->slackId = $slackId; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Botonomous/Action.php: -------------------------------------------------------------------------------- 1 | name; 18 | } 19 | 20 | /** 21 | * @param string $name 22 | */ 23 | public function setName(string $name) 24 | { 25 | $this->name = $name; 26 | } 27 | 28 | /** 29 | * @return string 30 | */ 31 | public function getText() 32 | { 33 | return $this->text; 34 | } 35 | 36 | /** 37 | * @param string $text 38 | */ 39 | public function setText(string $text) 40 | { 41 | $this->text = $text; 42 | } 43 | 44 | /** 45 | * @return string 46 | */ 47 | public function getValue(): string 48 | { 49 | return $this->value; 50 | } 51 | 52 | /** 53 | * @param string $value 54 | */ 55 | public function setValue(string $value) 56 | { 57 | $this->value = $value; 58 | } 59 | 60 | /** 61 | * @return string 62 | */ 63 | public function getType(): string 64 | { 65 | return $this->type; 66 | } 67 | 68 | /** 69 | * @param string $type 70 | */ 71 | public function setType(string $type) 72 | { 73 | $this->type = $type; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Botonomous/BlackList.php: -------------------------------------------------------------------------------- 1 | setRequest($request); 15 | } 16 | 17 | /** 18 | * @throws \Exception 19 | * 20 | * @return bool 21 | */ 22 | public function isBlackListed(): bool 23 | { 24 | return $this->isUsernameBlackListed() 25 | || $this->isUserIdBlackListed() 26 | || $this->isEmailBlackListed(); 27 | } 28 | 29 | /** 30 | * @throws \Exception 31 | * 32 | * @return bool 33 | */ 34 | public function isUsernameBlackListed(): bool 35 | { 36 | return $this->findInListByRequestKey('user_name', $this->getShortClassName(), 'username') === true 37 | ? true : false; 38 | } 39 | 40 | /** 41 | * @throws \Exception 42 | * 43 | * @return bool 44 | */ 45 | public function isUserIdBlackListed(): bool 46 | { 47 | return $this->findInListByRequestKey('user_id', $this->getShortClassName(), 'userId') === true 48 | ? true : false; 49 | } 50 | 51 | /** 52 | * @throws \Exception 53 | * 54 | * @return bool 55 | */ 56 | public function isEmailBlackListed() 57 | { 58 | return $this->checkEmail(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Botonomous/BotonomousException.php: -------------------------------------------------------------------------------- 1 | name; 18 | } 19 | 20 | /** 21 | * @param string $name 22 | */ 23 | public function setName(string $name) 24 | { 25 | $this->name = $name; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Botonomous/Command.php: -------------------------------------------------------------------------------- 1 | setKey($key); 28 | } 29 | 30 | /** 31 | * @return string 32 | */ 33 | public function getKey(): string 34 | { 35 | return $this->key; 36 | } 37 | 38 | /** 39 | * @param string $key 40 | */ 41 | public function setKey(string $key) 42 | { 43 | $this->key = $key; 44 | } 45 | 46 | /** 47 | * @return string 48 | */ 49 | public function getPlugin(): string 50 | { 51 | return $this->plugin; 52 | } 53 | 54 | /** 55 | * @param string $plugin 56 | */ 57 | public function setPlugin(string $plugin) 58 | { 59 | $this->plugin = $plugin; 60 | } 61 | 62 | /** 63 | * @return string 64 | */ 65 | public function getDescription(): string 66 | { 67 | return $this->description; 68 | } 69 | 70 | /** 71 | * @param string $description 72 | */ 73 | public function setDescription(string $description) 74 | { 75 | $this->description = $description; 76 | } 77 | 78 | /** 79 | * @return string 80 | */ 81 | public function getAction(): string 82 | { 83 | if (empty($this->action)) { 84 | $this->setAction(self::DEFAULT_ACTION); 85 | } 86 | 87 | return $this->action; 88 | } 89 | 90 | /** 91 | * @param string $action 92 | */ 93 | public function setAction(string $action) 94 | { 95 | $this->action = $action; 96 | } 97 | 98 | /** 99 | * @return string 100 | */ 101 | public function getClass(): string 102 | { 103 | if (empty($this->class)) { 104 | $class = __NAMESPACE__.'\\'.self::PLUGIN_DIR.'\\'.strtolower($this->getPlugin()).'\\'.$this->getPlugin(); 105 | $this->setClass($class); 106 | } 107 | 108 | return $this->class; 109 | } 110 | 111 | /** 112 | * @param string $class 113 | */ 114 | public function setClass(string $class) 115 | { 116 | $this->class = $class; 117 | } 118 | 119 | /** 120 | * @return array 121 | */ 122 | public function getKeywords() 123 | { 124 | return $this->keywords; 125 | } 126 | 127 | /** 128 | * @param array $keywords 129 | */ 130 | public function setKeywords(array $keywords) 131 | { 132 | $this->keywords = $keywords; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/Botonomous/CommandContainer.php: -------------------------------------------------------------------------------- 1 | [ 25 | self::PLUGIN_KEY => 'Ping', 26 | self::DESCRIPTION_KEY => self::HEALTH_CHECK_PLUGIN_DESCRIPTION, 27 | ], 28 | 'pong' => [ 29 | self::PLUGIN_KEY => 'Ping', 30 | self::ACTION_KEY => 'pong', 31 | self::DESCRIPTION_KEY => self::HEALTH_CHECK_PLUGIN_DESCRIPTION, 32 | ], 33 | 'commandWithoutFunctionForTest' => [ 34 | self::PLUGIN_KEY => 'Ping', 35 | self::ACTION_KEY => 'commandWithoutFunctionForTest', 36 | self::DESCRIPTION_KEY => self::HEALTH_CHECK_PLUGIN_DESCRIPTION, 37 | ], 38 | 'help' => [ 39 | self::PLUGIN_KEY => 'Help', 40 | self::DESCRIPTION_KEY => 'List all the available commands', 41 | self::KEYWORDS_KEY => [ 42 | 'help', 43 | 'ask', 44 | ], 45 | ], 46 | 'qa' => [ 47 | self::PLUGIN_KEY => 'QA', 48 | self::DESCRIPTION_KEY => 'Answer questions', 49 | ], 50 | ]; 51 | } 52 | -------------------------------------------------------------------------------- /src/Botonomous/CommandExtractor.php: -------------------------------------------------------------------------------- 1 | setError('Message is empty'); 32 | 33 | return; 34 | } 35 | 36 | /* 37 | * Process the message and find explicitly specified command 38 | */ 39 | return $this->getCommandObjectByMessage($message); 40 | } 41 | 42 | /** 43 | * @param string $message 44 | * 45 | * @throws \Exception 46 | * 47 | * @return array 48 | */ 49 | public function countKeywordOccurrence(string $message): array 50 | { 51 | $stemmer = new PorterStemmer(); 52 | 53 | // tokenize $message 54 | $stemmed = implode(' ', $stemmer->stemAll((new WhitespaceAndPunctuationTokenizer())->tokenize($message))); 55 | 56 | $count = []; 57 | foreach ($this->getCommandContainer()->getAllAsObject() as $commandKey => $commandObject) { 58 | $keywordsCount = $this->commandKeywordOccurrence($commandObject, $stemmed); 59 | 60 | $total = 0; 61 | if (empty($keywordsCount)) { 62 | $count[$commandKey] = $total; 63 | continue; 64 | } 65 | 66 | $count[$commandKey] = array_sum($keywordsCount); 67 | } 68 | 69 | return $count; 70 | } 71 | 72 | /** 73 | * @param Command $command 74 | * @param string $message 75 | * 76 | * @return array|void 77 | */ 78 | private function commandKeywordOccurrence(Command $command, string $message) 79 | { 80 | $stemmer = new PorterStemmer(); 81 | $keywords = $command->getKeywords(); 82 | if (empty($keywords)) { 83 | return; 84 | } 85 | 86 | return $this->getMessageUtility()->keywordCount( 87 | $stemmer->stemAll($keywords), 88 | $message 89 | ); 90 | } 91 | 92 | /** 93 | * @param string $message 94 | * 95 | * @throws \Exception 96 | * 97 | * @return Command|null 98 | */ 99 | private function getCommandObjectByMessage(string $message) 100 | { 101 | $command = $this->getMessageUtility()->extractCommandName($message); 102 | if (empty($command)) { 103 | $command = (new ArrayUtility())->maxPositiveValueKey($this->countKeywordOccurrence($message)); 104 | } 105 | 106 | // check command name 107 | if (empty($command)) { 108 | // get the default command if no command is find in the message 109 | $command = $this->checkDefaultCommand(); 110 | } 111 | 112 | return $this->getCommandObjectByCommand($command); 113 | } 114 | 115 | /** 116 | * @throws \Exception 117 | * 118 | * @return mixed|void 119 | */ 120 | private function checkDefaultCommand() 121 | { 122 | $command = $this->getConfig()->get('defaultCommand'); 123 | 124 | if (empty($command)) { 125 | $this->setError($this->getDictionary()->get('generic-messages')['noCommandMessage']); 126 | 127 | return; 128 | } 129 | 130 | return $command; 131 | } 132 | 133 | /** 134 | * @param $command 135 | * 136 | * @throws \Exception 137 | * 138 | * @return Command|void 139 | */ 140 | private function getCommandObjectByCommand($command) 141 | { 142 | if (empty($command)) { 143 | return; 144 | } 145 | 146 | $commandObject = $this->getCommandContainer()->getAsObject($command); 147 | 148 | if ($this->validateCommandObject($commandObject) !== true) { 149 | return; 150 | } 151 | 152 | return $commandObject; 153 | } 154 | 155 | /** 156 | * Validate the command object. 157 | * 158 | * @param Command|null $commandObject 159 | * 160 | * @throws \Exception 161 | * 162 | * @return bool 163 | */ 164 | private function validateCommandObject($commandObject): bool 165 | { 166 | // check command details 167 | if (empty($commandObject)) { 168 | $this->setError( 169 | $this->getDictionary()->getValueByKey('generic-messages', 'unknownCommandMessage') 170 | ); 171 | 172 | return false; 173 | } 174 | 175 | return true; 176 | } 177 | 178 | /** 179 | * @return string 180 | */ 181 | public function getError(): string 182 | { 183 | return $this->error; 184 | } 185 | 186 | /** 187 | * @param string $error 188 | */ 189 | public function setError(string $error) 190 | { 191 | $this->error = $error; 192 | } 193 | 194 | /** 195 | * @return Config 196 | */ 197 | public function getConfig(): Config 198 | { 199 | if ($this->config === null) { 200 | $this->config = (new Config()); 201 | } 202 | 203 | return $this->config; 204 | } 205 | 206 | /** 207 | * @param Config $config 208 | */ 209 | public function setConfig(Config $config) 210 | { 211 | $this->config = $config; 212 | } 213 | 214 | /** 215 | * @return MessageUtility 216 | */ 217 | public function getMessageUtility(): MessageUtility 218 | { 219 | if (!isset($this->messageUtility)) { 220 | $this->setMessageUtility(new MessageUtility()); 221 | } 222 | 223 | return $this->messageUtility; 224 | } 225 | 226 | /** 227 | * @param MessageUtility $messageUtility 228 | */ 229 | public function setMessageUtility(MessageUtility $messageUtility) 230 | { 231 | $this->messageUtility = $messageUtility; 232 | } 233 | 234 | /** 235 | * @return Dictionary 236 | */ 237 | public function getDictionary(): Dictionary 238 | { 239 | if (!isset($this->dictionary)) { 240 | $this->setDictionary(new Dictionary()); 241 | } 242 | 243 | return $this->dictionary; 244 | } 245 | 246 | /** 247 | * @param Dictionary $dictionary 248 | */ 249 | public function setDictionary(Dictionary $dictionary) 250 | { 251 | $this->dictionary = $dictionary; 252 | } 253 | 254 | /** 255 | * @return CommandContainer 256 | */ 257 | public function getCommandContainer(): CommandContainer 258 | { 259 | if (!isset($this->commandContainer)) { 260 | $this->setCommandContainer(new CommandContainer()); 261 | } 262 | 263 | return $this->commandContainer; 264 | } 265 | 266 | /** 267 | * @param CommandContainer $commandContainer 268 | */ 269 | public function setCommandContainer(CommandContainer $commandContainer) 270 | { 271 | $this->commandContainer = $commandContainer; 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /src/Botonomous/Config.php: -------------------------------------------------------------------------------- 1 | 'Australia/Melbourne', 14 | 'oAuthToken' => 'OAuth_Access_Token', 15 | 'botUserToken' => 'Bot_User_OAuth_Access_Token', 16 | 'botUserId' => 'YOUR_BOT_USER_ID', 17 | 'botUsername' => 'YOUR_BOT_USERNAME', 18 | 'logger' => [ 19 | 'enabled' => true, 20 | 'monolog' => [ 21 | 'channel' => 'logger', 22 | 'handlers' => [ 23 | 'file' => [ 24 | // should be full path 25 | 'fileName' => 'bot.log', 26 | ], 27 | ], 28 | ], 29 | ], 30 | 'iconURL' => 'YOUR_BOT_ICON_URL_48_BY_48', 31 | 'asUser' => true, 32 | // possible values are: SlashCommandListener::KEY, EventListener::KEY 33 | 'listener' => SlashCommandListener::KEY, 34 | // this is used if there is no command has been specified in the message 35 | 'defaultCommand' => 'qa', 36 | 'commandPrefix' => '/', 37 | /* 38 | * App credentials - This is required for Event listener 39 | * Can be found at https://api.slack.com/apps 40 | */ 41 | 'clientId' => 'YOUR_APP_CLIENT_ID', 42 | 'clientSecret' => 'YOUR_APP_SECRET', 43 | 'scopes' => ['bot'], 44 | /* 45 | * use this token to verify that requests are actually coming from Slack 46 | */ 47 | 'verificationToken' => 'YOUR_APP_VERIFICATION_TOKEN', 48 | 'appId' => 'YOUR_APP_ID', 49 | 'accessControlEnabled' => false, 50 | ]; 51 | } 52 | -------------------------------------------------------------------------------- /src/Botonomous/Dictionary.php: -------------------------------------------------------------------------------- 1 | jsonFileToArray($stopWordsPath); 31 | } 32 | 33 | /** 34 | * @return array 35 | */ 36 | public function getData() 37 | { 38 | return $this->data; 39 | } 40 | 41 | /** 42 | * @param array $data 43 | */ 44 | public function setData(array $data) 45 | { 46 | $this->data = $data; 47 | } 48 | 49 | /** 50 | * @param string $fileName 51 | * 52 | * @throws \Exception 53 | * 54 | * @return mixed 55 | */ 56 | public function get(string $fileName) 57 | { 58 | $data = $this->getData(); 59 | 60 | if (!isset($data[$fileName])) { 61 | $data[$fileName] = $this->load($fileName); 62 | $this->setData($data); 63 | } 64 | 65 | return $data[$fileName]; 66 | } 67 | 68 | /** 69 | * Return value for the key in the file content. 70 | * 71 | * @param $fileName 72 | * @param $key 73 | * @param array $replacements 74 | * 75 | * @throws \Exception 76 | * 77 | * @return mixed 78 | */ 79 | public function getValueByKey(string $fileName, string $key, array $replacements = []) 80 | { 81 | $fileContent = $this->get($fileName); 82 | 83 | if (!array_key_exists($key, $fileContent)) { 84 | throw new BotonomousException("Key: '{$key}' does not exist in file: {$fileName}"); 85 | } 86 | 87 | $found = $fileContent[$key]; 88 | 89 | return (new StringUtility())->applyReplacements($found, $replacements); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Botonomous/Event.php: -------------------------------------------------------------------------------- 1 | setType($type); 30 | } 31 | 32 | /** 33 | * @return string 34 | */ 35 | public function getType(): string 36 | { 37 | return $this->type; 38 | } 39 | 40 | /** 41 | * @param string $type 42 | */ 43 | public function setType(string $type) 44 | { 45 | $this->type = $type; 46 | } 47 | 48 | /** 49 | * @return string 50 | */ 51 | public function getUser(): string 52 | { 53 | return $this->user; 54 | } 55 | 56 | /** 57 | * @param string $user 58 | */ 59 | public function setUser(string $user) 60 | { 61 | $this->user = $user; 62 | } 63 | 64 | /** 65 | * @return string 66 | */ 67 | public function getText() 68 | { 69 | return $this->text; 70 | } 71 | 72 | /** 73 | * @param string $text 74 | */ 75 | public function setText(string $text) 76 | { 77 | $this->text = $text; 78 | } 79 | 80 | /** 81 | * @return string 82 | */ 83 | public function getTimestamp(): string 84 | { 85 | return $this->timestamp; 86 | } 87 | 88 | /** 89 | * @param string $timestamp 90 | */ 91 | public function setTimestamp(string $timestamp) 92 | { 93 | $this->timestamp = $timestamp; 94 | } 95 | 96 | /** 97 | * @return string 98 | */ 99 | public function getEventTimestamp(): string 100 | { 101 | return $this->eventTimestamp; 102 | } 103 | 104 | /** 105 | * @param string $eventTimestamp 106 | */ 107 | public function setEventTimestamp(string $eventTimestamp) 108 | { 109 | $this->eventTimestamp = $eventTimestamp; 110 | } 111 | 112 | /** 113 | * @return string 114 | */ 115 | public function getChannel() 116 | { 117 | return $this->channel; 118 | } 119 | 120 | /** 121 | * @param string $channel 122 | */ 123 | public function setChannel(string $channel) 124 | { 125 | $this->channel = $channel; 126 | } 127 | 128 | /** 129 | * @return string 130 | */ 131 | public function getBotId() 132 | { 133 | return $this->botId; 134 | } 135 | 136 | /** 137 | * @param string $botId 138 | */ 139 | public function setBotId(string $botId) 140 | { 141 | $this->botId = $botId; 142 | } 143 | 144 | /** 145 | * @return ApiClient 146 | */ 147 | public function getApiClient(): ApiClient 148 | { 149 | if (!isset($this->apiClient)) { 150 | $this->setApiClient(new ApiClient()); 151 | } 152 | 153 | return $this->apiClient; 154 | } 155 | 156 | /** 157 | * @param ApiClient $apiClient 158 | */ 159 | public function setApiClient(ApiClient $apiClient) 160 | { 161 | $this->apiClient = $apiClient; 162 | } 163 | 164 | /** 165 | * Check if the event belongs to a direct message. 166 | * 167 | * @throws \Exception 168 | * 169 | * @return bool|void 170 | */ 171 | public function isDirectMessage() 172 | { 173 | $imChannels = $this->getApiClient()->imListAsObject(); 174 | 175 | if (empty($imChannels)) { 176 | return; 177 | } 178 | 179 | foreach ($imChannels as $imChannel) { 180 | /** @var ImChannel $imChannel */ 181 | if ($imChannel->getUser() === 'USLACKBOT') { 182 | // ignore any direct conversation with the default slack bot 183 | continue; 184 | } 185 | 186 | // if IM Object id equals the event channel id the conversation is with the bot 187 | if ($imChannel->getSlackId() === $this->getChannel()) { 188 | return true; 189 | } 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/Botonomous/ImChannel.php: -------------------------------------------------------------------------------- 1 | isIm; 18 | } 19 | 20 | /** 21 | * @param bool $isIm 22 | */ 23 | public function setIm(bool $isIm) 24 | { 25 | $this->isIm = $isIm; 26 | } 27 | 28 | /** 29 | * @return string 30 | */ 31 | public function getUser(): string 32 | { 33 | return $this->user; 34 | } 35 | 36 | /** 37 | * @param string $user 38 | */ 39 | public function setUser(string $user) 40 | { 41 | $this->user = $user; 42 | } 43 | 44 | /** 45 | * @return string 46 | */ 47 | public function getCreated(): string 48 | { 49 | return $this->created; 50 | } 51 | 52 | /** 53 | * @param string $created 54 | */ 55 | public function setCreated(string $created) 56 | { 57 | $this->created = $created; 58 | } 59 | 60 | /** 61 | * @return bool 62 | */ 63 | public function isUserDeleted(): bool 64 | { 65 | return $this->isUserDeleted; 66 | } 67 | 68 | /** 69 | * @param bool $isUserDeleted 70 | */ 71 | public function setUserDeleted(bool $isUserDeleted) 72 | { 73 | $this->isUserDeleted = $isUserDeleted; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Botonomous/MessageAction.php: -------------------------------------------------------------------------------- 1 | actions; 25 | } 26 | 27 | /** 28 | * @param array $actions 29 | */ 30 | public function setActions(array $actions) 31 | { 32 | $this->actions = $actions; 33 | } 34 | 35 | /** 36 | * @param Action $action 37 | */ 38 | public function addAction(Action $action) 39 | { 40 | $actions = $this->getActions(); 41 | $actions[] = $action; 42 | $this->setActions($actions); 43 | } 44 | 45 | /** 46 | * @return string 47 | */ 48 | public function getCallbackId(): string 49 | { 50 | return $this->callbackId; 51 | } 52 | 53 | /** 54 | * @param string $callbackId 55 | */ 56 | public function setCallbackId(string $callbackId) 57 | { 58 | $this->callbackId = $callbackId; 59 | } 60 | 61 | /** 62 | * @return Team 63 | */ 64 | public function getTeam(): Team 65 | { 66 | return $this->team; 67 | } 68 | 69 | /** 70 | * @param mixed $team 71 | */ 72 | public function setTeam($team) 73 | { 74 | if ($team instanceof Team) { 75 | $this->team = $team; 76 | 77 | return; 78 | } 79 | 80 | // if array or json is passed create the object 81 | $this->team = (new Team())->load($team); 82 | } 83 | 84 | /** 85 | * @return Channel 86 | */ 87 | public function getChannel(): Channel 88 | { 89 | return $this->channel; 90 | } 91 | 92 | /** 93 | * @param mixed $channel 94 | */ 95 | public function setChannel($channel) 96 | { 97 | if ($channel instanceof Channel) { 98 | $this->channel = $channel; 99 | 100 | return; 101 | } 102 | 103 | // if array or json is passed create the object 104 | $this->channel = (new Channel())->load($channel); 105 | } 106 | 107 | /** 108 | * @return User 109 | */ 110 | public function getUser() 111 | { 112 | return $this->user; 113 | } 114 | 115 | /** 116 | * @param mixed $user 117 | */ 118 | public function setUser($user) 119 | { 120 | if ($user instanceof User) { 121 | $this->user = $user; 122 | 123 | return; 124 | } 125 | 126 | // if array or json is passed create the object 127 | $this->user = (new User())->load($user); 128 | } 129 | 130 | /** 131 | * @return string 132 | */ 133 | public function getActionTimestamp() 134 | { 135 | return $this->actionTimestamp; 136 | } 137 | 138 | /** 139 | * @param string $actionTimestamp 140 | */ 141 | public function setActionTimestamp($actionTimestamp) 142 | { 143 | $this->actionTimestamp = $actionTimestamp; 144 | } 145 | 146 | /** 147 | * @return string 148 | */ 149 | public function getMessageTimestamp() 150 | { 151 | return $this->messageTimestamp; 152 | } 153 | 154 | /** 155 | * @param string $messageTimestamp 156 | */ 157 | public function setMessageTimestamp($messageTimestamp) 158 | { 159 | $this->messageTimestamp = $messageTimestamp; 160 | } 161 | 162 | /** 163 | * @return string 164 | */ 165 | public function getAttachmentId() 166 | { 167 | return $this->attachmentId; 168 | } 169 | 170 | /** 171 | * @param string $attachmentId 172 | */ 173 | public function setAttachmentId($attachmentId) 174 | { 175 | $this->attachmentId = $attachmentId; 176 | } 177 | 178 | /** 179 | * @return string 180 | */ 181 | public function getToken() 182 | { 183 | return $this->token; 184 | } 185 | 186 | /** 187 | * @param string $token 188 | */ 189 | public function setToken($token) 190 | { 191 | $this->token = $token; 192 | } 193 | 194 | /** 195 | * @return string 196 | */ 197 | public function getOriginalMessage() 198 | { 199 | return $this->originalMessage; 200 | } 201 | 202 | /** 203 | * @param string $originalMessage 204 | */ 205 | public function setOriginalMessage($originalMessage) 206 | { 207 | $this->originalMessage = $originalMessage; 208 | } 209 | 210 | /** 211 | * @return string 212 | */ 213 | public function getResponseUrl() 214 | { 215 | return $this->responseUrl; 216 | } 217 | 218 | /** 219 | * @param string $responseUrl 220 | */ 221 | public function setResponseUrl($responseUrl) 222 | { 223 | $this->responseUrl = $responseUrl; 224 | } 225 | 226 | /** 227 | * @param $info 228 | * 229 | * @return AbstractBaseSlack 230 | */ 231 | public function load($info) 232 | { 233 | $thisObject = parent::load($info); 234 | 235 | $actions = []; 236 | foreach ($info['actions'] as $actionInfo) { 237 | // load action 238 | $actions[] = (new Action())->load($actionInfo); 239 | } 240 | $this->setActions($actions); 241 | /* 242 | * Finish adding actions 243 | */ 244 | 245 | return $thisObject; 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/Botonomous/Sender.php: -------------------------------------------------------------------------------- 1 | setSlackbot($slackbot); 26 | } 27 | 28 | /** 29 | * @return AbstractBot 30 | */ 31 | public function getSlackbot(): AbstractBot 32 | { 33 | return $this->slackbot; 34 | } 35 | 36 | /** 37 | * @param AbstractBot $slackbot 38 | */ 39 | public function setSlackbot(AbstractBot $slackbot) 40 | { 41 | $this->slackbot = $slackbot; 42 | } 43 | 44 | /** 45 | * Final endpoint for the response. 46 | * 47 | * @param $channel 48 | * @param $text 49 | * @param $attachments 50 | * 51 | * @throws \Exception 52 | * 53 | * @return bool 54 | */ 55 | public function send($text, $channel = null, $attachments = null) 56 | { 57 | // @codeCoverageIgnoreStart 58 | if ($this->getSlackbot()->getListener()->isThisBot() !== false) { 59 | return false; 60 | } 61 | // @codeCoverageIgnoreEnd 62 | 63 | if (empty($channel)) { 64 | $channel = $this->getSlackbot()->getListener()->getChannelId(); 65 | } 66 | 67 | $data = [ 68 | 'text' => $text, 69 | 'channel' => $channel, 70 | ]; 71 | 72 | if ($attachments !== null) { 73 | $data['attachments'] = json_encode($attachments); 74 | } 75 | 76 | $this->getLoggerUtility()->logChat(__METHOD__, $text, $channel); 77 | 78 | $responseType = $this->getResponseType(); 79 | if ($responseType === self::SLACK_RESPONSE_TYPE) { 80 | $this->respondToSlack($data); 81 | } elseif ($responseType === self::SLASH_COMMAND_RESPONSE_TYPE) { 82 | $this->respondToSlashCommand($text); 83 | } elseif ($responseType === self::JSON_RESPONSE_TYPE) { 84 | $this->respondAsJSON($data); 85 | } 86 | 87 | return true; 88 | } 89 | 90 | /** 91 | * @param $response 92 | */ 93 | private function respondToSlashCommand($response) 94 | { 95 | /** @noinspection PhpUndefinedClassInspection */ 96 | $request = new Request( 97 | 'POST', 98 | $this->getSlackbot()->getRequest('response_url'), 99 | ['Content-Type' => 'application/json'], 100 | json_encode([ 101 | 'text' => $response, 102 | 'response_type' => 'in_channel', 103 | ]) 104 | ); 105 | 106 | /* @noinspection PhpUndefinedClassInspection */ 107 | $this->getClient()->send($request); 108 | } 109 | 110 | /** 111 | * @param $data 112 | * 113 | * @throws \Exception 114 | */ 115 | private function respondToSlack($data) 116 | { 117 | $this->getApiClient()->chatPostMessage($data); 118 | } 119 | 120 | /** 121 | * @param $data 122 | */ 123 | private function respondAsJSON($data) 124 | { 125 | // headers_sent is used to avoid issue in the test 126 | if (!headers_sent()) { 127 | header('Content-type:application/json;charset=utf-8'); 128 | } 129 | 130 | echo json_encode($data); 131 | } 132 | 133 | /** 134 | * Specify the response type 135 | * If response in config is set to empty, it will be considered based on listener. 136 | * 137 | * @throws \Exception 138 | * 139 | * @return mixed|string 140 | */ 141 | private function getResponseType() 142 | { 143 | if ($this->getSlackbot()->getRequest('debug') === true 144 | || $this->getSlackbot()->getRequest('debug') === 'true') { 145 | return self::JSON_RESPONSE_TYPE; 146 | } 147 | 148 | // response type in the config is empty, so choose it based on listener type 149 | return $this->getResponseByListenerType(); 150 | } 151 | 152 | /** 153 | * @throws \Exception 154 | * 155 | * @return string 156 | */ 157 | private function getResponseByListenerType(): string 158 | { 159 | $listener = $this->getConfig()->get('listener'); 160 | switch ($listener) { 161 | case SlashCommandListener::KEY: 162 | return self::SLASH_COMMAND_RESPONSE_TYPE; 163 | case EventListener::KEY: 164 | return self::SLACK_RESPONSE_TYPE; 165 | default: 166 | return self::SLASH_COMMAND_RESPONSE_TYPE; 167 | } 168 | } 169 | 170 | /** 171 | * Send confirmation. 172 | * 173 | * @throws BotonomousException 174 | */ 175 | public function sendConfirmation() 176 | { 177 | try { 178 | $userId = $this->getSlackbot()->getRequest('user_id'); 179 | 180 | $user = ''; 181 | if (!empty($userId)) { 182 | $user = $this->getMessageUtility()->linkToUser($userId).' '; 183 | } 184 | 185 | $confirmMessage = $this->getSlackbot()->getDictionary()->getValueByKey( 186 | 'generic-messages', 187 | 'confirmReceivedMessage', 188 | ['user' => $user] 189 | ); 190 | 191 | if (!empty($confirmMessage)) { 192 | $this->send($confirmMessage); 193 | } 194 | } catch (\Exception $e) { 195 | throw new BotonomousException('Failed to send confirmatyion: '.$e->getMessage()); 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/Botonomous/Team.php: -------------------------------------------------------------------------------- 1 | name; 21 | } 22 | 23 | /** 24 | * @param string $name 25 | */ 26 | public function setName(string $name) 27 | { 28 | $this->name = $name; 29 | } 30 | 31 | /** 32 | * @return string 33 | */ 34 | public function getDomain(): string 35 | { 36 | return $this->domain; 37 | } 38 | 39 | /** 40 | * @param string $domain 41 | */ 42 | public function setDomain(string $domain) 43 | { 44 | $this->domain = $domain; 45 | } 46 | 47 | /** 48 | * @return string 49 | */ 50 | public function getEmailDomain(): string 51 | { 52 | return $this->emailDomain; 53 | } 54 | 55 | /** 56 | * @param string $emailDomain 57 | */ 58 | public function setEmailDomain(string $emailDomain) 59 | { 60 | $this->emailDomain = $emailDomain; 61 | } 62 | 63 | /** 64 | * @return array 65 | */ 66 | public function getIcon(): array 67 | { 68 | return $this->icon; 69 | } 70 | 71 | /** 72 | * @param array $icon 73 | */ 74 | public function setIcon(array $icon) 75 | { 76 | $this->icon = $icon; 77 | } 78 | 79 | /** 80 | * @return bool 81 | */ 82 | public function isIconDefault(): bool 83 | { 84 | $icon = $this->getIcon(); 85 | 86 | return isset($icon['image_default']) && $icon['image_default'] === true ? true : false; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Botonomous/User.php: -------------------------------------------------------------------------------- 1 | name; 18 | } 19 | 20 | /** 21 | * @param string $name 22 | */ 23 | public function setName(string $name) 24 | { 25 | $this->name = $name; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Botonomous/WhiteList.php: -------------------------------------------------------------------------------- 1 | setRequest($request); 15 | } 16 | 17 | /** 18 | * @throws \Exception 19 | * 20 | * @return bool 21 | */ 22 | public function isWhiteListed(): bool 23 | { 24 | $usernameCheck = true; 25 | $userIdCheck = true; 26 | $userEmailCheck = true; 27 | 28 | if ($this->isUsernameWhiteListed() === false) { 29 | $usernameCheck = false; 30 | } 31 | 32 | if ($this->isUserIdWhiteListed() === false) { 33 | $userIdCheck = false; 34 | } 35 | 36 | if ($this->isEmailWhiteListed() === false) { 37 | $userEmailCheck = false; 38 | } 39 | 40 | return $usernameCheck === true && $userIdCheck === true && $userEmailCheck === true ? true : false; 41 | } 42 | 43 | /** 44 | * @throws \Exception 45 | * 46 | * @return bool 47 | */ 48 | public function isUsernameWhiteListed(): bool 49 | { 50 | return empty($this->findInListByRequestKey('user_name', $this->getShortClassName(), 'username')) ? false : true; 51 | } 52 | 53 | /** 54 | * @throws \Exception 55 | * 56 | * @return bool 57 | */ 58 | public function isUserIdWhiteListed(): bool 59 | { 60 | return empty($this->findInListByRequestKey('user_id', $this->getShortClassName(), 'userId')) ? false : true; 61 | } 62 | 63 | /** 64 | * @throws \Exception 65 | * 66 | * @return bool 67 | */ 68 | public function isEmailWhiteListed() 69 | { 70 | return $this->checkEmail(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Botonomous/client/AbstractClient.php: -------------------------------------------------------------------------------- 1 | client = $client; 27 | } 28 | 29 | /** @noinspection PhpUndefinedClassInspection 30 | * @return Client|null 31 | */ 32 | public function getClient() 33 | { 34 | if (!isset($this->client)) { 35 | /* @noinspection PhpUndefinedClassInspection */ 36 | $this->setClient(new Client()); 37 | } 38 | 39 | return $this->client; 40 | } 41 | 42 | /** 43 | * @return ArrayUtility 44 | */ 45 | public function getArrayUtility() 46 | { 47 | if (!isset($this->arrayUtility)) { 48 | $this->setArrayUtility(new ArrayUtility()); 49 | } 50 | 51 | return $this->arrayUtility; 52 | } 53 | 54 | /** 55 | * @param ArrayUtility $arrayUtility 56 | */ 57 | public function setArrayUtility(ArrayUtility $arrayUtility) 58 | { 59 | $this->arrayUtility = $arrayUtility; 60 | } 61 | 62 | /** 63 | * @return Config 64 | */ 65 | public function getConfig() 66 | { 67 | if (!isset($this->config)) { 68 | $this->setConfig(new Config()); 69 | } 70 | 71 | return $this->config; 72 | } 73 | 74 | /** 75 | * @param Config $config 76 | */ 77 | public function setConfig(Config $config) 78 | { 79 | $this->config = $config; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Botonomous/dictionary/access-control.json: -------------------------------------------------------------------------------- 1 | { 2 | "whitelist": { 3 | "username": [], 4 | "userId": [], 5 | "userEmail": [] 6 | }, 7 | "blacklist": { 8 | "username": [], 9 | "userId": [], 10 | "userEmail": [] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Botonomous/dictionary/generic-messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "noCommandMessage": "Sorry. I couldn't find any command in your message. List the available commands using /help", 3 | "blacklistedMessage": "Sorry, we cannot process your message as we detected it in the blacklist", 4 | "whitelistedMessage": "Sorry, we cannot process your message as we could not find it in whitelist", 5 | "unknownCommandMessage": "Sorry. I do not know anything about your command. List the available commands using /help", 6 | "confirmReceivedMessage": ":point_right: {user}I've received your message and am thinking about that ..." 7 | } 8 | -------------------------------------------------------------------------------- /src/Botonomous/dictionary/punctuations.json: -------------------------------------------------------------------------------- 1 | ["?", "!", ",", ";", "."] 2 | -------------------------------------------------------------------------------- /src/Botonomous/dictionary/question-answer.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hi": { 3 | "answers": ["Hi there :raised_hand_with_fingers_splayed:", "Hello"] 4 | }, 5 | "How are you?": { 6 | "answers": ["I'm fine. Thank you :facepunch:"] 7 | }, 8 | "Who are you?": { 9 | "answers": ["My name is :tada: *Botonomous* :tada: a PHP framework to help you create awesome Slack bots :robot_face:"] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Botonomous/dictionary/stopwords-en.json: -------------------------------------------------------------------------------- 1 | ["a","a's","able","about","above","according","accordingly","across","actually","after","afterwards","again","against","ain't","all","allow","allows","almost","alone","along","already","also","although","always","am","among","amongst","an","and","another","any","anybody","anyhow","anyone","anything","anyway","anyways","anywhere","apart","appear","appreciate","appropriate","are","aren't","around","as","aside","ask","asking","associated","at","available","away","awfully","b","be","became","because","become","becomes","becoming","been","before","beforehand","behind","being","believe","below","beside","besides","best","better","between","beyond","both","brief","but","by","c","c'mon","c's","came","can","can't","cannot","cant","cause","causes","certain","certainly","changes","clearly","co","com","come","comes","concerning","consequently","consider","considering","contain","containing","contains","corresponding","could","couldn't","course","currently","d","definitely","described","despite","did","didn't","different","do","does","doesn't","doing","don't","done","down","downwards","during","e","each","edu","eg","eight","either","else","elsewhere","enough","entirely","especially","et","etc","even","ever","every","everybody","everyone","everything","everywhere","ex","exactly","example","except","f","far","few","fifth","first","five","followed","following","follows","for","former","formerly","forth","four","from","further","furthermore","g","get","gets","getting","given","gives","go","goes","going","gone","got","gotten","greetings","h","had","hadn't","happens","hardly","has","hasn't","have","haven't","having","he","he's","hello","help","hence","her","here","here's","hereafter","hereby","herein","hereupon","hers","herself","hi","him","himself","his","hither","hopefully","how","howbeit","however","i","i'd","i'll","i'm","i've","ie","if","ignored","immediate","in","inasmuch","inc","indeed","indicate","indicated","indicates","inner","insofar","instead","into","inward","is","isn't","it","it'd","it'll","it's","its","itself","j","just","k","keep","keeps","kept","know","known","knows","l","last","lately","later","latter","latterly","least","less","lest","let","let's","like","liked","likely","little","look","looking","looks","ltd","m","mainly","many","may","maybe","me","mean","meanwhile","merely","might","more","moreover","most","mostly","much","must","my","myself","n","name","namely","nd","near","nearly","necessary","need","needs","neither","never","nevertheless","new","next","nine","no","nobody","non","none","nor","normally","not","nothing","novel","now","nowhere","o","obviously","of","off","often","oh","ok","okay","old","on","once","one","ones","only","onto","or","other","others","otherwise","ought","our","ours","ourselves","out","outside","over","overall","own","p","particular","particularly","per","perhaps","placed","please","plus","possible","presumably","probably","provides","q","que","quite","qv","r","rather","rd","re","really","reasonably","regarding","regardless","regards","relatively","respectively","right","s","said","same","saw","say","saying","says","second","secondly","see","seeing","seem","seemed","seeming","seems","seen","self","selves","sensible","sent","serious","seriously","seven","several","shall","she","should","shouldn't","since","six","so","some","somebody","somehow","someone","something","sometime","sometimes","somewhat","somewhere","soon","sorry","specified","specify","specifying","still","sub","such","sup","sure","t","t's","take","taken","tell","tends","th","than","thank","thanks","that","that's","the","their","theirs","them","themselves","then","thence","there","there's","thereafter","thereby","therefore","therein","thereupon","these","they","they'd","they'll","they're","they've","think","third","this","thorough","thoroughly","those","though","three","through","throughout","thus","to","together","too","took","toward","towards","tried","tries","truly","try","trying","twice","two","u","un","under","unfortunately","unless","unlikely","until","unto","up","upon","us","use","used","useful","uses","using","usually","v","value","various","very","via","viz","vs","w","want","wants","was","wasn't","way","we","we'd","we'll","we're","we've","welcome","well","went","were","weren't","what","what's","whatever","when","whence","whenever","where","where's","whereas","whereby","wherein","whereupon","wherever","whether","which","while","whither","who","who's","whoever","whole","whom","whose","why","will","willing","wish","with","within","without","won't","wonder","would","wouldn't","x","y","yes","yet","you","you'd","you'll","you're","you've","your","yours","yourself","yourselves","z","zero"] 2 | -------------------------------------------------------------------------------- /src/Botonomous/dictionary/test-key-value.json: -------------------------------------------------------------------------------- 1 | { 2 | "testKey": "testValue", 3 | "testKeyWithReplacements": "testValue {user}" 4 | } 5 | -------------------------------------------------------------------------------- /src/Botonomous/dictionary/test.json: -------------------------------------------------------------------------------- 1 | ["test1", "test2"] 2 | -------------------------------------------------------------------------------- /src/Botonomous/listener/AbstractBaseListener.php: -------------------------------------------------------------------------------- 1 | respondOK(); 30 | 31 | $request = $this->extractRequest(); 32 | 33 | if (empty($request)) { 34 | /* @noinspection PhpInconsistentReturnPointsInspection */ 35 | return; 36 | } 37 | 38 | $this->setRequest($request); 39 | 40 | if ($this->isThisBot() !== false) { 41 | /* @noinspection PhpInconsistentReturnPointsInspection */ 42 | return; 43 | } 44 | 45 | return $request; 46 | } 47 | 48 | /** 49 | * @return mixed 50 | */ 51 | abstract public function extractRequest(); 52 | 53 | /** 54 | * @return string 55 | */ 56 | abstract public function getChannelId(): string; 57 | 58 | /** 59 | * @param null|string $key 60 | * 61 | * @return mixed 62 | */ 63 | public function getRequest(string $key = null) 64 | { 65 | if (!isset($this->request)) { 66 | // each listener has its own way of extracting the request 67 | $this->setRequest($this->extractRequest()); 68 | } 69 | 70 | if ($key === null) { 71 | // return the entire request since key is null 72 | return $this->request; 73 | } 74 | 75 | if (is_array($this->request) && array_key_exists($key, $this->request)) { 76 | return $this->request[$key]; 77 | } 78 | } 79 | 80 | /** 81 | * @param array $request 82 | */ 83 | public function setRequest($request) 84 | { 85 | $this->request = $request; 86 | } 87 | 88 | /** 89 | * @return Config 90 | */ 91 | public function getConfig(): Config 92 | { 93 | if (!isset($this->config)) { 94 | $this->setConfig(new Config()); 95 | } 96 | 97 | return $this->config; 98 | } 99 | 100 | /** 101 | * @param Config $config 102 | */ 103 | public function setConfig(Config $config) 104 | { 105 | $this->config = $config; 106 | } 107 | 108 | /** 109 | * Verify the request comes from Slack 110 | * Each listener must have have this and has got its own way to check the request. 111 | * 112 | * @throws \Exception 113 | * 114 | * @return array 115 | */ 116 | abstract public function verifyOrigin(); 117 | 118 | /** 119 | * Check if the request belongs to the bot itself. 120 | * 121 | * @throws \Exception 122 | * 123 | * @return bool 124 | */ 125 | abstract public function isThisBot(): bool; 126 | 127 | /** 128 | * @return RequestUtility 129 | */ 130 | public function getRequestUtility(): RequestUtility 131 | { 132 | if (!isset($this->requestUtility)) { 133 | $this->setRequestUtility((new RequestUtility())); 134 | } 135 | 136 | return $this->requestUtility; 137 | } 138 | 139 | /** 140 | * @param RequestUtility $requestUtility 141 | */ 142 | public function setRequestUtility(RequestUtility $requestUtility) 143 | { 144 | $this->requestUtility = $requestUtility; 145 | } 146 | 147 | /** 148 | * respondOK. 149 | */ 150 | protected function respondOK() 151 | { 152 | // check if fastcgi_finish_request is callable 153 | if (is_callable('fastcgi_finish_request')) { 154 | /* 155 | * http://stackoverflow.com/a/38918192 156 | * This works in Nginx but the next approach not 157 | */ 158 | session_write_close(); 159 | fastcgi_finish_request(); 160 | 161 | /* @noinspection PhpInconsistentReturnPointsInspection */ 162 | return; 163 | } 164 | 165 | ignore_user_abort(true); 166 | 167 | ob_start(); 168 | header($this->getRequestUtility()->getServerProtocol().' 200 OK'); 169 | // Disable compression (in case content length is compressed). 170 | header('Content-Encoding: none'); 171 | header('Content-Length: '.ob_get_length()); 172 | 173 | // Close the connection. 174 | header('Connection: close'); 175 | 176 | ob_end_flush(); 177 | // only if an output buffer is active do ob_flush 178 | if (ob_get_level() > 0) { 179 | ob_flush(); 180 | } 181 | 182 | flush(); 183 | 184 | /* @noinspection PhpInconsistentReturnPointsInspection */ 185 | } 186 | 187 | /** 188 | * @throws \Exception 189 | * 190 | * @return array 191 | */ 192 | public function verifyRequest(): array 193 | { 194 | $originCheck = $this->verifyOrigin(); 195 | if (!isset($originCheck[self::ORIGIN_VERIFICATION_SUCCESS_KEY])) { 196 | throw new BotonomousException('Success must be provided in verifyOrigin response'); 197 | } 198 | 199 | if ($originCheck[self::ORIGIN_VERIFICATION_SUCCESS_KEY] !== true) { 200 | return [ 201 | self::ORIGIN_VERIFICATION_SUCCESS_KEY => false, 202 | self::ORIGIN_VERIFICATION_MESSAGE_KEY => $originCheck[self::ORIGIN_VERIFICATION_MESSAGE_KEY], 203 | ]; 204 | } 205 | 206 | if ($this->isThisBot() !== false) { 207 | return [ 208 | self::ORIGIN_VERIFICATION_SUCCESS_KEY => false, 209 | self::ORIGIN_VERIFICATION_MESSAGE_KEY => 'Request comes from the bot', 210 | ]; 211 | } 212 | 213 | return [self::ORIGIN_VERIFICATION_SUCCESS_KEY => true, self::ORIGIN_VERIFICATION_MESSAGE_KEY => 'Yay!']; 214 | } 215 | 216 | /** 217 | * @return string|null 218 | */ 219 | public function determineAction() 220 | { 221 | $utility = $this->getRequestUtility(); 222 | $getRequest = $utility->getGet(); 223 | 224 | if (!empty($getRequest['action'])) { 225 | return strtolower($getRequest['action']); 226 | } 227 | 228 | $request = $utility->getPostedBody(); 229 | 230 | if (isset($request['type']) && $request['type'] === 'url_verification') { 231 | return 'url_verification'; 232 | } 233 | } 234 | 235 | /** 236 | * Return message based on the listener 237 | * If listener is event and event text is empty, fall back to request text. 238 | * 239 | * @throws \Exception 240 | * 241 | * @return mixed|string 242 | */ 243 | public function getMessage() 244 | { 245 | if ($this instanceof EventListener && $this->getEvent() instanceof Event) { 246 | $message = $this->getEvent()->getText(); 247 | 248 | if (!empty($message)) { 249 | return $message; 250 | } 251 | } 252 | 253 | return $this->getRequest('text'); 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/Botonomous/listener/EventListener.php: -------------------------------------------------------------------------------- 1 | 'timestamp', 26 | 'event_ts' => 'eventTimestamp', 27 | ]; 28 | 29 | /** 30 | * @return mixed 31 | */ 32 | public function extractRequest() 33 | { 34 | return $this->getRequestUtility()->getPostedBody(); 35 | } 36 | 37 | /** 38 | * @return string 39 | */ 40 | public function getToken(): string 41 | { 42 | return $this->token; 43 | } 44 | 45 | /** 46 | * @param string $token 47 | */ 48 | public function setToken(string $token) 49 | { 50 | $this->token = $token; 51 | } 52 | 53 | /** 54 | * @return string 55 | */ 56 | public function getTeamId(): string 57 | { 58 | return $this->teamId; 59 | } 60 | 61 | /** 62 | * @param string $teamId 63 | */ 64 | public function setTeamId(string $teamId) 65 | { 66 | $this->teamId = $teamId; 67 | } 68 | 69 | /** 70 | * @return string 71 | */ 72 | public function getAppId(): string 73 | { 74 | return $this->appId; 75 | } 76 | 77 | /** 78 | * @param string $appId 79 | */ 80 | public function setAppId(string $appId) 81 | { 82 | $this->appId = $appId; 83 | } 84 | 85 | /** 86 | * @throws \Exception 87 | * 88 | * @return Event 89 | */ 90 | public function getEvent() 91 | { 92 | if (!isset($this->event)) { 93 | $this->loadEvent(); 94 | } 95 | 96 | return $this->event; 97 | } 98 | 99 | /** 100 | * @param Event $event 101 | */ 102 | public function setEvent(Event $event) 103 | { 104 | $this->event = $event; 105 | } 106 | 107 | /** 108 | * @throws \Exception 109 | */ 110 | private function loadEvent() 111 | { 112 | $request = $this->getRequest(); 113 | $eventKey = 'event'; 114 | 115 | if (!isset($request[$eventKey])) { 116 | return; 117 | } 118 | 119 | $request = $request[$eventKey]; 120 | if (!isset($request['type'])) { 121 | throw new BotonomousException(self::MISSING_EVENT_TYPE_MESSAGE); 122 | } 123 | 124 | // create the event 125 | $eventObject = new Event($request['type']); 126 | 127 | // exclude type from the args since it's already passed 128 | unset($request['type']); 129 | 130 | $stringUtility = new StringUtility(); 131 | foreach ($request as $argKey => $argValue) { 132 | if (array_key_exists($argKey, $this->requestEventMaps)) { 133 | $argKey = $this->requestEventMaps[$argKey]; 134 | } 135 | 136 | $setterName = 'set'.$stringUtility->snakeCaseToCamelCase($argKey); 137 | 138 | // ignore calling the method if setter does not exist 139 | if (!method_exists($eventObject, $setterName)) { 140 | continue; 141 | } 142 | 143 | $eventObject->$setterName($argValue); 144 | } 145 | 146 | // set it 147 | $this->setEvent($eventObject); 148 | } 149 | 150 | /** 151 | * @throws \Exception 152 | * 153 | * @return array 154 | */ 155 | public function verifyOrigin() 156 | { 157 | $request = $this->getRequest(); 158 | 159 | if (!isset($request['token']) || !isset($request['api_app_id'])) { 160 | return [ 161 | parent::ORIGIN_VERIFICATION_SUCCESS_KEY => false, 162 | parent::ORIGIN_VERIFICATION_MESSAGE_KEY => self::MISSING_TOKEN_OR_APP_ID_MESSAGE, 163 | ]; 164 | } 165 | 166 | $verificationToken = $this->getConfig()->get('verificationToken'); 167 | 168 | if (empty($verificationToken)) { 169 | throw new BotonomousException('Verification token must be provided'); 170 | } 171 | 172 | $expectedAppId = $this->getConfig()->get('appId'); 173 | 174 | if (empty($expectedAppId)) { 175 | throw new BotonomousException(self::MISSING_APP_ID_MESSAGE); 176 | } 177 | 178 | if ($verificationToken === $request['token'] && 179 | $expectedAppId === $request['api_app_id']) { 180 | return [ 181 | parent::ORIGIN_VERIFICATION_SUCCESS_KEY => true, 182 | parent::ORIGIN_VERIFICATION_MESSAGE_KEY => 'O La la!', 183 | ]; 184 | } 185 | 186 | return [ 187 | parent::ORIGIN_VERIFICATION_SUCCESS_KEY => false, 188 | parent::ORIGIN_VERIFICATION_MESSAGE_KEY => 'Token or api_app_id mismatch', 189 | ]; 190 | } 191 | 192 | /** 193 | * Check if the request belongs to the bot itself. 194 | * 195 | * @throws \Exception 196 | * 197 | * @return bool 198 | */ 199 | public function isThisBot(): bool 200 | { 201 | $subType = $this->getRequest('subtype'); 202 | 203 | if ($subType === 'bot_message') { 204 | return true; 205 | } 206 | 207 | $event = $this->getEvent(); 208 | 209 | return $event instanceof Event && !empty($event->getBotId()); 210 | } 211 | 212 | /** 213 | * @throws \Exception 214 | * 215 | * @return string 216 | */ 217 | public function getChannelId(): string 218 | { 219 | return $this->getEvent()->getChannel(); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/Botonomous/listener/SlashCommandListener.php: -------------------------------------------------------------------------------- 1 | getRequestUtility()->getPost(); 20 | 21 | if (empty($postRequest)) { 22 | /* @noinspection PhpInconsistentReturnPointsInspection */ 23 | return; 24 | } 25 | 26 | return $postRequest; 27 | } 28 | 29 | /** 30 | * @throws \Exception 31 | * 32 | * @return array 33 | */ 34 | public function verifyOrigin(): array 35 | { 36 | $token = $this->getRequest('token'); 37 | 38 | if (empty($token)) { 39 | return [ 40 | parent::ORIGIN_VERIFICATION_SUCCESS_KEY => false, 41 | parent::ORIGIN_VERIFICATION_MESSAGE_KEY => self::MISSING_TOKEN_MESSAGE, 42 | ]; 43 | } 44 | 45 | $expectedToken = $this->getConfig()->get(self::VERIFICATION_TOKEN); 46 | 47 | if (empty($expectedToken)) { 48 | throw new BotonomousException(self::MISSING_TOKEN_CONFIG_MESSAGE); 49 | } 50 | 51 | if ($token === $expectedToken) { 52 | return [ 53 | parent::ORIGIN_VERIFICATION_SUCCESS_KEY => true, 54 | parent::ORIGIN_VERIFICATION_MESSAGE_KEY => 'Awesome!', 55 | ]; 56 | } 57 | 58 | return [ 59 | parent::ORIGIN_VERIFICATION_SUCCESS_KEY => false, 60 | parent::ORIGIN_VERIFICATION_MESSAGE_KEY => 'Token is not valid', 61 | ]; 62 | } 63 | 64 | /** 65 | * @throws \Exception 66 | * 67 | * @return bool 68 | */ 69 | public function isThisBot(): bool 70 | { 71 | $userId = $this->getRequest('user_id'); 72 | $username = $this->getRequest('user_name'); 73 | 74 | return ($userId == 'USLACKBOT' || $username == 'slackbot') ? true : false; 75 | } 76 | 77 | /** 78 | * @return string 79 | */ 80 | public function getChannelId(): string 81 | { 82 | return $this->getRequest('channel_id'); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Botonomous/plugin/AbstractPlugin.php: -------------------------------------------------------------------------------- 1 | setSlackbot($slackbot); 28 | } 29 | 30 | /** 31 | * Return Botonomous. 32 | * 33 | * @return Slackbot 34 | */ 35 | public function getSlackbot(): Slackbot 36 | { 37 | return $this->slackbot; 38 | } 39 | 40 | /** 41 | * Set Botonomous. 42 | * 43 | * @param Slackbot $slackbot 44 | */ 45 | public function setSlackbot($slackbot) 46 | { 47 | $this->slackbot = $slackbot; 48 | } 49 | 50 | /** 51 | * @return Dictionary 52 | */ 53 | public function getDictionary(): Dictionary 54 | { 55 | if (!isset($this->dictionary)) { 56 | $this->setDictionary((new Dictionary())); 57 | } 58 | 59 | return $this->dictionary; 60 | } 61 | 62 | /** 63 | * @param Dictionary $dictionary 64 | */ 65 | public function setDictionary(Dictionary $dictionary) 66 | { 67 | $this->dictionary = $dictionary; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Botonomous/plugin/PluginInterface.php: -------------------------------------------------------------------------------- 1 | getSlackbot()->getCommands(); 22 | 23 | $response = ''; 24 | if (!empty($allCommands)) { 25 | $formattingUtility = (new FormattingUtility()); 26 | 27 | foreach ($allCommands as $commandName => $commandObject) { 28 | if (!$commandObject instanceof Command) { 29 | continue; 30 | } 31 | 32 | $response .= "/{$commandName} - ".$commandObject->getDescription().$formattingUtility->newLine(); 33 | } 34 | } 35 | 36 | return $response; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Botonomous/plugin/help/HelpConfig.php: -------------------------------------------------------------------------------- 1 | 'testConfigValue', 12 | ]; 13 | } 14 | -------------------------------------------------------------------------------- /src/Botonomous/plugin/ping/Ping.php: -------------------------------------------------------------------------------- 1 | getQuestions(); 23 | $stringUtility = new StringUtility(); 24 | 25 | if (empty($questions)) { 26 | return ''; 27 | } 28 | 29 | foreach ($questions as $question => $questionInfo) { 30 | if ($stringUtility->findInString($question, $this->getSlackbot()->getListener()->getMessage())) { 31 | // found - return random answer 32 | $answers = $questionInfo['answers']; 33 | 34 | return $answers[array_rand($answers)]; 35 | } 36 | } 37 | 38 | return ''; 39 | } 40 | 41 | /** 42 | * @throws \Exception 43 | * 44 | * @return array 45 | */ 46 | public function getQuestions(): array 47 | { 48 | if (!isset($this->questions)) { 49 | $this->setQuestions($this->getDictionary()->get('question-answer')); 50 | } 51 | 52 | return $this->questions; 53 | } 54 | 55 | /** 56 | * @param array $questions 57 | */ 58 | public function setQuestions(array $questions) 59 | { 60 | $this->questions = $questions; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Botonomous/public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | RewriteEngine On 3 | 4 | # redirect www to non www 5 | RewriteBase / 6 | RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC] 7 | RewriteRule ^(.*)$ http://%1/$1 [R=301,L] 8 | 9 | # redirect all to index.php 10 | RewriteCond %{REQUEST_FILENAME} !-d 11 | RewriteCond %{REQUEST_FILENAME} !-f 12 | RewriteRule ^([^?]*)$ index.php?path=$1 [NC,L,QSA] 13 | 14 | -------------------------------------------------------------------------------- /src/Botonomous/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iranianpep/botonomous/294c1395ec1e86d1784b5c134f195b7b2024e0ee/src/Botonomous/public/favicon.png -------------------------------------------------------------------------------- /src/Botonomous/public/index.php: -------------------------------------------------------------------------------- 1 | run(); 21 | } catch (Exception $e) { 22 | echo $e->getMessage(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Botonomous/utility/AbstractUtility.php: -------------------------------------------------------------------------------- 1 | setConfig($config); 23 | } 24 | } 25 | 26 | /** 27 | * @return Config 28 | */ 29 | public function getConfig(): Config 30 | { 31 | if ($this->config === null) { 32 | $this->config = new Config(); 33 | } 34 | 35 | return $this->config; 36 | } 37 | 38 | /** 39 | * @param Config $config 40 | */ 41 | public function setConfig(Config $config) 42 | { 43 | $this->config = $config; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Botonomous/utility/ArrayUtility.php: -------------------------------------------------------------------------------- 1 | 0; 39 | } 40 | 41 | return true; 42 | } 43 | 44 | /** 45 | * Set a value in a nested array based on path. 46 | * 47 | * @param array $array The array to modify 48 | * @param array $path The path in the array 49 | * @param mixed $value The value to set 50 | * 51 | * @return void 52 | */ 53 | public function setNestedArrayValue(array &$array, array $path, &$value) 54 | { 55 | $current = &$array; 56 | foreach ($path as $key) { 57 | $current = &$current[$key]; 58 | } 59 | 60 | $current = $value; 61 | } 62 | 63 | /** 64 | * Get a value in a nested array based on path 65 | * See https://stackoverflow.com/a/9628276/419887. 66 | * 67 | * @param array $array The array to modify 68 | * @param array $path The path in the array 69 | * 70 | * @return mixed 71 | */ 72 | public function getNestedArrayValue(array &$array, array $path) 73 | { 74 | $current = &$array; 75 | foreach ($path as $key) { 76 | $current = &$current[$key]; 77 | } 78 | 79 | return $current; 80 | } 81 | 82 | /** 83 | * @param $array 84 | * 85 | * @return mixed 86 | */ 87 | public function sortArrayByLength(array $array) 88 | { 89 | usort($array, function ($array1, $array2) { 90 | return strlen($array2) <=> strlen($array1); 91 | }); 92 | 93 | return $array; 94 | } 95 | 96 | /** 97 | * Find the key for the max positive value. 98 | * 99 | * @param $array 100 | * 101 | * @return mixed 102 | */ 103 | public function maxPositiveValueKey(array $array) 104 | { 105 | $maxValue = max($array); 106 | 107 | if ($maxValue > 0) { 108 | return array_search($maxValue, $array); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Botonomous/utility/ClassUtility.php: -------------------------------------------------------------------------------- 1 | $attributeValue) { 39 | $method = $this->getSetMethodByAttribute($object, $attributeKey); 40 | 41 | // ignore if setter function does not exist 42 | if (empty($method)) { 43 | continue; 44 | } 45 | 46 | $object->$method($attributeValue); 47 | } 48 | 49 | return $object; 50 | } 51 | 52 | /** 53 | * @param AbstractBaseSlack $object 54 | * @param $attributeKey 55 | * 56 | * @return bool|string 57 | */ 58 | private function getSetMethodByAttribute(AbstractBaseSlack $object, string $attributeKey) 59 | { 60 | // For id, we cannot use 'set'.$stringUtility->snakeCaseToCamelCase($attributeKey) since it's named slackId 61 | if ($attributeKey === 'id') { 62 | return 'setSlackId'; 63 | } 64 | 65 | // handle ts because there is setTimestamp instead of setTs 66 | $camelCase = (new StringUtility())->snakeCaseToCamelCase($this->processTimestamp($attributeKey)); 67 | 68 | /** 69 | * If camel case attribute starts with 'is', 'has', ... following by an uppercase letter, remove it 70 | * This is used to handle calling functions such as setIm or setUserDeleted 71 | * instead of setIsIm or setIsUserDeleted. 72 | * 73 | * The style checkers complain about functions such as setIsIm, ... 74 | */ 75 | $function = 'set'.$this->removeBooleanPrefix($camelCase); 76 | 77 | return method_exists($object, $function) ? $function : false; 78 | } 79 | 80 | /** 81 | * If text is 'ts' or ends with '_ts' replace it with 'timestamp'. 82 | * 83 | * @param $text 84 | * 85 | * @return mixed 86 | */ 87 | private function processTimestamp(string $text): string 88 | { 89 | if ($text === 'ts' || (new StringUtility())->endsWith($text, '_ts')) { 90 | // replace the last ts with timestamp 91 | $text = preg_replace('/ts$/', 'timestamp', $text); 92 | } 93 | 94 | return $text; 95 | } 96 | 97 | /** 98 | * Check if the text starts with boolean prefixes such as 'is', 'has', ... 99 | * 100 | * @param $text 101 | * 102 | * @return mixed 103 | */ 104 | private function findBooleanPrefix(string $text) 105 | { 106 | $booleanPrefixes = ['is', 'has']; 107 | sort($booleanPrefixes); 108 | 109 | foreach ($booleanPrefixes as $booleanPrefix) { 110 | if (!preg_match('/^((?i)'.$booleanPrefix.')[A-Z0-9]/', $text)) { 111 | continue; 112 | } 113 | 114 | return $booleanPrefix; 115 | } 116 | } 117 | 118 | /** 119 | * If find the boolean prefix, remove it. 120 | * 121 | * @param $text 122 | * 123 | * @return string 124 | */ 125 | private function removeBooleanPrefix(string $text): string 126 | { 127 | $booleanPrefix = $this->findBooleanPrefix($text); 128 | if (!empty($booleanPrefix)) { 129 | // found the boolean prefix - remove it 130 | $text = substr($text, strlen($booleanPrefix)); 131 | } 132 | 133 | return $text; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/Botonomous/utility/FileUtility.php: -------------------------------------------------------------------------------- 1 | jsonToArray(file_get_contents($filePath)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Botonomous/utility/FormattingUtility.php: -------------------------------------------------------------------------------- 1 | tokenize($text); 22 | 23 | $stemmed = (new PorterStemmer())->stemAll($tokens); 24 | 25 | return implode(' ', $stemmed); 26 | } 27 | 28 | /** 29 | * @param $text 30 | * @param string $language 31 | * 32 | * @throws \Exception 33 | * 34 | * @return string 35 | */ 36 | public function removeStopWords($text, $language = 'en') 37 | { 38 | $stopWords = (new Dictionary())->get('stopwords-'.$language); 39 | 40 | $words = explode(' ', $text); 41 | if (!empty($words)) { 42 | foreach ($words as $key => $word) { 43 | if (in_array($word, $stopWords)) { 44 | unset($words[$key]); 45 | } 46 | } 47 | } 48 | 49 | return implode(' ', $words); 50 | } 51 | 52 | /** 53 | * @param $text 54 | * 55 | * @throws \Exception 56 | * 57 | * @return string 58 | */ 59 | public function removePunctuations($text) 60 | { 61 | $punctuations = (new Dictionary())->get('punctuations'); 62 | 63 | return str_replace($punctuations, '', $text); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Botonomous/utility/MessageUtility.php: -------------------------------------------------------------------------------- 1 | getUserLink(); 25 | 26 | return preg_replace("/{$userLink}/", '', $message, 1); 27 | } 28 | 29 | /** 30 | * Check if the bot user id is mentioned in the message. 31 | * 32 | * @param $message 33 | * 34 | * @throws \Exception 35 | * 36 | * @return bool 37 | */ 38 | public function isBotMentioned(string $message): bool 39 | { 40 | $userLink = $this->getUserLink(); 41 | 42 | return (new StringUtility())->findInString($userLink, $message, false); 43 | } 44 | 45 | /** 46 | * Return command name in the message. 47 | * 48 | * @param string $message 49 | * 50 | * @throws \Exception 51 | * 52 | * @return null|string 53 | */ 54 | public function extractCommandName(string $message) 55 | { 56 | // remove the bot mention if it exists 57 | $message = $this->removeMentionedBot($message); 58 | 59 | /** 60 | * Command must start with / and at the beginning of the sentence. 61 | */ 62 | $commandPrefix = $this->getConfig()->get('commandPrefix'); 63 | $commandPrefix = preg_quote($commandPrefix, '/'); 64 | 65 | $pattern = '/^('.$commandPrefix.'\w{1,})/'; 66 | 67 | preg_match($pattern, ltrim($message), $groups); 68 | 69 | // If command is found, remove command prefix from the beginning of the command 70 | return isset($groups[1]) ? ltrim($groups[1], $commandPrefix) : null; 71 | } 72 | 73 | /** 74 | * Return command details in the message. 75 | * 76 | * @param string $message 77 | * 78 | * @throws \Exception 79 | * 80 | * @return \Botonomous\Command|null 81 | */ 82 | public function extractCommandDetails(string $message) 83 | { 84 | // first get the command name 85 | $command = $this->extractCommandName($message); 86 | 87 | // then get the command details 88 | return (new CommandContainer())->getAsObject($command); 89 | } 90 | 91 | /** 92 | * @param $triggerWord 93 | * @param $message 94 | * 95 | * @return string 96 | */ 97 | public function removeTriggerWord(string $message, string $triggerWord) 98 | { 99 | $count = 1; 100 | 101 | return ltrim(str_replace($triggerWord, '', $message, $count)); 102 | } 103 | 104 | /** 105 | * @param $userId 106 | * @param string $userName 107 | * 108 | * @throws \Exception 109 | * 110 | * @return string 111 | */ 112 | public function linkToUser(string $userId, string $userName = '') 113 | { 114 | if (empty($userId)) { 115 | throw new BotonomousException('User id is not provided'); 116 | } 117 | 118 | if (!empty($userName)) { 119 | $userName = "|{$userName}"; 120 | } 121 | 122 | return "<@{$userId}{$userName}>"; 123 | } 124 | 125 | /** 126 | * @throws \Exception 127 | * 128 | * @return string 129 | */ 130 | private function getUserLink(): string 131 | { 132 | return $this->linkToUser($this->getConfig()->get('botUserId')); 133 | } 134 | 135 | /** 136 | * @param array $keywords 137 | * @param $message 138 | * 139 | * @return array 140 | */ 141 | public function keywordPos(array $keywords, string $message): array 142 | { 143 | $found = []; 144 | if (empty($keywords)) { 145 | return $found; 146 | } 147 | 148 | foreach ((new ArrayUtility())->sortArrayByLength($keywords) as $keyword) { 149 | foreach ((new StringUtility())->findPositionInString($keyword, $message) as $position) { 150 | // check if the keyword does not overlap with one of the already found 151 | if ($this->isPositionTaken($found, $position) === false) { 152 | $found[$keyword][] = $position; 153 | } 154 | } 155 | } 156 | 157 | return $found; 158 | } 159 | 160 | /** 161 | * @param array $keywords 162 | * @param $message 163 | * 164 | * @return array|void 165 | */ 166 | public function keywordCount(array $keywords, string $message) 167 | { 168 | $keysPositions = $this->keywordPos($keywords, $message); 169 | 170 | if (empty($keysPositions)) { 171 | return; 172 | } 173 | 174 | foreach ($keysPositions as $key => $positions) { 175 | $keysPositions[$key] = count($positions); 176 | } 177 | 178 | return $keysPositions; 179 | } 180 | 181 | /** 182 | * @param array $tokensPositions 183 | * @param $newPosition 184 | * 185 | * @return bool 186 | */ 187 | private function isPositionTaken(array $tokensPositions, int $newPosition) 188 | { 189 | if (empty($tokensPositions)) { 190 | return false; 191 | } 192 | 193 | foreach ($tokensPositions as $token => $positions) { 194 | $tokenLength = strlen($token); 195 | if ($this->isPositionIn($newPosition, $positions, $tokenLength) === true) { 196 | return true; 197 | } 198 | } 199 | 200 | return false; 201 | } 202 | 203 | /** 204 | * @param $newPosition 205 | * @param $positions 206 | * @param $tokenLength 207 | * 208 | * @return bool 209 | */ 210 | private function isPositionIn(int $newPosition, array $positions, int $tokenLength) 211 | { 212 | foreach ($positions as $position) { 213 | if ($newPosition >= $position && $newPosition < $position + $tokenLength) { 214 | return true; 215 | } 216 | } 217 | 218 | return false; 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/Botonomous/utility/RequestUtility.php: -------------------------------------------------------------------------------- 1 | content)) { 20 | return $this->content; 21 | } 22 | 23 | return file_get_contents('php://input'); 24 | } 25 | 26 | /** 27 | * @param $content 28 | */ 29 | public function setContent(string $content) 30 | { 31 | $this->content = $content; 32 | } 33 | 34 | /** 35 | * @return mixed 36 | */ 37 | public function getPost() 38 | { 39 | if (isset($this->post)) { 40 | return $this->post; 41 | } 42 | 43 | return filter_input_array(INPUT_POST); 44 | } 45 | 46 | /** 47 | * @return mixed 48 | */ 49 | public function getPostedBody() 50 | { 51 | $body = $this->getContent(); 52 | 53 | if (empty($body)) { 54 | /* @noinspection PhpInconsistentReturnPointsInspection */ 55 | return; 56 | } 57 | 58 | return json_decode($body, true); 59 | } 60 | 61 | /** 62 | * @param array $post 63 | */ 64 | public function setPost(array $post) 65 | { 66 | $this->post = $post; 67 | } 68 | 69 | /** 70 | * @return mixed 71 | */ 72 | public function getGet() 73 | { 74 | if (isset($this->get)) { 75 | return $this->get; 76 | } 77 | 78 | return filter_input_array(INPUT_GET); 79 | } 80 | 81 | /** 82 | * @param array $get 83 | */ 84 | public function setGet(array $get) 85 | { 86 | $this->get = $get; 87 | } 88 | 89 | /** 90 | * @return mixed 91 | */ 92 | public function getServerProtocol() 93 | { 94 | return filter_input(INPUT_SERVER, 'SERVER_PROTOCOL', FILTER_SANITIZE_STRING); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Botonomous/utility/SecurityUtility.php: -------------------------------------------------------------------------------- 1 | getHashAlgorithm(), uniqid(mt_rand(), true)); 26 | } 27 | 28 | /** 29 | * @param string $hashAlgorithm 30 | * 31 | * @throws \Exception 32 | */ 33 | public function setHashAlgorithm(string $hashAlgorithm) 34 | { 35 | /* 36 | * check if hashing algorithm is valid 37 | */ 38 | if (!in_array($hashAlgorithm, hash_algos(), true)) { 39 | throw new BotonomousException('Hash algorithm is not valid'); 40 | } 41 | 42 | $this->hashAlgorithm = $hashAlgorithm; 43 | } 44 | 45 | /** 46 | * @throws \Exception 47 | * 48 | * @return string 49 | */ 50 | public function getHashAlgorithm(): string 51 | { 52 | if (empty($this->hashAlgorithm)) { 53 | $this->setHashAlgorithm(self::DEFAULT_HASH_ALGORITHM); 54 | } 55 | 56 | return $this->hashAlgorithm; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Botonomous/utility/SessionUtility.php: -------------------------------------------------------------------------------- 1 | getSession(); 44 | $session[$key] = $value; 45 | $this->setSession($session); 46 | } 47 | 48 | /** 49 | * @param $key 50 | * 51 | * @return mixed 52 | */ 53 | public function get($key) 54 | { 55 | $session = $this->getSession(); 56 | 57 | if (!isset($session[$key])) { 58 | /* @noinspection PhpInconsistentReturnPointsInspection */ 59 | return; 60 | } 61 | 62 | return $session[$key]; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Botonomous/utility/StringUtility.php: -------------------------------------------------------------------------------- 1 | getFindPattern($toFind, $wordBoundary); 62 | 63 | return preg_match($pattern, $subject) ? true : false; 64 | } 65 | 66 | /** 67 | * @param string $toFind 68 | * @param string $subject 69 | * @param bool $wordBoundary 70 | * 71 | * @return mixed 72 | */ 73 | public function findPositionInString(string $toFind, string $subject, bool $wordBoundary = true) 74 | { 75 | $pattern = $this->getFindPattern($toFind, $wordBoundary); 76 | $positions = []; 77 | if (preg_match_all($pattern, $subject, $matches, PREG_OFFSET_CAPTURE) && !empty($matches[0])) { 78 | foreach ($matches[0] as $match) { 79 | $positions[] = $match[1]; 80 | } 81 | } 82 | 83 | return $positions; 84 | } 85 | 86 | /** 87 | * Convert snake case to camel case e.g. admin_user becomes AdminUser. 88 | * 89 | * @param string $string 90 | * 91 | * @return string 92 | */ 93 | public function snakeCaseToCamelCase(string $string) 94 | { 95 | return str_replace(' ', '', ucwords(str_replace('_', ' ', $string))); 96 | } 97 | 98 | /** 99 | * Check subject to see whether $string1 is followed by $string2. 100 | * 101 | * @param string $subject 102 | * @param string $string1 103 | * @param string $string2 104 | * @param array $exceptions 105 | * 106 | * @return bool 107 | */ 108 | public function isString1FollowedByString2( 109 | string $subject, 110 | string $string1, 111 | string $string2, 112 | array $exceptions = [] 113 | ): bool { 114 | $exceptionsString = ''; 115 | if (!empty($exceptions)) { 116 | $exceptions = implode('|', $exceptions); 117 | $exceptionsString = "(? $value) { 158 | $subject = str_replace('{'.$key.'}', $value, $subject); 159 | } 160 | 161 | return $subject; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /tests/Botonomous/ActionTest.php: -------------------------------------------------------------------------------- 1 | load($info); 17 | 18 | $this->assertEquals('Springfield, MA, USA', $action->getName()); 19 | $this->assertEquals('button', $action->getType()); 20 | $this->assertEquals('Springfield, MA, USA', $action->getValue()); 21 | $this->assertEmpty($action->getText()); 22 | } 23 | 24 | public function testGetText() 25 | { 26 | $action = new Action(); 27 | $action->setText('blah blah'); 28 | 29 | $this->assertEquals('blah blah', $action->getText()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Botonomous/CommandContainerTest.php: -------------------------------------------------------------------------------- 1 | getAsObject('ping'); 21 | 22 | $this->assertTrue($commandObject instanceof Command); 23 | 24 | /* @noinspection PhpUndefinedMethodInspection */ 25 | $this->assertEquals($commandObject->getPlugin(), 'Ping'); 26 | } 27 | 28 | /** 29 | * Test getAll. 30 | */ 31 | public function testGetAllAsObject() 32 | { 33 | $commands = (new CommandContainer())->getAllAsObject(); 34 | /* @noinspection PhpUndefinedMethodInspection */ 35 | $this->assertEquals($commands['ping']->getPlugin(), 'Ping'); 36 | } 37 | 38 | /** 39 | * Test getAll. 40 | */ 41 | public function testGetAllAsObjectUnknownSetter() 42 | { 43 | $commands = new CommandContainer(); 44 | $allCommands = $commands->getAll(); 45 | 46 | // set unknown attribute / property 47 | $allCommands['ping']['testKey'] = 'testValue'; 48 | 49 | $commands->setAll($allCommands); 50 | 51 | $commandObjects = $commands->getAllAsObject(); 52 | 53 | /* @noinspection PhpUndefinedMethodInspection */ 54 | $this->assertEquals($commandObjects['ping']->getPlugin(), 'Ping'); 55 | } 56 | 57 | /** 58 | * Test getAll. 59 | */ 60 | public function testGetAllAsObjectEmpty() 61 | { 62 | $commands = new CommandContainer(); 63 | 64 | $allCommands = $commands->getAll(); 65 | 66 | $commands->setAll([]); 67 | 68 | /* @noinspection PhpUndefinedMethodInspection */ 69 | $this->assertEquals([], $commands->getAllAsObject()); 70 | 71 | // reset the command container 72 | $commands->setAll($allCommands); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tests/Botonomous/CommandTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(Command::DEFAULT_ACTION, (new Command(self::PING_KEY))->getAction()); 16 | } 17 | 18 | public function testGetKey() 19 | { 20 | $this->assertEquals(self::PING_KEY, (new Command(self::PING_KEY))->getKey()); 21 | } 22 | 23 | public function testGetKeywords() 24 | { 25 | $command = new Command(self::PING_KEY); 26 | $keywords = ['help']; 27 | $command->setKeywords($keywords); 28 | 29 | $this->assertEquals($keywords, $command->getKeywords()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Botonomous/ConfigTest.php: -------------------------------------------------------------------------------- 1 | set('testKey', 'testValue'); 21 | 22 | $this->assertEquals('testValue', $config->get('testKey')); 23 | } 24 | 25 | public function testGetByPlugin() 26 | { 27 | $config = new Config(); 28 | $result = $config->get('testConfigKey', [], 'help'); 29 | 30 | $this->assertEquals('testConfigValue', $result); 31 | } 32 | 33 | public function testGetByInvalidPlugin() 34 | { 35 | $config = new Config(); 36 | 37 | $this->expectException('\Exception'); 38 | $this->expectExceptionMessage( 39 | "Config file: 'Botonomous\\plugin\\dummy\\DummyConfig.php' does not exist" 40 | ); 41 | 42 | $config->get('testConfigKey', [], 'dummy'); 43 | } 44 | 45 | /** 46 | * @throws \Exception 47 | */ 48 | public function testGetWithReplace() 49 | { 50 | $config = new Config(); 51 | $config->set('testKeyReplace', 'testValue {replaceIt}'); 52 | 53 | $this->assertEquals( 54 | 'testValue replaced', 55 | $config->get('testKeyReplace', ['replaceIt' => 'replaced']) 56 | ); 57 | } 58 | 59 | /** 60 | * Test getExceptException. 61 | */ 62 | public function testGetExceptException() 63 | { 64 | try { 65 | (new Config())->get('dummyKey'); 66 | } catch (\Exception $e) { 67 | $this->assertEquals('Key: \'dummyKey\' does not exist in configs', $e->getMessage()); 68 | } 69 | } 70 | 71 | /** 72 | * @throws \Exception 73 | */ 74 | public function testSet() 75 | { 76 | $config = new Config(); 77 | 78 | $config->set('testKey', 'testNewValue'); 79 | 80 | $this->assertEquals('testNewValue', $config->get('testKey')); 81 | 82 | $config->set('testConfigKey', 'testConfigValueEdited', 'help'); 83 | 84 | $this->assertEquals('testConfigValueEdited', $config->get('testConfigKey', [], 'help')); 85 | 86 | // reset config 87 | $config->set('testConfigKey', 'testConfigValue', 'help'); 88 | } 89 | 90 | /** 91 | * @throws \Exception 92 | */ 93 | public function testSetExceptException() 94 | { 95 | $this->expectException('\Exception'); 96 | $invalidPluginPath = 'Botonomous\plugin\dummyinvalidplugin\DummyInvalidPluginConfig.php'; 97 | $this->expectExceptionMessage( 98 | "Config file: '{$invalidPluginPath}' does not exist" 99 | ); 100 | 101 | $config = new Config(); 102 | $config->set('testConfigKey', 'testConfigValueEdited', 'dummyInvalidPlugin'); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /tests/Botonomous/DictionaryTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($expected, $dictionary->get('test')); 26 | 27 | // get it again to check load only is called once 28 | $this->assertEquals($expected, $dictionary->get('test')); 29 | } 30 | 31 | /** 32 | * Test getValueByKey. 33 | */ 34 | public function testGetValueByKey() 35 | { 36 | $dictionary = new Dictionary(); 37 | 38 | $this->assertEquals('testValue', $dictionary->getValueByKey('test-key-value', 'testKey')); 39 | } 40 | 41 | /** 42 | * Test getValueByKey. 43 | */ 44 | public function testGetValueByInvalidKey() 45 | { 46 | $dictionary = new Dictionary(); 47 | 48 | $this->expectException('\Exception'); 49 | $this->expectExceptionMessage("Key: 'testInvalidKey' does not exist in file: test-key-value"); 50 | 51 | $this->assertEquals('testValue', $dictionary->getValueByKey('test-key-value', 'testInvalidKey')); 52 | } 53 | 54 | /** 55 | * Test getValueByKey. 56 | */ 57 | public function testGetValueByKeyWithReplacements() 58 | { 59 | $dictionary = new Dictionary(); 60 | 61 | $this->assertEquals( 62 | 'testValue dummy user', 63 | $dictionary->getValueByKey( 64 | 'test-key-value', 65 | 'testKeyWithReplacements', 66 | ['user' => 'dummy user'] 67 | ) 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/Botonomous/EventTest.php: -------------------------------------------------------------------------------- 1 | setBotId('B123'); 22 | 23 | $this->assertEquals('B123', $event->getBotId()); 24 | } 25 | 26 | /** 27 | * @throws \Exception 28 | */ 29 | public function testIsDirectMessageEmpty() 30 | { 31 | $event = new Event('message'); 32 | $this->assertEmpty($event->isDirectMessage()); 33 | } 34 | 35 | /** 36 | * Test isDirectMessageEmpty. 37 | */ 38 | public function testIsDirectMessage() 39 | { 40 | $event = new Event('message'); 41 | $event->setChannel('D024BE7RE'); 42 | 43 | $apiClientTest = new ApiClientTest(); 44 | $apiClient = $apiClientTest->getApiClient($this->getDummyImListResponse()); 45 | 46 | $event->setApiClient($apiClient); 47 | 48 | $this->assertEquals(true, $event->isDirectMessage()); 49 | } 50 | 51 | /** 52 | * Test isDirectMessageEmpty. 53 | */ 54 | public function testIsNotDirectMessage() 55 | { 56 | $event = new Event('message'); 57 | 58 | $apiClientTest = new ApiClientTest(); 59 | $apiClient = $apiClientTest->getApiClient($this->getDummyImListResponse()); 60 | 61 | $event->setApiClient($apiClient); 62 | 63 | $this->assertEmpty($event->isDirectMessage()); 64 | } 65 | 66 | /** 67 | * Return im list dummy response content. 68 | */ 69 | private function getDummyImListResponse() 70 | { 71 | return '{"ok": true, "ims":[{"id": "D024BFF1M", "is_im": true, "user": "USLACKBOT", "created": 1372105335, 72 | "is_user_deleted": false }, { "id": "D024BE7RE", "is_im": true, "user": "U024BE7LH", "created": 1356250715, 73 | "is_user_deleted": false}]}'; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/Botonomous/ImChannelTest.php: -------------------------------------------------------------------------------- 1 | getImChannel(); 15 | $this->assertFalse($imChannel->isIm()); 16 | } 17 | 18 | public function testIsUserDeleted() 19 | { 20 | $imChannel = $this->getImChannel(); 21 | $this->assertTrue($imChannel->isUserDeleted()); 22 | } 23 | 24 | public function testGetCreated() 25 | { 26 | $imChannel = $this->getImChannel(); 27 | $this->assertEquals('1372105335', $imChannel->getCreated()); 28 | } 29 | 30 | private function getImChannel() 31 | { 32 | $imChannel = new ImChannel(); 33 | 34 | $imChannel->setIm(false); 35 | $imChannel->setUserDeleted(true); 36 | $imChannel->setCreated('1372105335'); 37 | 38 | return $imChannel; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/Botonomous/MessageActionTest.php: -------------------------------------------------------------------------------- 1 | load($this->getInfo()); 13 | 14 | $actions = []; 15 | $action = new Action(); 16 | $action->setName('recommend'); 17 | $action->setValue('yes'); 18 | $action->setType('button'); 19 | $actions[] = $action; 20 | 21 | $this->assertEquals($actions, $messageAction->getActions()); 22 | 23 | $this->assertEquals('comic_1234_xyz', $messageAction->getCallbackId()); 24 | 25 | $team = new Team(); 26 | $team->setSlackId('T47563693'); 27 | $team->setDomain('watermelonsugar'); 28 | 29 | $this->assertEquals($team, $messageAction->getTeam()); 30 | 31 | $channel = new Channel(); 32 | $channel->setSlackId('C065W1189'); 33 | $channel->setName('forgotten-works'); 34 | 35 | $this->assertEquals($channel, $messageAction->getChannel()); 36 | 37 | $user = new User(); 38 | $user->setSlackId('U045VRZFT'); 39 | $user->setName('brautigan'); 40 | 41 | $this->assertEquals($user, $messageAction->getUser()); 42 | 43 | $this->assertEquals('1458170917.164398', $messageAction->getActionTimestamp()); 44 | $this->assertEquals('1458170866.000004', $messageAction->getMessageTimestamp()); 45 | $this->assertEquals('1', $messageAction->getAttachmentId()); 46 | $this->assertEquals('xAB3yVzGS4BQ3O9FACTa8Ho4', $messageAction->getToken()); 47 | $this->assertEquals($this->getInfo('original_message'), $messageAction->getOriginalMessage()); 48 | $this->assertEquals( 49 | 'https://hooks.slack.com/actions/T47563693/6204672533/x7ZLaiVMoECAW50Gw1ZYAXEM', 50 | $messageAction->getResponseUrl() 51 | ); 52 | } 53 | 54 | public function testGetTeam() 55 | { 56 | $messageAction = new MessageAction(); 57 | 58 | $team = new Team(); 59 | $team->load($this->getInfo('team')); 60 | 61 | $messageAction->setTeam($team); 62 | 63 | $this->assertEquals($team, $messageAction->getTeam()); 64 | } 65 | 66 | public function testGeChannel() 67 | { 68 | $messageAction = new MessageAction(); 69 | 70 | $channel = new Channel(); 71 | $channel->load($this->getInfo('channel')); 72 | 73 | $messageAction->setChannel($channel); 74 | 75 | $this->assertEquals($channel, $messageAction->getChannel()); 76 | } 77 | 78 | public function testGeUser() 79 | { 80 | $messageAction = new MessageAction(); 81 | 82 | $user = new User(); 83 | $user->load($this->getInfo('user')); 84 | 85 | $messageAction->setUser($user); 86 | 87 | $this->assertEquals($user, $messageAction->getUser()); 88 | } 89 | 90 | public function testAddAction() 91 | { 92 | $messageAction = new MessageAction(); 93 | 94 | $action = new Action(); 95 | $action->setType('button'); 96 | 97 | $messageAction->addAction($action); 98 | $messageAction->addAction($action); 99 | 100 | $actions = [$action, $action]; 101 | 102 | $this->assertEquals($actions, $messageAction->getActions()); 103 | } 104 | 105 | private function getInfo($key = null) 106 | { 107 | $originalMessage = [ 108 | 'text' => 'New comic book alert!', 109 | 'attachments' => [ 110 | 0 => [ 111 | 'title' => 'The Further Adventures of Botonomous', 112 | 'fields' => [ 113 | 0 => [ 114 | 'title' => 'Volume', 115 | 'value' => '1', 116 | 'short' => true, 117 | ], 118 | 1 => [ 119 | 'title' => 'Issue', 120 | 'value' => '3', 121 | 'short' => true, 122 | ], 123 | ], 124 | 'author_name' => 'Stanford S. Strickland', 125 | 'author_icon' => 'https://a.slack-edge.com/homepage_custom_integrations-2x.png', 126 | 'image_url' => 'http://i.imgur.com/OJkaVOI.jpg?1', 127 | ], 128 | 1 => [ 129 | 'title' => 'Synopsis', 130 | 'text' => 'After @episod pushed exciting changes ...', 131 | ], 132 | 2 => [ 133 | 'fallback' => 'Would you recommend it to customers?', 134 | 'title' => 'Would you recommend it to customers?', 135 | 'callback_id' => 'comic_1234_xyz', 136 | 'color' => '#3AA3E3', 137 | 'attachment_type' => 'default', 138 | 'actions' => [ 139 | 0 => [ 140 | 'name' => 'recommend', 141 | 'text' => 'Recommend', 142 | 'type' => 'button', 143 | 'value' => 'recommend', 144 | ], 145 | 1 => [ 146 | 'name' => 'no', 147 | 'text' => 'No', 148 | 'type' => 'button', 149 | 'value' => 'bad', 150 | ], 151 | ], 152 | ], 153 | ], 154 | ]; 155 | 156 | $info = [ 157 | 'actions' => [ 158 | 0 => [ 159 | 'name' => 'recommend', 160 | 'value' => 'yes', 161 | 'type' => 'button', 162 | ], 163 | ], 164 | 'callback_id' => 'comic_1234_xyz', 165 | 'team' => [ 166 | 'id' => 'T47563693', 167 | 'domain' => 'watermelonsugar', 168 | ], 169 | 'channel' => [ 170 | 'id' => 'C065W1189', 171 | 'name' => 'forgotten-works', 172 | ], 173 | 'user' => [ 174 | 'id' => 'U045VRZFT', 175 | 'name' => 'brautigan', 176 | ], 177 | 'action_ts' => '1458170917.164398', 178 | 'message_ts' => '1458170866.000004', 179 | 'attachment_id' => '1', 180 | 'token' => 'xAB3yVzGS4BQ3O9FACTa8Ho4', 181 | 'original_message' => $originalMessage, 182 | 'response_url' => 'https://hooks.slack.com/actions/T47563693/6204672533/x7ZLaiVMoECAW50Gw1ZYAXEM', 183 | ]; 184 | 185 | if ($key !== null) { 186 | return $info[$key]; 187 | } 188 | 189 | return $info; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /tests/Botonomous/PhpunitHelper.php: -------------------------------------------------------------------------------- 1 | get('commandPrefix'); 15 | 16 | /** 17 | * Form the request. 18 | */ 19 | $botUsername = '@'.$config->get('botUsername'); 20 | $request = [ 21 | 'token' => $config->get(self::VERIFICATION_TOKEN), 22 | 'text' => "{$botUsername} {$commandPrefix}{$command}{$text}", 23 | ]; 24 | 25 | $slackbot = new Slackbot(); 26 | 27 | // get listener 28 | $listener = $slackbot->getListener(); 29 | 30 | // set request 31 | $listener->setRequest($request); 32 | 33 | return $slackbot; 34 | } 35 | 36 | public function getDictionaryData($listKey) 37 | { 38 | return [ 39 | 'access-control' => [ 40 | $listKey => [ 41 | 'username' => [ 42 | 'dummyUserName', 43 | ], 44 | 'userId' => [ 45 | 'dummyUserId', 46 | ], 47 | ], 48 | ], 49 | ]; 50 | } 51 | 52 | public function getRequest() 53 | { 54 | return [ 55 | 'user_name' => 'dummyUserName', 56 | 'user_id' => 'dummyUserId', 57 | ]; 58 | } 59 | 60 | public function getWhiteList() 61 | { 62 | return new WhiteList($this->getRequest()); 63 | } 64 | 65 | public function getBlackList() 66 | { 67 | return new BlackList($this->getRequest()); 68 | } 69 | 70 | public function getUserInfoClient() 71 | { 72 | return (new ApiClientTest())->getApiClient('{ 73 | "ok": true, 74 | "user": { 75 | "id": "U023BECGF", 76 | "name": "bobby", 77 | "deleted": false, 78 | "color": "9f69e7", 79 | "profile": { 80 | "first_name": "Bobby", 81 | "last_name": "Tables", 82 | "real_name": "Bobby Tables", 83 | "email": "bobby@slack.com", 84 | "skype": "my-skype-name", 85 | "phone": "+1 (123) 456 7890", 86 | "image_24": "https:\/\/...", 87 | "image_32": "https:\/\/...", 88 | "image_48": "https:\/\/...", 89 | "image_72": "https:\/\/...", 90 | "image_192": "https:\/\/..." 91 | }, 92 | "is_admin": true, 93 | "is_owner": true, 94 | "has_2fa": true 95 | } 96 | }'); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /tests/Botonomous/SenderTest.php: -------------------------------------------------------------------------------- 1 | setLogFile(); 31 | 32 | $slackbot = new Slackbot($config); 33 | 34 | /** 35 | * Overwrite the slackbot. 36 | */ 37 | $request = [ 38 | 'token' => $config->get(self::VERIFICATION_TOKEN), 39 | 'user_id' => 'dummyId', 40 | 'user_name' => 'dummyUsername', 41 | 'debug' => $debug, 42 | ]; 43 | 44 | // get listener 45 | $listener = $slackbot->getListener(); 46 | 47 | // set request 48 | $listener->setRequest($request); 49 | 50 | $slackbot->setConfig($config); 51 | 52 | return $slackbot; 53 | } 54 | 55 | /** 56 | * Test send. 57 | */ 58 | public function testSendDebug() 59 | { 60 | $sender = new Sender($this->getSlackbot()); 61 | 62 | $sender->send('test response 2', '#dummyChannel', []); 63 | 64 | $response = '{"text":"test response 2","channel":"#dummyChannel","attachments":"[]"}'; 65 | 66 | $this->expectOutputString($response); 67 | } 68 | 69 | /** 70 | * Test send. 71 | */ 72 | public function testSendSlackJson() 73 | { 74 | $sender = new Sender($this->getSlackbot()); 75 | 76 | $sender->send('test response 3', '#dummyChannel'); 77 | 78 | $response = '{"text":"test response 3","channel":"#dummyChannel"}'; 79 | 80 | $this->expectOutputString($response); 81 | } 82 | 83 | /** 84 | * Test send. 85 | */ 86 | public function testSendSlackWithDebug() 87 | { 88 | $sender = new Sender($this->getSlackbot()); 89 | 90 | $sender->send('test response 4', '#dummyChannel'); 91 | 92 | $response = '{"text":"test response 4","channel":"#dummyChannel"}'; 93 | 94 | $this->expectOutputString($response); 95 | } 96 | 97 | /** 98 | * Test send. 99 | */ 100 | public function testSendSlack() 101 | { 102 | $config = new Config(); 103 | $config->set('listener', 'event'); 104 | 105 | $sender = new Sender($this->getSlackbot(false)); 106 | 107 | $apiClient = (new ApiClientTest())->getApiClient( 108 | '{ "ok": true, "ts": "1405895017.000506", "channel": "C024BE91L", "message": {} }' 109 | ); 110 | 111 | $sender->setApiClient($apiClient); 112 | 113 | $result = $sender->send('test response 5', '#dummyChannel'); 114 | 115 | $this->assertTrue($result); 116 | 117 | // reset the config 118 | $config->set('listener', 'slashCommand'); 119 | } 120 | 121 | /** 122 | * Test send. 123 | */ 124 | public function testSendSlashCommand() 125 | { 126 | $sender = new Sender($this->getSlackbot(false)); 127 | 128 | $mock = new MockHandler([ 129 | new Response(200, [], ''), 130 | ]); 131 | 132 | /** @noinspection PhpUndefinedClassInspection */ 133 | $handler = new HandlerStack($mock); 134 | /** @noinspection PhpUndefinedClassInspection */ 135 | $client = new Client(['handler' => $handler]); 136 | 137 | // $client 138 | $sender->setClient($client); 139 | 140 | $result = $sender->send('test response 6', '#dummyChannel'); 141 | 142 | $this->assertTrue($result); 143 | } 144 | 145 | /** 146 | * Test getConfig. 147 | */ 148 | public function testGetConfig() 149 | { 150 | $config = new Config(); 151 | $originalTimezone = $config->get('timezone'); 152 | $config->set('timezone', 'America/Los_Angeles'); 153 | 154 | $sender = new Sender((new Slackbot())); 155 | $sender->setConfig($config); 156 | 157 | $this->assertEquals($config, $sender->getConfig()); 158 | 159 | // reset config value 160 | $config->set('timezone', $originalTimezone); 161 | } 162 | 163 | /** 164 | * Test getClient. 165 | */ 166 | public function testGetClient() 167 | { 168 | $client = new Client(); 169 | 170 | $sender = new Sender(new Slackbot()); 171 | $this->assertEquals($client, $sender->getClient()); 172 | } 173 | 174 | /** 175 | * Test getApiClient. 176 | */ 177 | public function testGetApiClient() 178 | { 179 | $apiClient = new ApiClient(); 180 | 181 | $sender = new Sender(new Slackbot()); 182 | $sender->setApiClient($apiClient); 183 | 184 | $this->assertEquals($apiClient, $sender->getApiClient()); 185 | } 186 | 187 | /** 188 | * Test getApiClient. 189 | */ 190 | public function testGetApiClientNotSet() 191 | { 192 | $apiClient = new ApiClient(); 193 | 194 | $sender = new Sender(new Slackbot()); 195 | $this->assertEquals($apiClient, $sender->getApiClient()); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /tests/Botonomous/TeamTest.php: -------------------------------------------------------------------------------- 1 | setSlackId('T0LCJF334'); 17 | 18 | $this->assertEquals('T0LCJF334', $team->getSlackId()); 19 | } 20 | 21 | public function testGetName() 22 | { 23 | $team = new Team(); 24 | $team->setName('test'); 25 | 26 | $this->assertEquals('test', $team->getName()); 27 | } 28 | 29 | public function testGetIcon() 30 | { 31 | $team = new Team(); 32 | 33 | $icon = [ 34 | 'image_34' => 'http:', 35 | 'image_44' => 'http:', 36 | 'image_default' => true, 37 | ]; 38 | 39 | $team->setIcon($icon); 40 | 41 | $this->assertEquals($icon, $team->getIcon()); 42 | } 43 | 44 | public function testGetEmailDomain() 45 | { 46 | $team = new Team(); 47 | $email = 'test'; 48 | $team->setEmailDomain($email); 49 | 50 | $this->assertEquals($email, $team->getEmailDomain()); 51 | } 52 | 53 | public function testGetDomain() 54 | { 55 | $team = new Team(); 56 | $domain = 'test'; 57 | $team->setDomain($domain); 58 | 59 | $this->assertEquals($domain, $team->getDomain()); 60 | } 61 | 62 | public function testIsIconDefault() 63 | { 64 | $team = new Team(); 65 | 66 | $icon = [ 67 | 'image_34' => 'http:', 68 | 'image_44' => 'http:', 69 | 'image_default' => true, 70 | ]; 71 | 72 | $team->setIcon($icon); 73 | 74 | $this->assertTrue($team->isIconDefault()); 75 | 76 | $icon = [ 77 | 'image_34' => 'http:', 78 | 'image_44' => 'http:', 79 | ]; 80 | 81 | $team->setIcon($icon); 82 | 83 | $this->assertFalse($team->isIconDefault()); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /tests/Botonomous/UserTest.php: -------------------------------------------------------------------------------- 1 | setSlackId(self::USER_ID); 16 | 17 | $this->assertEquals(self::USER_ID, $channel->getSlackId()); 18 | } 19 | 20 | public function testGetName() 21 | { 22 | $channel = new User(); 23 | $channel->setName(self::USER_NAME); 24 | 25 | $this->assertEquals(self::USER_NAME, $channel->getName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Botonomous/WhiteListTest.php: -------------------------------------------------------------------------------- 1 | getWhiteList(); 14 | } 15 | 16 | public function testGetRequest() 17 | { 18 | $whitelist = $this->getWhiteList(); 19 | 20 | $this->assertEquals((new PhpunitHelper())->getRequest(), $whitelist->getRequest()); 21 | 22 | // overwrite the request 23 | $whitelist->setRequest([]); 24 | 25 | $this->assertEmpty($whitelist->getRequest()); 26 | } 27 | 28 | public function testGetApiClient() 29 | { 30 | $whitelist = $this->getWhiteList(); 31 | 32 | $apiClient = new ApiClient(); 33 | $this->assertEquals($apiClient, $whitelist->getApiClient()); 34 | 35 | // call it again 36 | $this->assertEquals($apiClient, $whitelist->getApiClient()); 37 | } 38 | 39 | public function testIsUsernameWhiteListed() 40 | { 41 | $whitelist = $this->getWhiteList(); 42 | 43 | $inputsOutputs = [ 44 | [ 45 | 'input' => [ 46 | 'access-control' => [ 47 | 'whitelist' => [ 48 | 'userId' => [], 49 | ], 50 | ], 51 | ], 52 | 'output' => false, 53 | ], 54 | [ 55 | 'input' => (new PhpunitHelper())->getDictionaryData('whitelist'), 56 | 'output' => true, 57 | ], 58 | [ 59 | 'input' => [ 60 | 'access-control' => [ 61 | 'whitelist' => [ 62 | 'username' => [ 63 | 'blahblah', 64 | ], 65 | 'userId' => [ 66 | 'blahblah', 67 | ], 68 | ], 69 | ], 70 | ], 71 | 'output' => false, 72 | ], 73 | ]; 74 | 75 | $dictionary = new Dictionary(); 76 | foreach ($inputsOutputs as $inputOutput) { 77 | $dictionary->setData($inputOutput['input']); 78 | 79 | // set the dictionary 80 | $whitelist->setDictionary($dictionary); 81 | 82 | $this->assertEquals($inputOutput['output'], $whitelist->isUsernameWhiteListed()); 83 | } 84 | } 85 | 86 | public function testIsUserIdWhiteListed() 87 | { 88 | $inputsOutputs = [ 89 | [ 90 | 'input' => [], 91 | 'output' => false, 92 | ], 93 | [ 94 | 'input' => [ 95 | 'access-control' => [], 96 | ], 97 | 'output' => false, 98 | ], 99 | [ 100 | 'input' => [ 101 | 'access-control' => [ 102 | 'whitelist' => [ 103 | 'username' => [], 104 | ], 105 | ], 106 | ], 107 | 'output' => false, 108 | ], 109 | [ 110 | 'input'=> [ 111 | 'access-control' => [ 112 | 'whitelist' => [ 113 | 'username' => [], 114 | 'userId' => [ 115 | 'dummyUserId', 116 | ], 117 | ], 118 | ], 119 | ], 120 | 'output' => true, 121 | ], 122 | [ 123 | 'input'=> [ 124 | 'access-control' => [ 125 | 'whitelist' => [ 126 | 'username' => [], 127 | 'userId' => [], 128 | ], 129 | ], 130 | ], 131 | 'output' => false, 132 | ], 133 | [ 134 | 'input'=> [ 135 | 'access-control' => [ 136 | 'whitelist' => [ 137 | 'username' => [], 138 | 'userId' => [ 139 | 'blahblah', 140 | ], 141 | ], 142 | ], 143 | ], 144 | 'output' => false, 145 | ], 146 | ]; 147 | 148 | $whitelist = $this->getWhiteList(); 149 | $dictionary = new Dictionary(); 150 | foreach ($inputsOutputs as $inputOutput) { 151 | $dictionary->setData($inputOutput['input']); 152 | 153 | // set the dictionary 154 | $whitelist->setDictionary($dictionary); 155 | 156 | $this->assertEquals($inputOutput['output'], $whitelist->isUserIdWhiteListed()); 157 | } 158 | } 159 | 160 | public function testIsEmailWhiteListedFalse() 161 | { 162 | $client = (new PhpunitHelper())->getUserInfoClient(); 163 | 164 | $whitelist = $this->getWhiteList(); 165 | $whitelist->setApiClient($client); 166 | 167 | $this->assertEquals(false, $whitelist->isEmailWhiteListed()); 168 | } 169 | 170 | public function testIsEmailWhiteListed() 171 | { 172 | $whitelist = $this->getWhiteList(); 173 | 174 | $client = (new PhpunitHelper())->getUserInfoClient(); 175 | $whitelist->setApiClient($client); 176 | 177 | $dictionary = new Dictionary(); 178 | $dictionary->setData([ 179 | 'access-control' => [ 180 | 'whitelist' => [ 181 | 'userEmail' => [ 182 | 'bobby@slack.com', 183 | ], 184 | ], 185 | ], 186 | ]); 187 | 188 | $whitelist->setDictionary($dictionary); 189 | 190 | $whitelist->setRequest([ 191 | 'user_id' => 'U023BECGF', 192 | ]); 193 | 194 | $this->assertEquals(true, $whitelist->isEmailWhiteListed()); 195 | } 196 | 197 | public function testGetSlackUserInfoEmptyRequest() 198 | { 199 | $whitelist = $this->getWhiteList(); 200 | $whitelist->setRequest([]); 201 | 202 | $this->assertEmpty($whitelist->getSlackUserInfo()); 203 | } 204 | 205 | /** 206 | * @throws \Exception 207 | */ 208 | public function testGetSlackUserInfoNotFound() 209 | { 210 | $whitelist = $this->getWhiteList(); 211 | $this->assertFalse($whitelist->getSlackUserInfo()); 212 | } 213 | 214 | public function testIsWhiteListed() 215 | { 216 | $client = (new PhpunitHelper())->getUserInfoClient(); 217 | $whitelist = $this->getWhiteList(); 218 | $whitelist->setApiClient($client); 219 | 220 | $whitelist->setRequest([ 221 | 'user_id' => 'U023BECGF', 222 | 'user_name' => 'bobby', 223 | ]); 224 | 225 | $dictionary = new Dictionary(); 226 | $dictionary->setData([ 227 | 'access-control' => [ 228 | 'whitelist' => [ 229 | 'userId' => ['U023BECGF'], 230 | 'username' => ['bobby'], 231 | 'userEmail' => ['bobby@slack.com'], 232 | ], 233 | ], 234 | ]); 235 | 236 | $whitelist->setDictionary($dictionary); 237 | 238 | $this->assertEquals(true, $whitelist->isWhiteListed()); 239 | } 240 | 241 | /** 242 | * @throws \Exception 243 | */ 244 | public function testIsWhiteListedFalse() 245 | { 246 | $whitelist = $this->getWhiteList(); 247 | $this->assertEquals(false, $whitelist->isWhiteListed()); 248 | } 249 | 250 | public function testIsEmailWhiteListedEmptyEmailList() 251 | { 252 | $client = (new PhpunitHelper())->getUserInfoClient(); 253 | $whitelist = $this->getWhiteList(); 254 | $whitelist->setApiClient($client); 255 | 256 | $dictionary = new Dictionary(); 257 | $dictionary->setData([ 258 | 'access-control' => [ 259 | 'whitelist' => [], 260 | ], 261 | ]); 262 | 263 | $whitelist->setDictionary($dictionary); 264 | 265 | $whitelist->setRequest([ 266 | 'user_id' => 'U023BECGF', 267 | ]); 268 | 269 | $this->assertEquals(true, $whitelist->isEmailWhiteListed()); 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /tests/Botonomous/plugin/help/HelpConfigTest.php: -------------------------------------------------------------------------------- 1 | get('testConfigKey'); 14 | $this->assertEquals('testConfigValue', $result); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/Botonomous/plugin/help/HelpTest.php: -------------------------------------------------------------------------------- 1 | getSlackbot()))->index(); 17 | $this->assertFalse(empty($index)); 18 | } 19 | 20 | /** 21 | * test invalid commands in index. 22 | */ 23 | public function testIndexInvalidCommands() 24 | { 25 | $slackbot = (new PhpunitHelper())->getSlackbot(); 26 | $slackbot->setCommands(['dummy']); 27 | 28 | $index = (new Help($slackbot))->index(); 29 | 30 | $this->assertTrue(empty($index)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Botonomous/plugin/ping/PingTest.php: -------------------------------------------------------------------------------- 1 | getSlackbot(); 21 | $ping = new Ping($slackbot); 22 | 23 | $this->assertEquals('ping', $ping->pong()); 24 | } 25 | 26 | /** 27 | * Test getSlackbot. 28 | */ 29 | public function testGetSlackbot() 30 | { 31 | $slackbot = (new PhpunitHelper())->getSlackbot(); 32 | $ping = new Ping($slackbot); 33 | 34 | $this->assertEquals($slackbot, $ping->getSlackbot()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Botonomous/plugin/qa/QATest.php: -------------------------------------------------------------------------------- 1 | getSlackbot('qa', " {$question}"); 23 | 24 | $answer = (new QA($slackbot))->index(); 25 | 26 | $questionAnswer = (new Dictionary())->get('question-answer'); 27 | $this->assertContains($answer, $questionAnswer[$question]['answers']); 28 | } 29 | 30 | /** 31 | * Test index. 32 | */ 33 | public function testIndexEmptyQuestions() 34 | { 35 | $question = 'hi'; 36 | $slackbot = (new PhpunitHelper())->getSlackbot('qa', " {$question}"); 37 | 38 | $qaPlugin = new QA($slackbot); 39 | $qaPlugin->setQuestions([]); 40 | 41 | $this->assertEmpty($qaPlugin->index()); 42 | } 43 | 44 | /** 45 | * Test index. 46 | */ 47 | public function testIndexNotFoundQuestion() 48 | { 49 | $question = 'dummy'; 50 | $slackbot = (new PhpunitHelper())->getSlackbot('qa', " {$question}"); 51 | 52 | $answer = (new QA($slackbot))->index(); 53 | 54 | $this->assertEmpty($answer); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/Botonomous/utility/ClassUtilityTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('WhiteList', $utility->extractClassNameFromFullName('Botonomous\WhiteList')); 18 | 19 | $this->assertEquals('WhiteList', $utility->extractClassNameFromFullName('WhiteList')); 20 | 21 | $this->assertEquals('test', $utility->extractClassNameFromFullName('Botonomous\WhiteList\test')); 22 | 23 | $this->assertEquals('', $utility->extractClassNameFromFullName('')); 24 | } 25 | 26 | public function testLoadAttributes() 27 | { 28 | $utility = new ClassUtility(); 29 | 30 | $channel = new Channel(); 31 | $info = [ 32 | 'id' => self::DUMMY_ID, 33 | 'name' => self::DUMMY_NAME, 34 | ]; 35 | 36 | /** @var Channel $channel */ 37 | $channel = $utility->loadAttributes($channel, $info); 38 | 39 | $this->assertEquals(self::DUMMY_ID, $channel->getSlackId()); 40 | $this->assertEquals(self::DUMMY_NAME, $channel->getName()); 41 | 42 | $event = new Event('message'); 43 | $info = [ 44 | 'bot_id' => self::DUMMY_ID, 45 | // should be ignore 46 | 'non_existent_attr' => 'dummy_value', 47 | 'ts' => '1355517523.000005', 48 | 'event_ts' => '1355517523.000005', 49 | ]; 50 | 51 | /** @var Event $event */ 52 | $event = $utility->loadAttributes($event, $info); 53 | 54 | $this->assertEquals(self::DUMMY_ID, $event->getBotId()); 55 | $this->assertEquals('1355517523.000005', $event->getTimestamp()); 56 | $this->assertEquals('1355517523.000005', $event->getEventTimestamp()); 57 | $this->assertEmpty($event->getText()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/Botonomous/utility/FileUtilityTest.php: -------------------------------------------------------------------------------- 1 | getSlackbotDir().DIRECTORY_SEPARATOR.'dictionary'.DIRECTORY_SEPARATOR.'test.json'; 20 | 21 | $array = (new FileUtility())->jsonFileToArray($dir); 22 | 23 | $expected = [ 24 | 'test1', 25 | 'test2', 26 | ]; 27 | 28 | $this->assertEquals($expected, $array); 29 | } 30 | 31 | /** 32 | * Test jsonFileToArrayEmptyPath. 33 | */ 34 | public function testJsonFileToArrayEmptyPath() 35 | { 36 | try { 37 | (new FileUtility())->jsonFileToArray(''); 38 | } catch (\Exception $e) { 39 | $this->assertEquals(FileUtility::EMPTY_FILE_PATH_MESSAGE, $e->getMessage()); 40 | } 41 | } 42 | 43 | /** 44 | * Test jsonFileToArrayMissingFile. 45 | */ 46 | public function testJsonFileToArrayMissingFile() 47 | { 48 | try { 49 | (new FileUtility())->jsonFileToArray('/path/to/dummy.json'); 50 | } catch (\Exception $e) { 51 | $this->assertEquals(FileUtility::MISSING_FILE_MESSAGE, $e->getMessage()); 52 | } 53 | } 54 | 55 | /** 56 | * Test jsonFileToArrayInvalidFile. 57 | */ 58 | public function testJsonFileToArrayInvalidFile() 59 | { 60 | $dir = $this->getSlackbotDir().DIRECTORY_SEPARATOR.'Config.php'; 61 | 62 | try { 63 | (new FileUtility())->jsonFileToArray($dir); 64 | } catch (\Exception $e) { 65 | $this->assertEquals(FileUtility::INVALID_JSON_FILE_MESSAGE, $e->getMessage()); 66 | } 67 | } 68 | 69 | private function getSlackbotDir() 70 | { 71 | $namespaceParts = explode('\\', __NAMESPACE__); 72 | $rootNamespace = $namespaceParts[0]; 73 | 74 | return dirname(dirname(dirname(__DIR__))).DIRECTORY_SEPARATOR.'src'.DIRECTORY_SEPARATOR.$rootNamespace; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/Botonomous/utility/LanguageProcessingUtilityTest.php: -------------------------------------------------------------------------------- 1 | stem('Stemming is funnier than a bummer says the sushi loving computer scientist'); 22 | 23 | $this->assertEquals('Stem is funnier than a bummer sai the sushi love comput scientist', $result); 24 | } 25 | 26 | /** 27 | * Test stem. 28 | */ 29 | public function testStemEmpty() 30 | { 31 | $utility = new LanguageProcessingUtility(); 32 | 33 | $result = $utility->stem(''); 34 | 35 | $this->assertEquals('', $result); 36 | } 37 | 38 | /** 39 | * Test removePunctuations. 40 | */ 41 | public function testRemovePunctuations() 42 | { 43 | $utility = new LanguageProcessingUtility(); 44 | 45 | $result = $utility->removePunctuations('A dummy text?'); 46 | 47 | $expected = 'A dummy text'; 48 | 49 | $this->assertEquals($expected, $result); 50 | 51 | $result = $utility->removePunctuations('A dummy text.'); 52 | 53 | $expected = 'A dummy text'; 54 | 55 | $this->assertEquals($expected, $result); 56 | } 57 | 58 | /** 59 | * Test removeStopWords. 60 | */ 61 | public function testRemoveStopWords() 62 | { 63 | $utility = new LanguageProcessingUtility(); 64 | 65 | $inputsOutputs = [ 66 | [ 67 | 'i' => 'Stemming is funnier than a bummer says the sushi loving computer scientist', 68 | 'o' => 'Stemming funnier bummer sushi loving computer scientist', 69 | ], 70 | ]; 71 | 72 | foreach ($inputsOutputs as $inputOutput) { 73 | $result = $utility->removeStopWords($inputOutput['i']); 74 | $this->assertEquals($inputOutput['o'], $result); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/Botonomous/utility/LoggerUtilityTest.php: -------------------------------------------------------------------------------- 1 | setTimezone(); 25 | 26 | $this->setLogFile(); 27 | 28 | $utility = new LoggerUtility($this->getConfig(false)); 29 | 30 | $this->assertFalse($utility->logChat(__METHOD__, self::TEST_MESSAGE)); 31 | } 32 | 33 | /** 34 | * Test logChatEnabled. 35 | */ 36 | public function testLogChatEnabled() 37 | { 38 | $this->setTimezone(); 39 | 40 | $this->setLogFile(); 41 | 42 | $utility = new LoggerUtility($this->getConfig()); 43 | 44 | $this->assertTrue($utility->logChat(__METHOD__, self::TEST_MESSAGE)); 45 | } 46 | 47 | /** 48 | * Test logRaw. 49 | */ 50 | public function testLogRawLogging() 51 | { 52 | $this->setTimezone(); 53 | 54 | $this->setLogFile(); 55 | 56 | $utility = new LoggerUtility($this->getConfig()); 57 | 58 | $this->assertTrue($utility->logInfo(self::TEST_INFO_LOG)); 59 | } 60 | 61 | /** 62 | * Test logRaw. 63 | */ 64 | public function testLogRawNotLogging() 65 | { 66 | $this->setTimezone(); 67 | 68 | $utility = new LoggerUtility($this->getConfig(false)); 69 | 70 | $this->assertFalse($utility->logInfo(self::TEST_INFO_LOG)); 71 | } 72 | 73 | public function setLogFile($name = null) 74 | { 75 | if ($name === null) { 76 | $name = self::TEST_LOG_FILE; 77 | } 78 | 79 | $config = new Config(); 80 | $config->set(['logger', 'monolog', 'handlers', 'file', 'fileName'], $name); 81 | } 82 | 83 | public function testGetLogContent() 84 | { 85 | $utility = new LoggerUtility($this->getConfig()); 86 | 87 | $this->assertEquals( 88 | __METHOD__.'|test message|#dummy', 89 | $utility->getLogContent(__METHOD__, 'test message', '#dummy') 90 | ); 91 | } 92 | 93 | public function testLogDebug() 94 | { 95 | $this->setLogFile(); 96 | $utility = new LoggerUtility($this->getConfig()); 97 | 98 | $this->assertTrue($utility->logDebug('This is a debug log')); 99 | } 100 | 101 | public function testLogNotice() 102 | { 103 | $this->setLogFile(); 104 | $utility = new LoggerUtility($this->getConfig()); 105 | 106 | $this->assertTrue($utility->logNotice('This is a notice log')); 107 | } 108 | 109 | public function testLogWarning() 110 | { 111 | $this->setLogFile(); 112 | $utility = new LoggerUtility($this->getConfig()); 113 | 114 | $this->assertTrue($utility->logWarning('This is a warning log')); 115 | } 116 | 117 | public function testLogError() 118 | { 119 | $this->setLogFile(); 120 | $utility = new LoggerUtility($this->getConfig()); 121 | 122 | $this->assertTrue($utility->logError('This is an error log')); 123 | } 124 | 125 | public function testLogCritical() 126 | { 127 | $this->setLogFile(); 128 | $utility = new LoggerUtility($this->getConfig()); 129 | 130 | $this->assertTrue($utility->logCritical('This is a critical log')); 131 | } 132 | 133 | public function testLogAlert() 134 | { 135 | $this->setLogFile(); 136 | $utility = new LoggerUtility($this->getConfig()); 137 | 138 | $this->assertTrue($utility->logAlert('This is an alert log')); 139 | } 140 | 141 | public function testLogEmergency() 142 | { 143 | $this->setLogFile(); 144 | $utility = new LoggerUtility($this->getConfig()); 145 | 146 | $this->assertTrue($utility->logEmergency('This is an emergency log')); 147 | } 148 | 149 | public function testLogInvalidLevel() 150 | { 151 | $utility = new LoggerUtility($this->getConfig()); 152 | 153 | $this->expectException('\Exception'); 154 | $this->expectExceptionMessage("'invalidLevel' is an invalid log level"); 155 | 156 | $utility->log('invalidLevel', 'dummyMessage'); 157 | } 158 | 159 | public function testLogEmptyConfig() 160 | { 161 | $config = new Config(); 162 | $config->set('logger', null); 163 | 164 | $this->expectException('\Exception'); 165 | $this->expectExceptionMessage('Monolog config is missing'); 166 | 167 | new LoggerUtility($config); 168 | } 169 | 170 | private function getConfig($loggerEnabled = true) 171 | { 172 | $config = new Config(); 173 | $config->set(['logger', 'enabled'], $loggerEnabled === true ? true : false); 174 | 175 | return $config; 176 | } 177 | 178 | private function setTimezone() 179 | { 180 | $config = new Config(); 181 | date_default_timezone_set($config->get('timezone')); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /tests/Botonomous/utility/MessageUtilityTest.php: -------------------------------------------------------------------------------- 1 | get('botUserId'); 25 | $removed = $utility->removeMentionedBot("<@{$botUserId}> /help"); 26 | 27 | $this->assertEquals($removed, ' /help'); 28 | 29 | $removed = $utility->removeMentionedBot(' /help'); 30 | 31 | $this->assertEquals($removed, ' /help'); 32 | 33 | $removed = $utility->removeMentionedBot("<@{$botUserId}> /help <@{$botUserId}>"); 34 | 35 | $this->assertEquals($removed, " /help <@{$botUserId}>"); 36 | 37 | $removed = $utility->removeMentionedBot("<@{$botUserId}> <@{$botUserId}>"); 38 | 39 | $this->assertEquals($removed, " <@{$botUserId}>"); 40 | 41 | $removed = $utility->removeMentionedBot("Test <@{$botUserId}>"); 42 | 43 | $this->assertEquals($removed, 'Test '); 44 | } 45 | 46 | /** 47 | * Test extractCommandName. 48 | */ 49 | public function testExtractCommandName() 50 | { 51 | $utility = new MessageUtility(); 52 | $config = new Config(); 53 | $commandPrefix = $config->get('commandPrefix'); 54 | 55 | $command = $utility->extractCommandName("{$commandPrefix}help dummy @test {$commandPrefix}help de"); 56 | 57 | $this->assertEquals('help', $command); 58 | 59 | $command = $utility->extractCommandName(" {$commandPrefix}help dummy @test {$commandPrefix}help de"); 60 | 61 | $this->assertEquals('help', $command); 62 | 63 | $command = $utility->extractCommandName(" dummy {$commandPrefix}help dummy @test {$commandPrefix}help dummy"); 64 | 65 | $this->assertEquals(null, $command); 66 | 67 | $config->set('commandPrefix', ''); 68 | $commandPrefix = $config->get('commandPrefix'); 69 | 70 | $command = $utility->extractCommandName("{$commandPrefix}help dummy @test {$commandPrefix}help de"); 71 | 72 | $this->assertEquals('help', $command); 73 | 74 | $command = $utility->extractCommandName(" dummy {$commandPrefix}help dummy @test {$commandPrefix}help dummy"); 75 | 76 | $this->assertEquals('dummy', $command); 77 | 78 | $command = $utility->extractCommandName(" {$commandPrefix}help dummy @test {$commandPrefix}help de"); 79 | 80 | $this->assertEquals('help', $command); 81 | 82 | $config->set('commandPrefix', '@'); 83 | $commandPrefix = $config->get('commandPrefix'); 84 | 85 | $command = $utility->extractCommandName("{$commandPrefix}help dummy @test {$commandPrefix}help de"); 86 | 87 | $this->assertEquals('help', $command); 88 | 89 | $command = $utility->extractCommandName(" {$commandPrefix}help dummy @test {$commandPrefix}help de"); 90 | 91 | $this->assertEquals('help', $command); 92 | 93 | $command = $utility->extractCommandName(" dummy {$commandPrefix}help dummy @test {$commandPrefix}help dummy"); 94 | 95 | $this->assertEquals(null, $command); 96 | } 97 | 98 | /** 99 | * @throws \Exception 100 | */ 101 | public function testExtractCommandDetails() 102 | { 103 | $utility = new MessageUtility(); 104 | $config = new Config(); 105 | $botUserId = $config->get('botUserId'); 106 | $commandPrefix = $config->get('commandPrefix'); 107 | $commandObject = $utility->extractCommandDetails("<@{$botUserId}> {$commandPrefix}ping"); 108 | 109 | $expected = new Command('ping'); 110 | $expected->setPlugin('Ping'); 111 | $expected->setDescription('Use as a health check'); 112 | 113 | $this->assertEquals($expected, $commandObject); 114 | } 115 | 116 | /** 117 | * test removeTriggerWord. 118 | */ 119 | public function testRemoveTriggerWord() 120 | { 121 | $utility = new MessageUtility(); 122 | $result = $utility->removeTriggerWord('google_bot: do this google_bot', 'google_bot:'); 123 | 124 | $this->assertEquals('do this google_bot', $result); 125 | } 126 | 127 | /** 128 | * test linkToUser. 129 | */ 130 | public function testLinkToUser() 131 | { 132 | $utility = new MessageUtility(); 133 | 134 | $this->assertEquals('<@U024BE7LH>', $utility->linkToUser('U024BE7LH')); 135 | 136 | $this->assertEquals('<@U024BE7LH|bob>', $utility->linkToUser('U024BE7LH', 'bob')); 137 | 138 | $this->assertEquals('<@U024BE7LH>', $utility->linkToUser('U024BE7LH', '')); 139 | 140 | $this->expectException('\Exception'); 141 | $this->expectExceptionMessage('User id is not provided'); 142 | 143 | $this->assertEquals('<@U024BE7LH>', $utility->linkToUser('')); 144 | } 145 | 146 | public function testIsBotMentioned() 147 | { 148 | $config = new Config(); 149 | $utility = new MessageUtility($config); 150 | 151 | $botUserId = $config->get('botUserId'); 152 | 153 | $this->assertEquals(true, $utility->isBotMentioned("<@{$botUserId}> /help")); 154 | $this->assertEquals(true, $utility->isBotMentioned("How are you <@{$botUserId}>?")); 155 | $this->assertEquals(false, $utility->isBotMentioned('/help')); 156 | } 157 | 158 | public function testKeywordPos() 159 | { 160 | $utility = new MessageUtility(); 161 | $result = $utility->keywordPos([ 162 | 'two words', 163 | 'word', 164 | 'two', 165 | 'words', 166 | ' plus', 167 | ], 'This is a two words plus one word and two words'); 168 | 169 | $expected = [ 170 | 'two words' => [ 171 | 10, 172 | 38, 173 | ], 174 | 'word' => [ 175 | 29, 176 | ], 177 | ' plus' => [ 178 | 19, 179 | ], 180 | ]; 181 | 182 | $this->assertEquals($expected, $result); 183 | 184 | $result = $utility->keywordPos([ 185 | "What's", 186 | 'the', 187 | 'the weather', 188 | ' like ', 189 | 'tomorrow', 190 | ], "What's the weather like tomorrow?"); 191 | 192 | $expected = [ 193 | "What's" => [ 194 | 0, 195 | ], 196 | 'the weather' => [ 197 | 7, 198 | ], 199 | 'tomorrow' => [ 200 | 24, 201 | ], 202 | ' like ' => [ 203 | 18, 204 | ], 205 | ]; 206 | 207 | $this->assertEquals($expected, $result); 208 | 209 | $result = $utility->keywordPos([], "What's the weather like tomorrow?"); 210 | 211 | $this->assertEmpty($result); 212 | } 213 | 214 | public function testKeywordCount() 215 | { 216 | $utility = new MessageUtility(); 217 | 218 | $result = $utility->keywordCount([ 219 | 'two words', 220 | 'word', 221 | 'two', 222 | 'words', 223 | ' plus', 224 | ], 'This is a two words plus one word and two words'); 225 | 226 | $expected = [ 227 | 'two words' => 2, 228 | 'word' => 1, 229 | ' plus' => 1, 230 | ]; 231 | 232 | $this->assertEquals($expected, $result); 233 | 234 | $result = $utility->keywordCount([ 235 | "What's", 236 | 'the', 237 | 'the weather', 238 | ' like ', 239 | 'tomorrow', 240 | ], "What's the weather like tomorrow?"); 241 | 242 | $expected = [ 243 | "What's" => 1, 244 | 'the weather' => 1, 245 | 'tomorrow' => 1, 246 | ' like ' => 1, 247 | ]; 248 | 249 | $this->assertEquals($expected, $result); 250 | 251 | $result = $utility->keywordCount([], "What's the weather like tomorrow?"); 252 | 253 | $this->assertEmpty($result); 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /tests/Botonomous/utility/RequestUtilityTest.php: -------------------------------------------------------------------------------- 1 | assertEquals( 14 | filter_input(INPUT_SERVER, 'SERVER_PROTOCOL', FILTER_SANITIZE_STRING), 15 | $requestUtility->getServerProtocol() 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/Botonomous/utility/SecurityUtilityTest.php: -------------------------------------------------------------------------------- 1 | generateToken(); 17 | $result = hash($securityUtility->getHashAlgorithm(), 'something dummy'); 18 | 19 | $this->assertEquals(strlen($result), strlen($token)); 20 | } 21 | 22 | /** 23 | * Test getHashAlgorithm. 24 | * 25 | * @throws \Exception 26 | */ 27 | public function testGetHashAlgorithm() 28 | { 29 | $securityUtility = new SecurityUtility(); 30 | 31 | $this->assertEquals($securityUtility::DEFAULT_HASH_ALGORITHM, $securityUtility->getHashAlgorithm()); 32 | 33 | $securityUtility->setHashAlgorithm('sha256'); 34 | $this->assertEquals('sha256', $securityUtility->getHashAlgorithm()); 35 | 36 | $this->expectException('\Exception'); 37 | $this->expectExceptionMessage('Hash algorithm is not valid'); 38 | 39 | $securityUtility->setHashAlgorithm(''); 40 | $this->assertEquals($securityUtility::DEFAULT_HASH_ALGORITHM, $securityUtility->getHashAlgorithm()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Botonomous/utility/SessionUtilityTest.php: -------------------------------------------------------------------------------- 1 | set('testKey', 'testValue'); 18 | 19 | $this->assertEquals('testValue', $sessionUtility->get('testKey')); 20 | 21 | $sessionUtility->set('testKey', 'testNewValue'); 22 | 23 | $this->assertEquals('testNewValue', $sessionUtility->get('testKey')); 24 | } 25 | 26 | /** 27 | * Test get. 28 | * 29 | * @runInSeparateProcess 30 | */ 31 | public function testGet() 32 | { 33 | $sessionUtility = new SessionUtility(); 34 | 35 | $this->assertEquals(null, $sessionUtility->get('unknownKey')); 36 | } 37 | 38 | /** 39 | * Test getSession. 40 | * 41 | * @runInSeparateProcess 42 | */ 43 | public function testGetSession() 44 | { 45 | $sessionUtility = new SessionUtility(); 46 | $session = $sessionUtility->getSession(); 47 | 48 | $session['myKey'] = 'meyValue'; 49 | $sessionUtility->setSession($session); 50 | 51 | $this->assertEquals($session, $sessionUtility->getSession()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 |