├── .codeclimate.yml ├── .csslintrc ├── .env.sample ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .rspec ├── .rubocop.yml ├── .ruby-gemset ├── .ruby-version ├── Gemfile ├── Gemfile.lock ├── MIT-LICENSE ├── README.md ├── Rakefile ├── app ├── assets │ ├── images │ │ └── message_quickly │ │ │ └── .keep │ ├── javascripts │ │ └── message_quickly │ │ │ └── index.js.erb │ └── stylesheets │ │ └── message_quickly │ │ └── application.css ├── controllers │ └── message_quickly │ │ ├── application_controller.rb │ │ └── webhooks_controller.rb ├── helpers │ └── message_quickly │ │ └── application_helper.rb └── views │ └── layouts │ └── message_quickly │ └── application.html.erb ├── bin └── rails ├── config └── routes.rb ├── lib ├── generators │ └── message_quickly │ │ └── callbacks │ │ ├── USAGE │ │ ├── callbacks_generator.rb │ │ └── templates │ │ ├── account_linking_callback.rb │ │ ├── authentication_callback.rb │ │ ├── change_update_callback.rb │ │ ├── message_delivered_callback.rb │ │ ├── message_read_callback.rb │ │ ├── message_received_callback.rb │ │ ├── postback_callback.rb │ │ ├── process_messenger_callback_job.rb │ │ ├── send_messenger_delivery_job.rb │ │ └── webhooks.rb ├── message_quickly.rb └── message_quickly │ ├── api │ ├── account_link.rb │ ├── base.rb │ ├── client.rb │ ├── facebook_api_exception.rb │ ├── graph_method_exception.rb │ ├── messages.rb │ ├── no_matching_user_exception.rb │ ├── not_permitted_exception.rb │ ├── oauth_exception.rb │ ├── send_message_exception.rb │ ├── thread_settings.rb │ └── user_profile.rb │ ├── callback.rb │ ├── callback_parser.rb │ ├── callback_registry.rb │ ├── change_update_event.rb │ ├── engine.rb │ ├── messaging │ ├── account_link_button.rb │ ├── account_link_event.rb │ ├── account_unlink_button.rb │ ├── attachment.rb │ ├── audio_attachment.rb │ ├── base.rb │ ├── button.rb │ ├── button_template_attachment.rb │ ├── delivery.rb │ ├── delivery_event.rb │ ├── element.rb │ ├── entry.rb │ ├── event.rb │ ├── generic_template_attachment.rb │ ├── image_attachment.rb │ ├── location_attachment.rb │ ├── message.rb │ ├── message_event.rb │ ├── optin_event.rb │ ├── postback_button.rb │ ├── postback_event.rb │ ├── quick_reply.rb │ ├── read_event.rb │ ├── receipt │ │ ├── address.rb │ │ ├── adjustment.rb │ │ ├── element.rb │ │ └── summary.rb │ ├── receipt_template_attachment.rb │ ├── recipient.rb │ ├── sender.rb │ ├── template_attachment.rb │ ├── user.rb │ ├── video_attachment.rb │ └── web_url_button.rb │ └── version.rb ├── message_quickly.gemspec └── spec ├── callback_parser_spec.rb ├── callback_registry_spec.rb ├── controllers └── webhooks_controller_spec.rb ├── dummy ├── README.rdoc ├── Rakefile ├── app │ ├── assets │ │ ├── images │ │ │ └── .keep │ │ ├── javascripts │ │ │ └── application.js │ │ └── stylesheets │ │ │ └── application.css │ ├── controllers │ │ ├── application_controller.rb │ │ └── concerns │ │ │ └── .keep │ ├── helpers │ │ └── application_helper.rb │ ├── jobs │ │ └── process_messenger_callback_job.rb │ ├── mailers │ │ └── .keep │ ├── models │ │ ├── .keep │ │ └── concerns │ │ │ └── .keep │ ├── views │ │ └── layouts │ │ │ └── application.html.erb │ └── webhooks │ │ ├── account_linking_callback.rb │ │ ├── authentication_callback.rb │ │ ├── change_update_callback.rb │ │ ├── message_delivered_callback.rb │ │ ├── message_read_callback.rb │ │ ├── message_received_callback.rb │ │ └── postback_callback.rb ├── bin │ ├── bundle │ ├── rails │ ├── rake │ └── setup ├── config.ru ├── config │ ├── application.rb │ ├── boot.rb │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── assets.rb │ │ ├── backtrace_silencers.rb │ │ ├── cookies_serializer.rb │ │ ├── filter_parameter_logging.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ ├── session_store.rb │ │ ├── webhooks.rb │ │ └── wrap_parameters.rb │ ├── locales │ │ └── en.yml │ ├── routes.rb │ └── secrets.yml ├── lib │ └── assets │ │ └── .keep ├── log │ └── .keep └── public │ ├── 404.html │ ├── 422.html │ ├── 500.html │ └── favicon.ico ├── fixtures ├── 12057251_909506139117248_2059695706_n.png ├── SampleVideo_1280x720_1mb.mp4 ├── account_link.json ├── change_notification_request.json ├── delivery_request.json ├── jailhouse-rock-demo.mp3 ├── message_read.json ├── message_request.json ├── message_request_with_attachment.json ├── optin_request.json └── postback_request.json ├── helpers └── application_helper_spec.rb ├── message_quickly ├── api │ ├── account_link_spec.rb │ ├── messages_spec.rb │ ├── thread_settings_spec.rb │ └── user_profile_spec.rb └── messaging │ └── user_spec.rb └── spec_helper.rb /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | brakeman: 4 | enabled: true 5 | bundler-audit: 6 | enabled: true 7 | csslint: 8 | enabled: true 9 | duplication: 10 | enabled: true 11 | config: 12 | languages: 13 | - ruby 14 | - javascript 15 | - python 16 | - php 17 | eslint: 18 | enabled: true 19 | fixme: 20 | enabled: true 21 | rubocop: 22 | enabled: true 23 | ratings: 24 | paths: 25 | - Gemfile.lock 26 | - "**.erb" 27 | - "**.haml" 28 | - "**.rb" 29 | - "**.rhtml" 30 | - "**.slim" 31 | - "**.css" 32 | - "**.inc" 33 | - "**.js" 34 | - "**.jsx" 35 | - "**.module" 36 | - "**.php" 37 | - "**.py" 38 | exclude_paths: 39 | - config/ 40 | - spec/ 41 | -------------------------------------------------------------------------------- /.csslintrc: -------------------------------------------------------------------------------- 1 | --exclude-exts=.min.css 2 | --ignore=adjoining-classes,box-model,ids,order-alphabetical,unqualified-attributes 3 | -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | FACEBOOK_MESSENGER_VERIFICATION_TOKEN= 2 | FACEBOOK_MESSENGER_PAGE_ACCESS_TOKEN= 3 | FACEBOOK_MESSENGER_PAGE_ID= 4 | 5 | FACEBOOK_MESSENGER_USER_ID= 6 | FACEBOOK_MESSENGER_USER_FIRST_NAME= 7 | FACEBOOK_MESSENGER_USER_LAST_NAME= 8 | 9 | FACEBOOK_APP_ID= 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*{.,-}min.js 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | ecmaFeatures: 2 | modules: true 3 | jsx: true 4 | 5 | env: 6 | amd: true 7 | browser: true 8 | es6: true 9 | jquery: true 10 | node: true 11 | 12 | # http://eslint.org/docs/rules/ 13 | rules: 14 | # Possible Errors 15 | comma-dangle: [2, never] 16 | no-cond-assign: 2 17 | no-console: 0 18 | no-constant-condition: 2 19 | no-control-regex: 2 20 | no-debugger: 2 21 | no-dupe-args: 2 22 | no-dupe-keys: 2 23 | no-duplicate-case: 2 24 | no-empty: 2 25 | no-empty-character-class: 2 26 | no-ex-assign: 2 27 | no-extra-boolean-cast: 2 28 | no-extra-parens: 0 29 | no-extra-semi: 2 30 | no-func-assign: 2 31 | no-inner-declarations: [2, functions] 32 | no-invalid-regexp: 2 33 | no-irregular-whitespace: 2 34 | no-negated-in-lhs: 2 35 | no-obj-calls: 2 36 | no-regex-spaces: 2 37 | no-sparse-arrays: 2 38 | no-unexpected-multiline: 2 39 | no-unreachable: 2 40 | use-isnan: 2 41 | valid-jsdoc: 0 42 | valid-typeof: 2 43 | 44 | # Best Practices 45 | accessor-pairs: 2 46 | block-scoped-var: 0 47 | complexity: [2, 6] 48 | consistent-return: 0 49 | curly: 0 50 | default-case: 0 51 | dot-location: 0 52 | dot-notation: 0 53 | eqeqeq: 2 54 | guard-for-in: 2 55 | no-alert: 2 56 | no-caller: 2 57 | no-case-declarations: 2 58 | no-div-regex: 2 59 | no-else-return: 0 60 | no-empty-label: 2 61 | no-empty-pattern: 2 62 | no-eq-null: 2 63 | no-eval: 2 64 | no-extend-native: 2 65 | no-extra-bind: 2 66 | no-fallthrough: 2 67 | no-floating-decimal: 0 68 | no-implicit-coercion: 0 69 | no-implied-eval: 2 70 | no-invalid-this: 0 71 | no-iterator: 2 72 | no-labels: 0 73 | no-lone-blocks: 2 74 | no-loop-func: 2 75 | no-magic-number: 0 76 | no-multi-spaces: 0 77 | no-multi-str: 0 78 | no-native-reassign: 2 79 | no-new-func: 2 80 | no-new-wrappers: 2 81 | no-new: 2 82 | no-octal-escape: 2 83 | no-octal: 2 84 | no-proto: 2 85 | no-redeclare: 2 86 | no-return-assign: 2 87 | no-script-url: 2 88 | no-self-compare: 2 89 | no-sequences: 0 90 | no-throw-literal: 0 91 | no-unused-expressions: 2 92 | no-useless-call: 2 93 | no-useless-concat: 2 94 | no-void: 2 95 | no-warning-comments: 0 96 | no-with: 2 97 | radix: 2 98 | vars-on-top: 0 99 | wrap-iife: 2 100 | yoda: 0 101 | 102 | # Strict 103 | strict: 0 104 | 105 | # Variables 106 | init-declarations: 0 107 | no-catch-shadow: 2 108 | no-delete-var: 2 109 | no-label-var: 2 110 | no-shadow-restricted-names: 2 111 | no-shadow: 0 112 | no-undef-init: 2 113 | no-undef: 0 114 | no-undefined: 0 115 | no-unused-vars: 0 116 | no-use-before-define: 0 117 | 118 | # Node.js and CommonJS 119 | callback-return: 2 120 | global-require: 2 121 | handle-callback-err: 2 122 | no-mixed-requires: 0 123 | no-new-require: 0 124 | no-path-concat: 2 125 | no-process-exit: 2 126 | no-restricted-modules: 0 127 | no-sync: 0 128 | 129 | # Stylistic Issues 130 | array-bracket-spacing: 0 131 | block-spacing: 0 132 | brace-style: 0 133 | camelcase: 0 134 | comma-spacing: 0 135 | comma-style: 0 136 | computed-property-spacing: 0 137 | consistent-this: 0 138 | eol-last: 0 139 | func-names: 0 140 | func-style: 0 141 | id-length: 0 142 | id-match: 0 143 | indent: 0 144 | jsx-quotes: 0 145 | key-spacing: 0 146 | linebreak-style: 0 147 | lines-around-comment: 0 148 | max-depth: 0 149 | max-len: 0 150 | max-nested-callbacks: 0 151 | max-params: 0 152 | max-statements: [2, 30] 153 | new-cap: 0 154 | new-parens: 0 155 | newline-after-var: 0 156 | no-array-constructor: 0 157 | no-bitwise: 0 158 | no-continue: 0 159 | no-inline-comments: 0 160 | no-lonely-if: 0 161 | no-mixed-spaces-and-tabs: 0 162 | no-multiple-empty-lines: 0 163 | no-negated-condition: 0 164 | no-nested-ternary: 0 165 | no-new-object: 0 166 | no-plusplus: 0 167 | no-restricted-syntax: 0 168 | no-spaced-func: 0 169 | no-ternary: 0 170 | no-trailing-spaces: 0 171 | no-underscore-dangle: 0 172 | no-unneeded-ternary: 0 173 | object-curly-spacing: 0 174 | one-var: 0 175 | operator-assignment: 0 176 | operator-linebreak: 0 177 | padded-blocks: 0 178 | quote-props: 0 179 | quotes: 0 180 | require-jsdoc: 0 181 | semi-spacing: 0 182 | semi: 0 183 | sort-vars: 0 184 | space-after-keywords: 0 185 | space-before-blocks: 0 186 | space-before-function-paren: 0 187 | space-before-keywords: 0 188 | space-in-parens: 0 189 | space-infix-ops: 0 190 | space-return-throw-case: 0 191 | space-unary-ops: 0 192 | spaced-comment: 0 193 | wrap-regex: 0 194 | 195 | # ECMAScript 6 196 | arrow-body-style: 0 197 | arrow-parens: 0 198 | arrow-spacing: 0 199 | constructor-super: 0 200 | generator-star-spacing: 0 201 | no-arrow-condition: 0 202 | no-class-assign: 0 203 | no-const-assign: 0 204 | no-dupe-class-members: 0 205 | no-this-before-super: 0 206 | no-var: 0 207 | object-shorthand: 0 208 | prefer-arrow-callback: 0 209 | prefer-const: 0 210 | prefer-reflect: 0 211 | prefer-spread: 0 212 | prefer-template: 0 213 | require-yield: 0 214 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle/ 2 | log/*.log 3 | pkg/ 4 | spec/dummy/db/*.sqlite3 5 | spec/dummy/db/*.sqlite3-journal 6 | spec/dummy/log/*.log 7 | spec/dummy/tmp/ 8 | spec/dummy/.sass-cache 9 | .env 10 | .DS_Store 11 | *.gem 12 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | DisabledByDefault: true 3 | 4 | #################### Lint ################################ 5 | 6 | Lint/AmbiguousOperator: 7 | Description: >- 8 | Checks for ambiguous operators in the first argument of a 9 | method invocation without parentheses. 10 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-as-args' 11 | Enabled: true 12 | 13 | Lint/AmbiguousRegexpLiteral: 14 | Description: >- 15 | Checks for ambiguous regexp literals in the first argument of 16 | a method invocation without parenthesis. 17 | Enabled: true 18 | 19 | Lint/AssignmentInCondition: 20 | Description: "Don't use assignment in conditions." 21 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition' 22 | Enabled: true 23 | 24 | Lint/BlockAlignment: 25 | Description: 'Align block ends correctly.' 26 | Enabled: true 27 | 28 | Lint/CircularArgumentReference: 29 | Description: "Don't refer to the keyword argument in the default value." 30 | Enabled: true 31 | 32 | Lint/ConditionPosition: 33 | Description: >- 34 | Checks for condition placed in a confusing position relative to 35 | the keyword. 36 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#same-line-condition' 37 | Enabled: true 38 | 39 | Lint/Debugger: 40 | Description: 'Check for debugger calls.' 41 | Enabled: true 42 | 43 | Lint/DefEndAlignment: 44 | Description: 'Align ends corresponding to defs correctly.' 45 | Enabled: true 46 | 47 | Lint/DeprecatedClassMethods: 48 | Description: 'Check for deprecated class method calls.' 49 | Enabled: true 50 | 51 | Lint/DuplicateMethods: 52 | Description: 'Check for duplicate methods calls.' 53 | Enabled: true 54 | 55 | Lint/EachWithObjectArgument: 56 | Description: 'Check for immutable argument given to each_with_object.' 57 | Enabled: true 58 | 59 | Lint/ElseLayout: 60 | Description: 'Check for odd code arrangement in an else block.' 61 | Enabled: true 62 | 63 | Lint/EmptyEnsure: 64 | Description: 'Checks for empty ensure block.' 65 | Enabled: true 66 | 67 | Lint/EmptyInterpolation: 68 | Description: 'Checks for empty string interpolation.' 69 | Enabled: true 70 | 71 | Lint/EndAlignment: 72 | Description: 'Align ends correctly.' 73 | Enabled: true 74 | 75 | Lint/EndInMethod: 76 | Description: 'END blocks should not be placed inside method definitions.' 77 | Enabled: true 78 | 79 | Lint/EnsureReturn: 80 | Description: 'Do not use return in an ensure block.' 81 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-return-ensure' 82 | Enabled: true 83 | 84 | Lint/Eval: 85 | Description: 'The use of eval represents a serious security risk.' 86 | Enabled: true 87 | 88 | Lint/FormatParameterMismatch: 89 | Description: 'The number of parameters to format/sprint must match the fields.' 90 | Enabled: true 91 | 92 | Lint/HandleExceptions: 93 | Description: "Don't suppress exception." 94 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions' 95 | Enabled: true 96 | 97 | Lint/InvalidCharacterLiteral: 98 | Description: >- 99 | Checks for invalid character literals with a non-escaped 100 | whitespace character. 101 | Enabled: true 102 | 103 | Lint/LiteralInCondition: 104 | Description: 'Checks of literals used in conditions.' 105 | Enabled: true 106 | 107 | Lint/LiteralInInterpolation: 108 | Description: 'Checks for literals used in interpolation.' 109 | Enabled: true 110 | 111 | Lint/Loop: 112 | Description: >- 113 | Use Kernel#loop with break rather than begin/end/until or 114 | begin/end/while for post-loop tests. 115 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#loop-with-break' 116 | Enabled: true 117 | 118 | Lint/NestedMethodDefinition: 119 | Description: 'Do not use nested method definitions.' 120 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-methods' 121 | Enabled: true 122 | 123 | Lint/NonLocalExitFromIterator: 124 | Description: 'Do not use return in iterator to cause non-local exit.' 125 | Enabled: true 126 | 127 | Lint/ParenthesesAsGroupedExpression: 128 | Description: >- 129 | Checks for method calls with a space before the opening 130 | parenthesis. 131 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces' 132 | Enabled: true 133 | 134 | Lint/RequireParentheses: 135 | Description: >- 136 | Use parentheses in the method call to avoid confusion 137 | about precedence. 138 | Enabled: true 139 | 140 | Lint/RescueException: 141 | Description: 'Avoid rescuing the Exception class.' 142 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-blind-rescues' 143 | Enabled: true 144 | 145 | Lint/ShadowingOuterLocalVariable: 146 | Description: >- 147 | Do not use the same name as outer local variable 148 | for block arguments or block local variables. 149 | Enabled: true 150 | 151 | Lint/StringConversionInInterpolation: 152 | Description: 'Checks for Object#to_s usage in string interpolation.' 153 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-to-s' 154 | Enabled: true 155 | 156 | Lint/UnderscorePrefixedVariableName: 157 | Description: 'Do not use prefix `_` for a variable that is used.' 158 | Enabled: true 159 | 160 | Lint/UnneededDisable: 161 | Description: >- 162 | Checks for rubocop:disable comments that can be removed. 163 | Note: this cop is not disabled when disabling all cops. 164 | It must be explicitly disabled. 165 | Enabled: true 166 | 167 | Lint/UnusedBlockArgument: 168 | Description: 'Checks for unused block arguments.' 169 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars' 170 | Enabled: true 171 | 172 | Lint/UnusedMethodArgument: 173 | Description: 'Checks for unused method arguments.' 174 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars' 175 | Enabled: true 176 | 177 | Lint/UnreachableCode: 178 | Description: 'Unreachable code.' 179 | Enabled: true 180 | 181 | Lint/UselessAccessModifier: 182 | Description: 'Checks for useless access modifiers.' 183 | Enabled: true 184 | 185 | Lint/UselessAssignment: 186 | Description: 'Checks for useless assignment to a local variable.' 187 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars' 188 | Enabled: true 189 | 190 | Lint/UselessComparison: 191 | Description: 'Checks for comparison of something with itself.' 192 | Enabled: true 193 | 194 | Lint/UselessElseWithoutRescue: 195 | Description: 'Checks for useless `else` in `begin..end` without `rescue`.' 196 | Enabled: true 197 | 198 | Lint/UselessSetterCall: 199 | Description: 'Checks for useless setter call to a local variable.' 200 | Enabled: true 201 | 202 | Lint/Void: 203 | Description: 'Possible use of operator/literal/variable in void context.' 204 | Enabled: true 205 | 206 | ###################### Metrics #################################### 207 | 208 | Metrics/AbcSize: 209 | Description: >- 210 | A calculated magnitude based on number of assignments, 211 | branches, and conditions. 212 | Reference: 'http://c2.com/cgi/wiki?AbcMetric' 213 | Enabled: false 214 | Max: 20 215 | 216 | Metrics/BlockNesting: 217 | Description: 'Avoid excessive block nesting' 218 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count' 219 | Enabled: true 220 | Max: 4 221 | 222 | Metrics/ClassLength: 223 | Description: 'Avoid classes longer than 250 lines of code.' 224 | Enabled: true 225 | Max: 250 226 | 227 | Metrics/CyclomaticComplexity: 228 | Description: >- 229 | A complexity metric that is strongly correlated to the number 230 | of test cases needed to validate a method. 231 | Enabled: true 232 | 233 | Metrics/LineLength: 234 | Description: 'Limit lines to 80 characters.' 235 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits' 236 | Enabled: false 237 | 238 | Metrics/MethodLength: 239 | Description: 'Avoid methods longer than 30 lines of code.' 240 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods' 241 | Enabled: true 242 | Max: 30 243 | 244 | Metrics/ModuleLength: 245 | Description: 'Avoid modules longer than 250 lines of code.' 246 | Enabled: true 247 | Max: 250 248 | 249 | Metrics/ParameterLists: 250 | Description: 'Avoid parameter lists longer than three or four parameters.' 251 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params' 252 | Enabled: true 253 | 254 | Metrics/PerceivedComplexity: 255 | Description: >- 256 | A complexity metric geared towards measuring complexity for a 257 | human reader. 258 | Enabled: false 259 | 260 | ##################### Performance ############################# 261 | 262 | Performance/Count: 263 | Description: >- 264 | Use `count` instead of `select...size`, `reject...size`, 265 | `select...count`, `reject...count`, `select...length`, 266 | and `reject...length`. 267 | Enabled: true 268 | 269 | Performance/Detect: 270 | Description: >- 271 | Use `detect` instead of `select.first`, `find_all.first`, 272 | `select.last`, and `find_all.last`. 273 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerabledetect-vs-enumerableselectfirst-code' 274 | Enabled: true 275 | 276 | Performance/FlatMap: 277 | Description: >- 278 | Use `Enumerable#flat_map` 279 | instead of `Enumerable#map...Array#flatten(1)` 280 | or `Enumberable#collect..Array#flatten(1)` 281 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablemaparrayflatten-vs-enumerableflat_map-code' 282 | Enabled: true 283 | EnabledForFlattenWithoutParams: false 284 | # If enabled, this cop will warn about usages of 285 | # `flatten` being called without any parameters. 286 | # This can be dangerous since `flat_map` will only flatten 1 level, and 287 | # `flatten` without any parameters can flatten multiple levels. 288 | 289 | Performance/ReverseEach: 290 | Description: 'Use `reverse_each` instead of `reverse.each`.' 291 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablereverseeach-vs-enumerablereverse_each-code' 292 | Enabled: true 293 | 294 | Performance/Sample: 295 | Description: >- 296 | Use `sample` instead of `shuffle.first`, 297 | `shuffle.last`, and `shuffle[Fixnum]`. 298 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#arrayshufflefirst-vs-arraysample-code' 299 | Enabled: true 300 | 301 | Performance/Size: 302 | Description: >- 303 | Use `size` instead of `count` for counting 304 | the number of elements in `Array` and `Hash`. 305 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#arraycount-vs-arraysize-code' 306 | Enabled: true 307 | 308 | Performance/StringReplacement: 309 | Description: >- 310 | Use `tr` instead of `gsub` when you are replacing the same 311 | number of characters. Use `delete` instead of `gsub` when 312 | you are deleting characters. 313 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#stringgsub-vs-stringtr-code' 314 | Enabled: true 315 | 316 | ##################### Rails ################################## 317 | 318 | Rails/ActionFilter: 319 | Description: 'Enforces consistent use of action filter methods.' 320 | Enabled: false 321 | 322 | Rails/Date: 323 | Description: >- 324 | Checks the correct usage of date aware methods, 325 | such as Date.today, Date.current etc. 326 | Enabled: false 327 | 328 | Rails/Delegate: 329 | Description: 'Prefer delegate method for delegations.' 330 | Enabled: false 331 | 332 | Rails/FindBy: 333 | Description: 'Prefer find_by over where.first.' 334 | Enabled: false 335 | 336 | Rails/FindEach: 337 | Description: 'Prefer all.find_each over all.find.' 338 | Enabled: false 339 | 340 | Rails/HasAndBelongsToMany: 341 | Description: 'Prefer has_many :through to has_and_belongs_to_many.' 342 | Enabled: false 343 | 344 | Rails/Output: 345 | Description: 'Checks for calls to puts, print, etc.' 346 | Enabled: false 347 | 348 | Rails/ReadWriteAttribute: 349 | Description: >- 350 | Checks for read_attribute(:attr) and 351 | write_attribute(:attr, val). 352 | Enabled: false 353 | 354 | Rails/ScopeArgs: 355 | Description: 'Checks the arguments of ActiveRecord scopes.' 356 | Enabled: false 357 | 358 | Rails/TimeZone: 359 | Description: 'Checks the correct usage of time zone aware methods.' 360 | StyleGuide: 'https://github.com/bbatsov/rails-style-guide#time' 361 | Reference: 'http://danilenko.org/2012/7/6/rails_timezones' 362 | Enabled: false 363 | 364 | Rails/Validation: 365 | Description: 'Use validates :attribute, hash of validations.' 366 | Enabled: false 367 | 368 | ################## Style ################################# 369 | 370 | Style/AccessModifierIndentation: 371 | Description: Check indentation of private/protected visibility modifiers. 372 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#indent-public-private-protected' 373 | Enabled: false 374 | 375 | Style/AccessorMethodName: 376 | Description: Check the naming of accessor methods for get_/set_. 377 | Enabled: false 378 | 379 | Style/Alias: 380 | Description: 'Use alias_method instead of alias.' 381 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#alias-method' 382 | Enabled: false 383 | 384 | Style/AlignArray: 385 | Description: >- 386 | Align the elements of an array literal if they span more than 387 | one line. 388 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#align-multiline-arrays' 389 | Enabled: false 390 | 391 | Style/AlignHash: 392 | Description: >- 393 | Align the elements of a hash literal if they span more than 394 | one line. 395 | Enabled: false 396 | 397 | Style/AlignParameters: 398 | Description: >- 399 | Align the parameters of a method call if they span more 400 | than one line. 401 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-double-indent' 402 | Enabled: false 403 | 404 | Style/AndOr: 405 | Description: 'Use &&/|| instead of and/or.' 406 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-and-or-or' 407 | Enabled: false 408 | 409 | Style/ArrayJoin: 410 | Description: 'Use Array#join instead of Array#*.' 411 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#array-join' 412 | Enabled: false 413 | 414 | Style/AsciiComments: 415 | Description: 'Use only ascii symbols in comments.' 416 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-comments' 417 | Enabled: false 418 | 419 | Style/AsciiIdentifiers: 420 | Description: 'Use only ascii symbols in identifiers.' 421 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-identifiers' 422 | Enabled: false 423 | 424 | Style/Attr: 425 | Description: 'Checks for uses of Module#attr.' 426 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr' 427 | Enabled: false 428 | 429 | Style/BeginBlock: 430 | Description: 'Avoid the use of BEGIN blocks.' 431 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-BEGIN-blocks' 432 | Enabled: false 433 | 434 | Style/BarePercentLiterals: 435 | Description: 'Checks if usage of %() or %Q() matches configuration.' 436 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q-shorthand' 437 | Enabled: false 438 | 439 | Style/BlockComments: 440 | Description: 'Do not use block comments.' 441 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-block-comments' 442 | Enabled: false 443 | 444 | Style/BlockEndNewline: 445 | Description: 'Put end statement of multiline block on its own line.' 446 | Enabled: false 447 | 448 | Style/BlockDelimiters: 449 | Description: >- 450 | Avoid using {...} for multi-line blocks (multiline chaining is 451 | always ugly). 452 | Prefer {...} over do...end for single-line blocks. 453 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks' 454 | Enabled: false 455 | 456 | Style/BracesAroundHashParameters: 457 | Description: 'Enforce braces style around hash parameters.' 458 | Enabled: false 459 | 460 | Style/CaseEquality: 461 | Description: 'Avoid explicit use of the case equality operator(===).' 462 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-case-equality' 463 | Enabled: false 464 | 465 | Style/CaseIndentation: 466 | Description: 'Indentation of when in a case/when/[else/]end.' 467 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#indent-when-to-case' 468 | Enabled: false 469 | 470 | Style/CharacterLiteral: 471 | Description: 'Checks for uses of character literals.' 472 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-character-literals' 473 | Enabled: false 474 | 475 | Style/ClassAndModuleCamelCase: 476 | Description: 'Use CamelCase for classes and modules.' 477 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#camelcase-classes' 478 | Enabled: false 479 | 480 | Style/ClassAndModuleChildren: 481 | Description: 'Checks style of children classes and modules.' 482 | Enabled: false 483 | 484 | Style/ClassCheck: 485 | Description: 'Enforces consistent use of `Object#is_a?` or `Object#kind_of?`.' 486 | Enabled: false 487 | 488 | Style/ClassMethods: 489 | Description: 'Use self when defining module/class methods.' 490 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#def-self-class-methods' 491 | Enabled: false 492 | 493 | Style/ClassVars: 494 | Description: 'Avoid the use of class variables.' 495 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-class-vars' 496 | Enabled: false 497 | 498 | Style/ClosingParenthesisIndentation: 499 | Description: 'Checks the indentation of hanging closing parentheses.' 500 | Enabled: false 501 | 502 | Style/ColonMethodCall: 503 | Description: 'Do not use :: for method call.' 504 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#double-colons' 505 | Enabled: false 506 | 507 | Style/CommandLiteral: 508 | Description: 'Use `` or %x around command literals.' 509 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-x' 510 | Enabled: false 511 | 512 | Style/CommentAnnotation: 513 | Description: 'Checks formatting of annotation comments.' 514 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#annotate-keywords' 515 | Enabled: false 516 | 517 | Style/CommentIndentation: 518 | Description: 'Indentation of comments.' 519 | Enabled: false 520 | 521 | Style/ConstantName: 522 | Description: 'Constants should use SCREAMING_SNAKE_CASE.' 523 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#screaming-snake-case' 524 | Enabled: false 525 | 526 | Style/DefWithParentheses: 527 | Description: 'Use def with parentheses when there are arguments.' 528 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens' 529 | Enabled: false 530 | 531 | Style/DeprecatedHashMethods: 532 | Description: 'Checks for use of deprecated Hash methods.' 533 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-key' 534 | Enabled: false 535 | 536 | Style/Documentation: 537 | Description: 'Document classes and non-namespace modules.' 538 | Enabled: false 539 | 540 | Style/DotPosition: 541 | Description: 'Checks the position of the dot in multi-line method calls.' 542 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains' 543 | Enabled: false 544 | 545 | Style/DoubleNegation: 546 | Description: 'Checks for uses of double negation (!!).' 547 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-bang-bang' 548 | Enabled: false 549 | 550 | Style/EachWithObject: 551 | Description: 'Prefer `each_with_object` over `inject` or `reduce`.' 552 | Enabled: false 553 | 554 | Style/ElseAlignment: 555 | Description: 'Align elses and elsifs correctly.' 556 | Enabled: false 557 | 558 | Style/EmptyElse: 559 | Description: 'Avoid empty else-clauses.' 560 | Enabled: false 561 | 562 | Style/EmptyLineBetweenDefs: 563 | Description: 'Use empty lines between defs.' 564 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#empty-lines-between-methods' 565 | Enabled: false 566 | 567 | Style/EmptyLines: 568 | Description: "Don't use several empty lines in a row." 569 | Enabled: false 570 | 571 | Style/EmptyLinesAroundAccessModifier: 572 | Description: "Keep blank lines around access modifiers." 573 | Enabled: false 574 | 575 | Style/EmptyLinesAroundBlockBody: 576 | Description: "Keeps track of empty lines around block bodies." 577 | Enabled: false 578 | 579 | Style/EmptyLinesAroundClassBody: 580 | Description: "Keeps track of empty lines around class bodies." 581 | Enabled: false 582 | 583 | Style/EmptyLinesAroundModuleBody: 584 | Description: "Keeps track of empty lines around module bodies." 585 | Enabled: false 586 | 587 | Style/EmptyLinesAroundMethodBody: 588 | Description: "Keeps track of empty lines around method bodies." 589 | Enabled: false 590 | 591 | Style/EmptyLiteral: 592 | Description: 'Prefer literals to Array.new/Hash.new/String.new.' 593 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#literal-array-hash' 594 | Enabled: false 595 | 596 | Style/EndBlock: 597 | Description: 'Avoid the use of END blocks.' 598 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-END-blocks' 599 | Enabled: false 600 | 601 | Style/EndOfLine: 602 | Description: 'Use Unix-style line endings.' 603 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#crlf' 604 | Enabled: false 605 | 606 | Style/EvenOdd: 607 | Description: 'Favor the use of Fixnum#even? && Fixnum#odd?' 608 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods' 609 | Enabled: false 610 | 611 | Style/ExtraSpacing: 612 | Description: 'Do not use unnecessary spacing.' 613 | Enabled: false 614 | 615 | Style/FileName: 616 | Description: 'Use snake_case for source file names.' 617 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-files' 618 | Enabled: false 619 | 620 | Style/InitialIndentation: 621 | Description: >- 622 | Checks the indentation of the first non-blank non-comment line in a file. 623 | Enabled: false 624 | 625 | Style/FirstParameterIndentation: 626 | Description: 'Checks the indentation of the first parameter in a method call.' 627 | Enabled: false 628 | 629 | Style/FlipFlop: 630 | Description: 'Checks for flip flops' 631 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-flip-flops' 632 | Enabled: false 633 | 634 | Style/For: 635 | Description: 'Checks use of for or each in multiline loops.' 636 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-for-loops' 637 | Enabled: false 638 | 639 | Style/FormatString: 640 | Description: 'Enforce the use of Kernel#sprintf, Kernel#format or String#%.' 641 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#sprintf' 642 | Enabled: false 643 | 644 | Style/GlobalVars: 645 | Description: 'Do not introduce global variables.' 646 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#instance-vars' 647 | Reference: 'http://www.zenspider.com/Languages/Ruby/QuickRef.html' 648 | Enabled: false 649 | 650 | Style/GuardClause: 651 | Description: 'Check for conditionals that can be replaced with guard clauses' 652 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals' 653 | Enabled: false 654 | 655 | Style/HashSyntax: 656 | Description: >- 657 | Prefer Ruby 1.9 hash syntax { a: 1, b: 2 } over 1.8 syntax 658 | { :a => 1, :b => 2 }. 659 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-literals' 660 | Enabled: false 661 | 662 | Style/IfUnlessModifier: 663 | Description: >- 664 | Favor modifier if/unless usage when you have a 665 | single-line body. 666 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier' 667 | Enabled: false 668 | 669 | Style/IfWithSemicolon: 670 | Description: 'Do not use if x; .... Use the ternary operator instead.' 671 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon-ifs' 672 | Enabled: false 673 | 674 | Style/IndentationConsistency: 675 | Description: 'Keep indentation straight.' 676 | Enabled: false 677 | 678 | Style/IndentationWidth: 679 | Description: 'Use 2 spaces for indentation.' 680 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation' 681 | Enabled: false 682 | 683 | Style/IndentArray: 684 | Description: >- 685 | Checks the indentation of the first element in an array 686 | literal. 687 | Enabled: false 688 | 689 | Style/IndentHash: 690 | Description: 'Checks the indentation of the first key in a hash literal.' 691 | Enabled: false 692 | 693 | Style/InfiniteLoop: 694 | Description: 'Use Kernel#loop for infinite loops.' 695 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#infinite-loop' 696 | Enabled: false 697 | 698 | Style/Lambda: 699 | Description: 'Use the new lambda literal syntax for single-line blocks.' 700 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#lambda-multi-line' 701 | Enabled: false 702 | 703 | Style/LambdaCall: 704 | Description: 'Use lambda.call(...) instead of lambda.(...).' 705 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc-call' 706 | Enabled: false 707 | 708 | Style/LeadingCommentSpace: 709 | Description: 'Comments should start with a space.' 710 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-space' 711 | Enabled: false 712 | 713 | Style/LineEndConcatenation: 714 | Description: >- 715 | Use \ instead of + or << to concatenate two string literals at 716 | line end. 717 | Enabled: false 718 | 719 | Style/MethodCallParentheses: 720 | Description: 'Do not use parentheses for method calls with no arguments.' 721 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-args-no-parens' 722 | Enabled: false 723 | 724 | Style/MethodDefParentheses: 725 | Description: >- 726 | Checks if the method definitions have or don't have 727 | parentheses. 728 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens' 729 | Enabled: false 730 | 731 | Style/MethodName: 732 | Description: 'Use the configured style when naming methods.' 733 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars' 734 | Enabled: false 735 | 736 | Style/ModuleFunction: 737 | Description: 'Checks for usage of `extend self` in modules.' 738 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#module-function' 739 | Enabled: false 740 | 741 | Style/MultilineBlockChain: 742 | Description: 'Avoid multi-line chains of blocks.' 743 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks' 744 | Enabled: false 745 | 746 | Style/MultilineBlockLayout: 747 | Description: 'Ensures newlines after multiline block do statements.' 748 | Enabled: false 749 | 750 | Style/MultilineIfThen: 751 | Description: 'Do not use then for multi-line if/unless.' 752 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-then' 753 | Enabled: false 754 | 755 | Style/MultilineOperationIndentation: 756 | Description: >- 757 | Checks indentation of binary operations that span more than 758 | one line. 759 | Enabled: false 760 | 761 | Style/MultilineTernaryOperator: 762 | Description: >- 763 | Avoid multi-line ?: (the ternary operator); 764 | use if/unless instead. 765 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-multiline-ternary' 766 | Enabled: false 767 | 768 | Style/NegatedIf: 769 | Description: >- 770 | Favor unless over if for negative conditions 771 | (or control flow or). 772 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#unless-for-negatives' 773 | Enabled: false 774 | 775 | Style/NegatedWhile: 776 | Description: 'Favor until over while for negative conditions.' 777 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#until-for-negatives' 778 | Enabled: false 779 | 780 | Style/NestedTernaryOperator: 781 | Description: 'Use one expression per branch in a ternary operator.' 782 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-ternary' 783 | Enabled: false 784 | 785 | Style/Next: 786 | Description: 'Use `next` to skip iteration instead of a condition at the end.' 787 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals' 788 | Enabled: false 789 | 790 | Style/NilComparison: 791 | Description: 'Prefer x.nil? to x == nil.' 792 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods' 793 | Enabled: false 794 | 795 | Style/NonNilCheck: 796 | Description: 'Checks for redundant nil checks.' 797 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-non-nil-checks' 798 | Enabled: false 799 | 800 | Style/Not: 801 | Description: 'Use ! instead of not.' 802 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bang-not-not' 803 | Enabled: false 804 | 805 | Style/NumericLiterals: 806 | Description: >- 807 | Add underscores to large numeric literals to improve their 808 | readability. 809 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscores-in-numerics' 810 | Enabled: false 811 | 812 | Style/OneLineConditional: 813 | Description: >- 814 | Favor the ternary operator(?:) over 815 | if/then/else/end constructs. 816 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#ternary-operator' 817 | Enabled: false 818 | 819 | Style/OpMethod: 820 | Description: 'When defining binary operators, name the argument other.' 821 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#other-arg' 822 | Enabled: false 823 | 824 | Style/OptionalArguments: 825 | Description: >- 826 | Checks for optional arguments that do not appear at the end 827 | of the argument list 828 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#optional-arguments' 829 | Enabled: false 830 | 831 | Style/ParallelAssignment: 832 | Description: >- 833 | Check for simple usages of parallel assignment. 834 | It will only warn when the number of variables 835 | matches on both sides of the assignment. 836 | This also provides performance benefits 837 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parallel-assignment' 838 | Enabled: false 839 | 840 | Style/ParenthesesAroundCondition: 841 | Description: >- 842 | Don't use parentheses around the condition of an 843 | if/unless/while. 844 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-parens-if' 845 | Enabled: false 846 | 847 | Style/PercentLiteralDelimiters: 848 | Description: 'Use `%`-literal delimiters consistently' 849 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-literal-braces' 850 | Enabled: false 851 | 852 | Style/PercentQLiterals: 853 | Description: 'Checks if uses of %Q/%q match the configured preference.' 854 | Enabled: false 855 | 856 | Style/PerlBackrefs: 857 | Description: 'Avoid Perl-style regex back references.' 858 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers' 859 | Enabled: false 860 | 861 | Style/PredicateName: 862 | Description: 'Check the names of predicate methods.' 863 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark' 864 | Enabled: false 865 | 866 | Style/Proc: 867 | Description: 'Use proc instead of Proc.new.' 868 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc' 869 | Enabled: false 870 | 871 | Style/RaiseArgs: 872 | Description: 'Checks the arguments passed to raise/fail.' 873 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#exception-class-messages' 874 | Enabled: false 875 | 876 | Style/RedundantBegin: 877 | Description: "Don't use begin blocks when they are not needed." 878 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#begin-implicit' 879 | Enabled: false 880 | 881 | Style/RedundantException: 882 | Description: "Checks for an obsolete RuntimeException argument in raise/fail." 883 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-explicit-runtimeerror' 884 | Enabled: false 885 | 886 | Style/RedundantReturn: 887 | Description: "Don't use return where it's not required." 888 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-explicit-return' 889 | Enabled: false 890 | 891 | Style/RedundantSelf: 892 | Description: "Don't use self where it's not needed." 893 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-self-unless-required' 894 | Enabled: false 895 | 896 | Style/RegexpLiteral: 897 | Description: 'Use / or %r around regular expressions.' 898 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-r' 899 | Enabled: false 900 | 901 | Style/RescueEnsureAlignment: 902 | Description: 'Align rescues and ensures correctly.' 903 | Enabled: false 904 | 905 | Style/RescueModifier: 906 | Description: 'Avoid using rescue in its modifier form.' 907 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-rescue-modifiers' 908 | Enabled: false 909 | 910 | Style/SelfAssignment: 911 | Description: >- 912 | Checks for places where self-assignment shorthand should have 913 | been used. 914 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#self-assignment' 915 | Enabled: false 916 | 917 | Style/Semicolon: 918 | Description: "Don't use semicolons to terminate expressions." 919 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon' 920 | Enabled: false 921 | 922 | Style/SignalException: 923 | Description: 'Checks for proper usage of fail and raise.' 924 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#fail-method' 925 | Enabled: false 926 | 927 | Style/SingleLineBlockParams: 928 | Description: 'Enforces the names of some block params.' 929 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#reduce-blocks' 930 | Enabled: false 931 | 932 | Style/SingleLineMethods: 933 | Description: 'Avoid single-line methods.' 934 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-single-line-methods' 935 | Enabled: false 936 | 937 | Style/SpaceBeforeFirstArg: 938 | Description: >- 939 | Checks that exactly one space is used between a method name 940 | and the first argument for method calls without parentheses. 941 | Enabled: true 942 | 943 | Style/SpaceAfterColon: 944 | Description: 'Use spaces after colons.' 945 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' 946 | Enabled: false 947 | 948 | Style/SpaceAfterComma: 949 | Description: 'Use spaces after commas.' 950 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' 951 | Enabled: false 952 | 953 | Style/SpaceAroundKeyword: 954 | Description: 'Use spaces around keywords.' 955 | Enabled: false 956 | 957 | Style/SpaceAfterMethodName: 958 | Description: >- 959 | Do not put a space between a method name and the opening 960 | parenthesis in a method definition. 961 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces' 962 | Enabled: false 963 | 964 | Style/SpaceAfterNot: 965 | Description: Tracks redundant space after the ! operator. 966 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-space-bang' 967 | Enabled: false 968 | 969 | Style/SpaceAfterSemicolon: 970 | Description: 'Use spaces after semicolons.' 971 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' 972 | Enabled: false 973 | 974 | Style/SpaceBeforeBlockBraces: 975 | Description: >- 976 | Checks that the left block brace has or doesn't have space 977 | before it. 978 | Enabled: false 979 | 980 | Style/SpaceBeforeComma: 981 | Description: 'No spaces before commas.' 982 | Enabled: false 983 | 984 | Style/SpaceBeforeComment: 985 | Description: >- 986 | Checks for missing space between code and a comment on the 987 | same line. 988 | Enabled: false 989 | 990 | Style/SpaceBeforeSemicolon: 991 | Description: 'No spaces before semicolons.' 992 | Enabled: false 993 | 994 | Style/SpaceInsideBlockBraces: 995 | Description: >- 996 | Checks that block braces have or don't have surrounding space. 997 | For blocks taking parameters, checks that the left brace has 998 | or doesn't have trailing space. 999 | Enabled: false 1000 | 1001 | Style/SpaceAroundBlockParameters: 1002 | Description: 'Checks the spacing inside and after block parameters pipes.' 1003 | Enabled: false 1004 | 1005 | Style/SpaceAroundEqualsInParameterDefault: 1006 | Description: >- 1007 | Checks that the equals signs in parameter default assignments 1008 | have or don't have surrounding space depending on 1009 | configuration. 1010 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-around-equals' 1011 | Enabled: false 1012 | 1013 | Style/SpaceAroundOperators: 1014 | Description: 'Use a single space around operators.' 1015 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' 1016 | Enabled: false 1017 | 1018 | Style/SpaceInsideBrackets: 1019 | Description: 'No spaces after [ or before ].' 1020 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces' 1021 | Enabled: false 1022 | 1023 | Style/SpaceInsideHashLiteralBraces: 1024 | Description: "Use spaces inside hash literal braces - or don't." 1025 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' 1026 | Enabled: false 1027 | 1028 | Style/SpaceInsideParens: 1029 | Description: 'No spaces after ( or before ).' 1030 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces' 1031 | Enabled: false 1032 | 1033 | Style/SpaceInsideRangeLiteral: 1034 | Description: 'No spaces inside range literals.' 1035 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-space-inside-range-literals' 1036 | Enabled: false 1037 | 1038 | Style/SpaceInsideStringInterpolation: 1039 | Description: 'Checks for padding/surrounding spaces inside string interpolation.' 1040 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#string-interpolation' 1041 | Enabled: false 1042 | 1043 | Style/SpecialGlobalVars: 1044 | Description: 'Avoid Perl-style global variables.' 1045 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms' 1046 | Enabled: false 1047 | 1048 | Style/StringLiterals: 1049 | Description: 'Checks if uses of quotes match the configured preference.' 1050 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-string-literals' 1051 | Enabled: false 1052 | 1053 | Style/StringLiteralsInInterpolation: 1054 | Description: >- 1055 | Checks if uses of quotes inside expressions in interpolated 1056 | strings match the configured preference. 1057 | Enabled: false 1058 | 1059 | Style/StructInheritance: 1060 | Description: 'Checks for inheritance from Struct.new.' 1061 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-extend-struct-new' 1062 | Enabled: false 1063 | 1064 | Style/SymbolLiteral: 1065 | Description: 'Use plain symbols instead of string symbols when possible.' 1066 | Enabled: false 1067 | 1068 | Style/SymbolProc: 1069 | Description: 'Use symbols as procs instead of blocks when possible.' 1070 | Enabled: false 1071 | 1072 | Style/Tab: 1073 | Description: 'No hard tabs.' 1074 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation' 1075 | Enabled: false 1076 | 1077 | Style/TrailingBlankLines: 1078 | Description: 'Checks trailing blank lines and final newline.' 1079 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#newline-eof' 1080 | Enabled: false 1081 | 1082 | Style/TrailingCommaInArguments: 1083 | Description: 'Checks for trailing comma in parameter lists.' 1084 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-params-comma' 1085 | Enabled: false 1086 | 1087 | Style/TrailingCommaInLiteral: 1088 | Description: 'Checks for trailing comma in literals.' 1089 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas' 1090 | Enabled: false 1091 | 1092 | Style/TrailingWhitespace: 1093 | Description: 'Avoid trailing whitespace.' 1094 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-whitespace' 1095 | Enabled: false 1096 | 1097 | Style/TrivialAccessors: 1098 | Description: 'Prefer attr_* methods to trivial readers/writers.' 1099 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr_family' 1100 | Enabled: false 1101 | 1102 | Style/UnlessElse: 1103 | Description: >- 1104 | Do not use unless with else. Rewrite these with the positive 1105 | case first. 1106 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-else-with-unless' 1107 | Enabled: false 1108 | 1109 | Style/UnneededCapitalW: 1110 | Description: 'Checks for %W when interpolation is not needed.' 1111 | Enabled: false 1112 | 1113 | Style/UnneededPercentQ: 1114 | Description: 'Checks for %q/%Q when single quotes or double quotes would do.' 1115 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q' 1116 | Enabled: false 1117 | 1118 | Style/TrailingUnderscoreVariable: 1119 | Description: >- 1120 | Checks for the usage of unneeded trailing underscores at the 1121 | end of parallel variable assignment. 1122 | Enabled: false 1123 | 1124 | Style/VariableInterpolation: 1125 | Description: >- 1126 | Don't interpolate global, instance and class variables 1127 | directly in strings. 1128 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#curlies-interpolate' 1129 | Enabled: false 1130 | 1131 | Style/VariableName: 1132 | Description: 'Use the configured style when naming variables.' 1133 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars' 1134 | Enabled: false 1135 | 1136 | Style/WhenThen: 1137 | Description: 'Use when x then ... for one-line cases.' 1138 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#one-line-cases' 1139 | Enabled: false 1140 | 1141 | Style/WhileUntilDo: 1142 | Description: 'Checks for redundant do after while or until.' 1143 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-multiline-while-do' 1144 | Enabled: false 1145 | 1146 | Style/WhileUntilModifier: 1147 | Description: >- 1148 | Favor modifier while/until usage when you have a 1149 | single-line body. 1150 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#while-as-a-modifier' 1151 | Enabled: false 1152 | 1153 | Style/WordArray: 1154 | Description: 'Use %w or %W for arrays of words.' 1155 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-w' 1156 | Enabled: false 1157 | -------------------------------------------------------------------------------- /.ruby-gemset: -------------------------------------------------------------------------------- 1 | messenger_platform 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.3.0 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Declare your gem's dependencies in message_quickly.gemspec. 4 | # Bundler will treat runtime dependencies like base dependencies, and 5 | # development dependencies will be added by default to the :development group. 6 | gemspec 7 | 8 | # Declare any dependencies that are still in development here instead of in 9 | # your gemspec. These might include edge Rails or gems from your path or 10 | # Git. Remember to move these dependencies to your gemspec before releasing 11 | # your gem to rubygems.org. 12 | 13 | # To use a debugger 14 | # gem 'byebug', group: [:development, :test] 15 | 16 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | message_quickly (1.2.0) 5 | faraday (~> 0) 6 | rails (~> 4.2) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | actionmailer (4.2.5.2) 12 | actionpack (= 4.2.5.2) 13 | actionview (= 4.2.5.2) 14 | activejob (= 4.2.5.2) 15 | mail (~> 2.5, >= 2.5.4) 16 | rails-dom-testing (~> 1.0, >= 1.0.5) 17 | actionpack (4.2.5.2) 18 | actionview (= 4.2.5.2) 19 | activesupport (= 4.2.5.2) 20 | rack (~> 1.6) 21 | rack-test (~> 0.6.2) 22 | rails-dom-testing (~> 1.0, >= 1.0.5) 23 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 24 | actionview (4.2.5.2) 25 | activesupport (= 4.2.5.2) 26 | builder (~> 3.1) 27 | erubis (~> 2.7.0) 28 | rails-dom-testing (~> 1.0, >= 1.0.5) 29 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 30 | activejob (4.2.5.2) 31 | activesupport (= 4.2.5.2) 32 | globalid (>= 0.3.0) 33 | activemodel (4.2.5.2) 34 | activesupport (= 4.2.5.2) 35 | builder (~> 3.1) 36 | activerecord (4.2.5.2) 37 | activemodel (= 4.2.5.2) 38 | activesupport (= 4.2.5.2) 39 | arel (~> 6.0) 40 | activesupport (4.2.5.2) 41 | i18n (~> 0.7) 42 | json (~> 1.7, >= 1.7.7) 43 | minitest (~> 5.1) 44 | thread_safe (~> 0.3, >= 0.3.4) 45 | tzinfo (~> 1.1) 46 | arel (6.0.3) 47 | builder (3.2.2) 48 | codeclimate-test-reporter (0.5.0) 49 | simplecov (>= 0.7.1, < 1.0.0) 50 | coderay (1.1.1) 51 | concurrent-ruby (1.0.2) 52 | diff-lcs (1.2.5) 53 | docile (1.1.5) 54 | dotenv (2.1.1) 55 | erubis (2.7.0) 56 | faraday (0.9.2) 57 | multipart-post (>= 1.2, < 3) 58 | globalid (0.3.6) 59 | activesupport (>= 4.1.0) 60 | i18n (0.7.0) 61 | json (1.8.3) 62 | loofah (2.0.3) 63 | nokogiri (>= 1.5.9) 64 | mail (2.6.4) 65 | mime-types (>= 1.16, < 4) 66 | method_source (0.8.2) 67 | mime-types (3.1) 68 | mime-types-data (~> 3.2015) 69 | mime-types-data (3.2016.0521) 70 | mini_portile2 (2.0.0) 71 | minitest (5.8.4) 72 | multipart-post (2.0.0) 73 | nokogiri (1.6.7.2) 74 | mini_portile2 (~> 2.0.0.rc2) 75 | pry (0.10.3) 76 | coderay (~> 1.1.0) 77 | method_source (~> 0.8.1) 78 | slop (~> 3.4) 79 | rack (1.6.4) 80 | rack-test (0.6.3) 81 | rack (>= 1.0) 82 | rails (4.2.5.2) 83 | actionmailer (= 4.2.5.2) 84 | actionpack (= 4.2.5.2) 85 | actionview (= 4.2.5.2) 86 | activejob (= 4.2.5.2) 87 | activemodel (= 4.2.5.2) 88 | activerecord (= 4.2.5.2) 89 | activesupport (= 4.2.5.2) 90 | bundler (>= 1.3.0, < 2.0) 91 | railties (= 4.2.5.2) 92 | sprockets-rails 93 | rails-deprecated_sanitizer (1.0.3) 94 | activesupport (>= 4.2.0.alpha) 95 | rails-dom-testing (1.0.7) 96 | activesupport (>= 4.2.0.beta, < 5.0) 97 | nokogiri (~> 1.6.0) 98 | rails-deprecated_sanitizer (>= 1.0.1) 99 | rails-html-sanitizer (1.0.3) 100 | loofah (~> 2.0) 101 | railties (4.2.5.2) 102 | actionpack (= 4.2.5.2) 103 | activesupport (= 4.2.5.2) 104 | rake (>= 0.8.7) 105 | thor (>= 0.18.1, < 2.0) 106 | rake (11.1.2) 107 | rspec-core (3.4.4) 108 | rspec-support (~> 3.4.0) 109 | rspec-expectations (3.4.0) 110 | diff-lcs (>= 1.2.0, < 2.0) 111 | rspec-support (~> 3.4.0) 112 | rspec-mocks (3.4.1) 113 | diff-lcs (>= 1.2.0, < 2.0) 114 | rspec-support (~> 3.4.0) 115 | rspec-rails (3.4.2) 116 | actionpack (>= 3.0, < 4.3) 117 | activesupport (>= 3.0, < 4.3) 118 | railties (>= 3.0, < 4.3) 119 | rspec-core (~> 3.4.0) 120 | rspec-expectations (~> 3.4.0) 121 | rspec-mocks (~> 3.4.0) 122 | rspec-support (~> 3.4.0) 123 | rspec-support (3.4.1) 124 | simplecov (0.11.2) 125 | docile (~> 1.1.0) 126 | json (~> 1.8) 127 | simplecov-html (~> 0.10.0) 128 | simplecov-html (0.10.0) 129 | slop (3.6.0) 130 | sprockets (3.6.2) 131 | concurrent-ruby (~> 1.0) 132 | rack (> 1, < 3) 133 | sprockets-rails (3.1.1) 134 | actionpack (>= 4.0) 135 | activesupport (>= 4.0) 136 | sprockets (>= 3.0.0) 137 | sqlite3 (1.3.11) 138 | thor (0.19.1) 139 | thread_safe (0.3.5) 140 | tzinfo (1.2.2) 141 | thread_safe (~> 0.1) 142 | 143 | PLATFORMS 144 | ruby 145 | 146 | DEPENDENCIES 147 | codeclimate-test-reporter (~> 0) 148 | dotenv (~> 2.1) 149 | message_quickly! 150 | pry (~> 0) 151 | rspec-rails (~> 3.4) 152 | sqlite3 (~> 1.3) 153 | 154 | BUNDLED WITH 155 | 1.11.2 156 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 Jaryl Sim 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Code Climate](https://codeclimate.com/github/tinkerbox/message_quickly/badges/gpa.svg)](https://codeclimate.com/github/tinkerbox/message_quickly) 2 | [![Test Coverage](https://codeclimate.com/github/tinkerbox/message_quickly/badges/coverage.svg)](https://codeclimate.com/github/tinkerbox/message_quickly/coverage) 3 | [![Circle CI](https://circleci.com/gh/tinkerbox/message_quickly.svg?style=svg)](https://circleci.com/gh/tinkerbox/message_quickly) 4 | 5 | # MessageQuickly 6 | 7 | By [Tinkerbox Studios](https://www.tinkerbox.com.sg). 8 | 9 | This gem is a lightweight solution to integrate [Facebook's Messenger Platform](https://developers.facebook.com/products/messenger/) into your rails app, allowing you to create bots to facilitate conversations with people on Facebook Messenger. It includes a mountable rails engine to handle [webhooks](https://developers.facebook.com/docs/messenger-platform/webhook-reference), and a simple client to talk to the [Send API](https://developers.facebook.com/docs/messenger-platform/send-api-reference). 10 | 11 | We also have an [accompanying demo app](https://github.com/tinkerbox/messenger_platform_demo). 12 | 13 | ## Installation 14 | 15 | Add this to your Gemfile, and then `bundle install`: 16 | 17 | gem 'message_quickly' 18 | 19 | Generate the page access token on the [developer portal](https://developers.facebook.com), which will allow you to start using the APIs: 20 | 21 | ![Generate Page Access Token](https://cloud.githubusercontent.com/assets/19878/14728362/682e3ba0-0866-11e6-9b68-fe9d2a220d56.png) 22 | 23 | With the token, make sure to run this: 24 | 25 | curl -ik -X POST "https://graph.facebook.com/v2.6/me/subscribed_apps?access_token=" 26 | 27 | Create the following environment variables: 28 | 29 | FACEBOOK_APP_ID= 30 | FACEBOOK_PAGE_ID= 31 | 32 | FACEBOOK_MESSENGER_VERIFICATION_TOKEN=my_voice_is_my_password_verify_me 33 | FACEBOOK_MESSENGER_PAGE_ACCESS_TOKEN= 34 | 35 | You will need to run your app, and make it accessible to the developer portal now, so run your server: 36 | 37 | rails server 38 | 39 | Use something like [Burrow](https://burrow.io/) to provide access to your localhost: 40 | 41 | ![Create Tunnel on Burrow](https://cloud.githubusercontent.com/assets/19878/14728394/da5f99e4-0866-11e6-8b9c-dc788af4f296.png) 42 | 43 | Go to your Facebook App page in the developer portal, and use the above verification token like so: 44 | 45 | ![New Page Subscription](https://cloud.githubusercontent.com/assets/19878/14728285/d1eaacaa-0865-11e6-98a6-dba8d62c1953.png) 46 | 47 | Facebook will then verify with the mounted engine, and you're all set. 48 | 49 | ### Notes 50 | 51 | Messenger Platform is designed to handle webhooks in background jobs on ActiveJob, so you should set up a queuing backend (e.g. Sidekiq) and configure ActiveJob to use it. 52 | 53 | Your app is required to be served over HTTPS. When working locally, I used the default WEBrick server as it supports HTTPS connections out of the box. 54 | 55 | ## Usage 56 | 57 | There are two parts to this gem: handling [webhooks](https://developers.facebook.com/docs/messenger-platform/webhook-reference) (which is what the rails engine is for), and calling the [Send API](https://developers.facebook.com/docs/messenger-platform/send-api-reference). 58 | 59 | Additionally, there are some helpers for working with [messenger plugins](https://developers.facebook.com/docs/messenger-platform/plugin-reference). 60 | 61 | ### Webhooks 62 | 63 | Webhooks allow the Facebook Messenger Platform to talk to your app. For example, you will receive a request on your webhook when a user authenticates or messages your Facebook page/app. 64 | 65 | Mount the engine in your `routes.rb` (`/webhook` is used in the examples): 66 | 67 | mount MessageQuickly::Engine, at: "/webhook" 68 | 69 | Generate the callback files: 70 | 71 | rails generate message_quickly:callbacks 72 | 73 | When you run `rails generate callbacks`, four files will be created for you. They look something like this: 74 | 75 | ```ruby 76 | class AuthenticationCallback < MessageQuickly::Callback 77 | 78 | def callback_name 79 | :messaging_optins 80 | end 81 | 82 | def run(event, json) 83 | # for e.g. 84 | # puts event.text 85 | end 86 | 87 | end 88 | ``` 89 | 90 | All you need to do is make sure your app is available publicly, and to fill up the `run` method. All the four callbacks defined in the platform are supported this way: 91 | 92 | Webhook Name | Callback Class | Description 93 | -------------|----------------|------------ 94 | messaging_optins | AuthenticationCallback | Subscribes to authentication callbacks via the Send-to-Messenger Plugin 95 | messages | MessageReceivedCallback | Subscribes to message-received callbacks 96 | message_deliveries | MessageDeliveredCallback | Subscribes to message-delivered callbacks 97 | message_reads | MessageReadCallback | Subscribes to message read callbacks 98 | messaging_postbacks | PostbackCallback | Subscribes to postback callbacks 99 | account_linking | AccountLinkingCallback | Subscribes to account linking callbacks 100 | 101 | ### Send API 102 | 103 | By default, the API client will be created for you, and is accessible at: 104 | 105 | MessageQuickly::Api::Base.client 106 | 107 | This makes use of the environment variables `FACEBOOK_MESSENGER_PAGE_ACCESS_TOKEN` and `FACEBOOK_MESSENGER_PAGE_ID`. 108 | 109 | If you would like to use different sets of credentials in the app, you can create your own clients like so: 110 | 111 | ```ruby 112 | send_api_client = MessageQuickly::Api::Client.new do |client| 113 | client.page_access_token = '' 114 | client.page_id = '' 115 | end 116 | 117 | MessageQuickly::Api::UserProfile.new(send_api_client).find('') 118 | ``` 119 | 120 | #### Looking up a user's profile 121 | 122 | Do note that we are using the default client, which is loaded automatically by default. 123 | 124 | ```ruby 125 | MessageQuickly::Api::UserProfile.find('') 126 | ``` 127 | 128 | #### Creating a recipient object 129 | 130 | You can create a recipient with either an `id`, or a `phone_number`. If both are provided, messages will be sent via the `id`. 131 | 132 | ```ruby 133 | recipient = MessageQuickly::Messaging::Recipient.new(id: '123') 134 | recipient = MessageQuickly::Messaging::Recipient.new(id: '123', phone_number: '+1(212)555-2368') 135 | ``` 136 | 137 | #### Sending a simple text message 138 | 139 | ```ruby 140 | delivery = MessageQuickly::Api::Messages.create(recipient) do |message| 141 | message.text = 'Hello' 142 | end 143 | ``` 144 | 145 | #### Sending image attachments 146 | 147 | ![Image attachments](https://cloud.githubusercontent.com/assets/19878/14765512/92b33ac6-0a17-11e6-8673-164b7802c102.png) 148 | 149 | You can either send an image attachment as a URL: 150 | 151 | ```ruby 152 | delivery = MessageQuickly::Api::Messages.create(recipient) do |message| 153 | message.build_attachment(:image) { |attachment| attachment.url = 'http://placehold.it/350x150' } 154 | end 155 | ``` 156 | 157 | Or you can send it as a file: 158 | 159 | ```ruby 160 | delivery = MessageQuickly::Api::Messages.create(recipient) do |message| 161 | message.build_attachment(:image) do |attachment| 162 | attachment.file = "spec/fixtures/12057251_909506139117248_2059695706_n.png" 163 | attachment.file_type = 'image/png' 164 | end 165 | end 166 | ``` 167 | 168 | #### Sending a generic template attachment 169 | 170 | ![Generic template attachment](https://cloud.githubusercontent.com/assets/19878/14765514/9bd0ec48-0a17-11e6-8988-bb3652213285.png) 171 | 172 | ```ruby 173 | delivery = MessageQuickly::Api::Messages.create(recipient) do |message| 174 | message.build_attachment(:generic_template) do |template| 175 | 176 | template.build_element do |element| 177 | 178 | element.title = "Classic White T-Shirt" 179 | element.image_url = 'http://petersapparel.parseapp.com/img/item100-thumb.png' 180 | element.subtitle = 'Soft white cotton t-shirt is back in style' 181 | 182 | element.build_button(:web_url) do |button| 183 | button.url = "https://petersapparel.parseapp.com/view_item?item_id=100" 184 | button.title = "View Item" 185 | end 186 | 187 | element.build_button(:web_url) do |button| 188 | button.url = "https://petersapparel.parseapp.com/buy_item?item_id=100" 189 | button.title = "Buy Item" 190 | end 191 | 192 | element.build_button(:postback) do |button| 193 | button.payload = "USER_DEFINED_PAYLOAD_FOR_ITEM100" 194 | button.title = "Bookmark Item" 195 | end 196 | 197 | end 198 | 199 | template.build_element do |element| 200 | 201 | element.title = "Classic Grey T-Shirt" 202 | element.image_url = 'http://petersapparel.parseapp.com/img/item101-thumb.png' 203 | element.subtitle = 'Soft gray cotton t-shirt is back in style' 204 | 205 | element.build_button(:web_url) do |button| 206 | button.url = "https://petersapparel.parseapp.com/view_item?item_id=101" 207 | button.title = "View Item" 208 | end 209 | 210 | element.build_button(:web_url) do |button| 211 | button.url = "https://petersapparel.parseapp.com/buy_item?item_id=101" 212 | button.title = "Buy Item" 213 | end 214 | 215 | element.build_button(:postback) do |button| 216 | button.payload = "USER_DEFINED_PAYLOAD_FOR_ITEM101" 217 | button.title = "Bookmark Item" 218 | end 219 | 220 | end 221 | 222 | end 223 | end 224 | ``` 225 | 226 | #### Sending a button template attachment 227 | 228 | ```ruby 229 | delivery = MessageQuickly::Api::Messages.create(recipient) do |message| 230 | message.build_attachment(:button_template) do |template| 231 | 232 | template.text = 'How are you doing today?' 233 | 234 | template.build_button(:web_url) do |button| 235 | button.url = 'https://petersapparel.parseapp.com' 236 | button.title = 'Show Website' 237 | end 238 | 239 | template.build_button(:postback) do |button| 240 | button.payload = 'USER_DEFINED_PAYLOAD' 241 | button.title = 'Start Chatting' 242 | end 243 | 244 | end 245 | end 246 | ``` 247 | 248 | #### Sending an account linking button 249 | 250 | ```ruby 251 | delivery = MessageQuickly::Api::Messages.create(recipient) do |message| 252 | 253 | message.build_attachment(:button_template) do |template| 254 | 255 | template.text = 'Please log in' 256 | 257 | template.build_button(:account_link) do |button| 258 | button.url = 'https://www.example.com/oauth/authorize' 259 | end 260 | 261 | end 262 | end 263 | ``` 264 | 265 | #### Sending a receipt template attachment 266 | 267 | ![Receipt template attachment](https://cloud.githubusercontent.com/assets/19878/14765517/ab69af46-0a17-11e6-8700-17ae8b7d1e53.png) 268 | 269 | ```ruby 270 | delivery = MessageQuickly::Api::Messages.create(recipient) do |message| 271 | message.build_attachment(:receipt_template) do |template| 272 | 273 | template.recipient_name = 'Stephane Crozatier' 274 | template.order_number = (0...50).map { ('a'..'z').to_a[rand(26)] }.join 275 | template.currency = 'USD' 276 | template.payment_method = 'Visa 2345' 277 | template.order_url = 'http://petersapparel.parseapp.com/order?order_id=123456' 278 | template.timestamp = '1428444852' 279 | 280 | template.build_element do |element| 281 | element.title = 'Classic White T-Shirt' 282 | element.subtitle = '100% Soft and Luxurious Cotton' 283 | element.quantity = 2 284 | element.price = 50 285 | element.currency = 'USD' 286 | element.image_url = 'http://petersapparel.parseapp.com/img/whiteshirt.png' 287 | end 288 | 289 | template.build_element do |element| 290 | element.title = 'Classic Gray T-Shirt' 291 | element.subtitle = '100% Soft and Luxurious Cotton' 292 | element.quantity = 1 293 | element.price = 25 294 | element.currency = 'USD' 295 | element.image_url = 'http://petersapparel.parseapp.com/img/grayshirt.png' 296 | end 297 | 298 | template.build_address do |address| 299 | address.street_1 = "1 Hacker Way" 300 | address.street_2 = "" 301 | address.city = "Menlo Park" 302 | address.postal_code = "94025" 303 | address.state = "CA" 304 | address.country = "US" 305 | end 306 | 307 | template.build_summary do |summary| 308 | summary.subtotal = 75.00 309 | summary.shipping_cost = 4.95 310 | summary.total_tax = 6.19 311 | summary.total_cost = 56.14 312 | end 313 | 314 | template.build_adjustment do |adjustment| 315 | adjustment.name = "New Customer Discount" 316 | adjustment.amount = 20 317 | end 318 | 319 | template.build_adjustment do |adjustment| 320 | adjustment.name = "$10 Off Coupon" 321 | adjustment.amount = 10 322 | end 323 | 324 | end 325 | end 326 | ``` 327 | 328 | #### Sending quick replies 329 | 330 | ```ruby 331 | delivery = MessageQuickly::Api::Messages.create(recipient) do |message| 332 | 333 | message.text = "Pick a color:" 334 | 335 | message.build_quick_reply do |quick_reply| 336 | quick_reply.title = 'Green' 337 | quick_reply.payload = 'DEVELOPER_DEFINED_PAYLOAD_FOR_PICKING_GREEN' 338 | end 339 | 340 | message.build_quick_reply do |quick_reply| 341 | quick_reply.title = 'Red' 342 | quick_reply.payload = 'DEVELOPER_DEFINED_PAYLOAD_FOR_PICKING_RED' 343 | end 344 | 345 | end 346 | ``` 347 | 348 | ### Plugins 349 | 350 | This is optional, and only necessary if you want to add the 'Send to Messenger' or 'Message Us' [buttons](https://developers.facebook.com/docs/messenger-platform/plugin-reference) to your app, shown here: 351 | 352 | ![Messenger plugins](https://cloud.githubusercontent.com/assets/19878/14765515/a16c57a0-0a17-11e6-8fec-b9798610b8bf.png) 353 | 354 | Firstly, add the javascript require to your manifest file: 355 | 356 | //= require message_quickly 357 | 358 | Then, in your view templates (presumably slim), add: 359 | 360 | = send_to_messenger 361 | = message_us 362 | 363 | You can also customize them as such: 364 | 365 | = send_to_messenger(size: 'large') 366 | = message_us(size: 'xlarge', color: 'white') 367 | 368 | For size, supported values include `standard`, `large` and `xlarge`, while color supports `blue` and `white` only. 369 | 370 | ## Development & Contributing 371 | 372 | Set up your `.env` file like so: 373 | 374 | FACEBOOK_MESSENGER_VERIFICATION_TOKEN=my_voice_is_my_password_verify_me 375 | FACEBOOK_MESSENGER_PAGE_ACCESS_TOKEN= 376 | FACEBOOK_MESSENGER_PAGE_ID= 377 | FACEBOOK_MESSENGER_USER_ID= 378 | FACEBOOK_MESSENGER_USER_FIRST_NAME= 379 | FACEBOOK_MESSENGER_USER_LAST_NAME= 380 | FACEBOOK_APP_ID= 381 | 382 | You will need your own Facebook profile id if you are to run the specs. Run them now with: 383 | 384 | rake 385 | 386 | Things on the roadmap include: 387 | 388 | * simplify callback names 389 | * catch FacebookApiException errors, can't generate one in the wild yet 390 | * support for customer matching (US-based page required), done but not tested yet 391 | * support for more complex welcome messages 392 | * use webmock to disallow remote requests in specs 393 | 394 | ## Credits 395 | 396 | This gem was created by [Jaryl Sim](http://github.com/jaryl). See [MIT-LICENSE](MIT-LICENSE) for details. 397 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | begin 2 | require 'bundler/setup' 3 | rescue LoadError 4 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks' 5 | end 6 | 7 | require 'rdoc/task' 8 | 9 | RDoc::Task.new(:rdoc) do |rdoc| 10 | rdoc.rdoc_dir = 'rdoc' 11 | rdoc.title = 'MessageQuickly' 12 | rdoc.options << '--line-numbers' 13 | rdoc.rdoc_files.include('README.rdoc') 14 | rdoc.rdoc_files.include('lib/**/*.rb') 15 | end 16 | 17 | APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__) 18 | load 'rails/tasks/engine.rake' 19 | 20 | load 'rails/tasks/statistics.rake' 21 | 22 | Bundler::GemHelper.install_tasks 23 | 24 | require 'rspec/core' 25 | require 'rspec/core/rake_task' 26 | 27 | RSpec::Core::RakeTask.new(:spec) 28 | 29 | task :default => :spec 30 | -------------------------------------------------------------------------------- /app/assets/images/message_quickly/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerbox/message_quickly/886227b11459872e59704c61ade55ba2ad4a32eb/app/assets/images/message_quickly/.keep -------------------------------------------------------------------------------- /app/assets/javascripts/message_quickly/index.js.erb: -------------------------------------------------------------------------------- 1 | window.fbAsyncInit = function() { 2 | FB.init({ 3 | appId : '<%= ENV['FACEBOOK_APP_ID'] %>', 4 | xfbml : true, 5 | version : 'v2.6' 6 | }); 7 | }; 8 | 9 | (function(d, s, id){ 10 | var js, fjs = d.getElementsByTagName(s)[0]; 11 | if (d.getElementById(id)) {return;} 12 | js = d.createElement(s); js.id = id; 13 | js.src = "//connect.facebook.net/en_US/sdk.js"; 14 | fjs.parentNode.insertBefore(js, fjs); 15 | }(document, 'script', 'facebook-jssdk')); 16 | -------------------------------------------------------------------------------- /app/assets/stylesheets/message_quickly/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any styles 10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new 11 | * file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /app/controllers/message_quickly/application_controller.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | class ApplicationController < ActionController::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/message_quickly/webhooks_controller.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | class WebhooksController < ApplicationController 3 | 4 | skip_before_action :verify_authenticity_token 5 | 6 | def verify 7 | if params['hub.verify_token'] == ENV['FACEBOOK_MESSENGER_VERIFICATION_TOKEN'] 8 | render plain: params['hub.challenge'], status: 200 9 | else 10 | render plain: 'Wrong validation token', status: 500 11 | end 12 | end 13 | 14 | def callback 15 | json = JSON.parse(request.body.read) 16 | ProcessMessengerCallbackJob.perform_later(json) 17 | render nothing: true, status: 200 18 | rescue JSON::ParserError => e 19 | render plain: 'Error processing callback', status: 500 20 | end 21 | 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/helpers/message_quickly/application_helper.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module ApplicationHelper 3 | 4 | def send_to_messenger(params = {}, client = nil) 5 | params[:class] = 'fb-send-to-messenger' 6 | params[:messenger_app_id] = client.try(:app_id) || ENV['FACEBOOK_APP_ID'] 7 | params[:page_id] = client.try(:page_id) || ENV['FACEBOOK_PAGE_ID'] 8 | params[:data] ||= {} 9 | params[:data][:ref] ||= 'PASS_THROUGH_PARAM' 10 | params[:color] ||= 'blue' 11 | params[:size] ||= 'standard' 12 | content_tag(:div, '', params) 13 | end 14 | 15 | def message_us(params = {}, client = nil) 16 | params[:class] = 'fb-messengermessageus' 17 | params[:messenger_app_id] = client.try(:app_id) || ENV['FACEBOOK_APP_ID'] 18 | params[:page_id] = client.try(:page_id) || ENV['FACEBOOK_PAGE_ID'] 19 | params[:color] ||= 'blue' 20 | params[:size] ||= 'standard' 21 | content_tag(:div, '', params) 22 | end 23 | 24 | # def close_window(image_url = '', display_text = '') 25 | # redirect_to "https://www.messenger.com/closeWindow/?image_url=#{image_url}&display_text=#{display_text}." 26 | # end 27 | 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/views/layouts/message_quickly/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MessageQuickly 5 | <%= stylesheet_link_tag "message_quickly/application", media: "all" %> 6 | <%= javascript_include_tag "message_quickly/application" %> 7 | <%= csrf_meta_tags %> 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application. 3 | 4 | ENGINE_ROOT = File.expand_path('../..', __FILE__) 5 | ENGINE_PATH = File.expand_path('../../lib/message_quickly/engine', __FILE__) 6 | 7 | # Set up gems listed in the Gemfile. 8 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 9 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 10 | 11 | require 'rails/all' 12 | require 'rails/engine/commands' 13 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | MessageQuickly::Engine.routes.draw do 2 | get '/' => 'webhooks#verify' 3 | post '/' => 'webhooks#callback' 4 | end 5 | -------------------------------------------------------------------------------- /lib/generators/message_quickly/callbacks/USAGE: -------------------------------------------------------------------------------- 1 | Description: 2 | Explain the generator 3 | 4 | Example: 5 | rails generate callbacks 6 | 7 | This will create: 8 | config/initializers/webhooks.rb 9 | app/webhooks/authentication_callback.rb 10 | app/webhooks/message_delivered_callback.rb 11 | app/webhooks/message_received_callback.rb 12 | app/webhooks/postback_callback.rb 13 | app/webhooks/change_update_callback.rb 14 | app/jobs/process_messenger_callback_job.rb 15 | app/jobs/send_messenger_delivery_job.rb 16 | -------------------------------------------------------------------------------- /lib/generators/message_quickly/callbacks/callbacks_generator.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Generators 3 | class CallbacksGenerator < Rails::Generators::Base 4 | 5 | source_root File.expand_path('../templates', __FILE__) 6 | 7 | def copy_webhooks 8 | 9 | copy_file "webhooks.rb", "config/initializers/webhooks.rb" 10 | 11 | copy_file "authentication_callback.rb", "app/webhooks/authentication_callback.rb" 12 | copy_file "message_delivered_callback.rb", "app/webhooks/message_delivered_callback.rb" 13 | copy_file "message_received_callback.rb", "app/webhooks/message_received_callback.rb" 14 | copy_file "postback_callback.rb", "app/webhooks/postback_callback.rb" 15 | 16 | copy_file "change_update_callback.rb", "app/webhooks/change_update_callback.rb" 17 | 18 | copy_file "process_messenger_callback_job.rb", "app/jobs/process_messenger_callback_job.rb" 19 | copy_file "send_messenger_delivery_job.rb", "app/jobs/send_messenger_delivery_job.rb" 20 | 21 | end 22 | 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/generators/message_quickly/callbacks/templates/account_linking_callback.rb: -------------------------------------------------------------------------------- 1 | class AccountLinkingCallback < MessageQuickly::Callback 2 | 3 | def self.webhook_name 4 | :account_linking 5 | end 6 | 7 | def initialize(event, json) 8 | super 9 | end 10 | 11 | def run 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /lib/generators/message_quickly/callbacks/templates/authentication_callback.rb: -------------------------------------------------------------------------------- 1 | class AuthenticationCallback < MessageQuickly::Callback 2 | 3 | def self.webhook_name 4 | :messaging_optins 5 | end 6 | 7 | def initialize(event, json) 8 | super 9 | end 10 | 11 | def run 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /lib/generators/message_quickly/callbacks/templates/change_update_callback.rb: -------------------------------------------------------------------------------- 1 | class ChangeUpdateCallback < MessageQuickly::Callback 2 | 3 | def self.webhook_name 4 | :changes 5 | end 6 | 7 | def initialize(event, json) 8 | super 9 | end 10 | 11 | def run 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /lib/generators/message_quickly/callbacks/templates/message_delivered_callback.rb: -------------------------------------------------------------------------------- 1 | class MessageDeliveredCallback < MessageQuickly::Callback 2 | 3 | def self.webhook_name 4 | :message_deliveries 5 | end 6 | 7 | def initialize(event, json) 8 | super 9 | end 10 | 11 | def run 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /lib/generators/message_quickly/callbacks/templates/message_read_callback.rb: -------------------------------------------------------------------------------- 1 | class MessageReadCallback < MessageQuickly::Callback 2 | 3 | def self.webhook_name 4 | :message_reads 5 | end 6 | 7 | def initialize(event, json) 8 | super 9 | end 10 | 11 | def run 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /lib/generators/message_quickly/callbacks/templates/message_received_callback.rb: -------------------------------------------------------------------------------- 1 | class MessageReceivedCallback < MessageQuickly::Callback 2 | 3 | def self.webhook_name 4 | :messages 5 | end 6 | 7 | def initialize(event, json) 8 | super 9 | end 10 | 11 | def run 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /lib/generators/message_quickly/callbacks/templates/postback_callback.rb: -------------------------------------------------------------------------------- 1 | class PostbackCallback < MessageQuickly::Callback 2 | 3 | def self.webhook_name 4 | :messaging_postbacks 5 | end 6 | 7 | def initialize(event, json) 8 | super 9 | end 10 | 11 | def run 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /lib/generators/message_quickly/callbacks/templates/process_messenger_callback_job.rb: -------------------------------------------------------------------------------- 1 | class ProcessMessengerCallbackJob < ActiveJob::Base 2 | 3 | queue_as :messaging 4 | 5 | def perform(json) 6 | MessageQuickly::CallbackParser.new(json.deep_dup).parse do |event| 7 | callback_handler = MessageQuickly::CallbackRegistry.handler_for(event.webhook_name) 8 | if callback_handler 9 | callback = callback_handler.new(event, json.deep_dup) 10 | callback.run 11 | end 12 | end 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /lib/generators/message_quickly/callbacks/templates/send_messenger_delivery_job.rb: -------------------------------------------------------------------------------- 1 | class SendMessengerDeliveryJob < ActiveJob::Base 2 | 3 | queue_as :messaging 4 | 5 | def perform(hash) 6 | MessageQuickly::Api::Client.create_from_hash(hash) 7 | end 8 | 9 | # To use this: 10 | 11 | # recipient = MessageQuickly::Messaging::Recipient.new(id: '') 12 | # delivery = MessageQuickly::Messaging::Delivery.new(recipient: recipient) do |delivery| 13 | # delivery.build_message do |message| 14 | # message.text = 'Hello' 15 | # end 16 | # end 17 | # 18 | # SendMessengerDeliveryJob.perform_later(delivery.to_hash) 19 | 20 | end 21 | -------------------------------------------------------------------------------- /lib/generators/message_quickly/callbacks/templates/webhooks.rb: -------------------------------------------------------------------------------- 1 | Rails.application.eager_load! 2 | 3 | MessageQuickly::Callback.subclasses.each do |callback| 4 | MessageQuickly::CallbackRegistry.register(callback) 5 | end 6 | -------------------------------------------------------------------------------- /lib/message_quickly.rb: -------------------------------------------------------------------------------- 1 | require "message_quickly/version" 2 | 3 | require "message_quickly/api/client" 4 | require "message_quickly/api/base" 5 | require "message_quickly/api/messages" 6 | require "message_quickly/api/facebook_api_exception" 7 | require "message_quickly/api/oauth_exception" 8 | require "message_quickly/api/graph_method_exception" 9 | require "message_quickly/api/no_matching_user_exception" 10 | require "message_quickly/api/not_permitted_exception" 11 | require "message_quickly/api/send_message_exception" 12 | require "message_quickly/api/thread_settings" 13 | require "message_quickly/api/user_profile" 14 | require "message_quickly/api/account_link" 15 | 16 | require "message_quickly/engine" 17 | 18 | require "message_quickly/callback" 19 | require "message_quickly/callback_parser" 20 | require "message_quickly/callback_registry" 21 | 22 | module MessageQuickly 23 | end 24 | -------------------------------------------------------------------------------- /lib/message_quickly/api/account_link.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Api 3 | class AccountLink < Base 4 | 5 | def self.page_scoped_id(account_linking_token) 6 | # curl -X GET "https://graph.facebook.com/v2.6/me?access_token=PAGE_ACCESS_TOKEN \ 7 | # &fields=recipient \ 8 | # &account_linking_token=ACCOUNT_LINKING_TOKEN" 9 | 10 | # { 11 | # "id": "PAGE_ID", 12 | # "recipient": "PSID" 13 | # } 14 | AccountLink.new.page_scoped_id(account_linking_token) 15 | end 16 | 17 | def page_scoped_id(account_linking_token) 18 | json = @client.get("me", { fields: 'recipient', account_linking_token: account_linking_token }) 19 | json['recipient'] 20 | end 21 | 22 | def self.unlink_account(page_scoped_id) 23 | # curl -X POST -H "Content-Type: application/json" -d '{ 24 | # "psid":"PSID" 25 | # }' "https://graph.facebook.com/v2.6/me/unlink_accounts?access_token=PAGE_ACCESS_TOKEN" 26 | 27 | # { 28 | # "result": "unlink account success" 29 | # } 30 | AccountLink.new.unlink_account(page_scoped_id) 31 | end 32 | 33 | def unlink_account(page_scoped_id) 34 | json = @client.post("me/unlink_accounts", { psid: page_scoped_id }) 35 | json['result'] == "unlink account success" 36 | end 37 | 38 | end 39 | end 40 | end 41 | 42 | -------------------------------------------------------------------------------- /lib/message_quickly/api/base.rb: -------------------------------------------------------------------------------- 1 | require "faraday" 2 | 3 | module MessageQuickly 4 | module Api 5 | class Base 6 | 7 | def self.client 8 | @@client ||= Client.new do |client| 9 | client.page_access_token = ENV['FACEBOOK_MESSENGER_PAGE_ACCESS_TOKEN'] 10 | client.page_id = ENV['FACEBOOK_MESSENGER_PAGE_ID'] 11 | end 12 | end 13 | 14 | def initialize(override_client = nil) 15 | @client = override_client || self.class.client 16 | end 17 | 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/message_quickly/api/client.rb: -------------------------------------------------------------------------------- 1 | require "faraday" 2 | 3 | module MessageQuickly 4 | module Api 5 | class Client 6 | 7 | attr_accessor :page_access_token, :page_id, :app_id 8 | 9 | def initialize(options = {}) 10 | options.each { |key, value| instance_variable_set("@#{key}", value) } 11 | yield self if block_given? 12 | end 13 | 14 | def get(request_string, params = {}) 15 | params[:access_token] = page_access_token 16 | response = connection.get(request_string, params) 17 | parse_json(response) 18 | end 19 | 20 | def post(request_string, params = {}) 21 | params[:access_token] = page_access_token 22 | response = connection.post(request_string) do |request| 23 | if params[:filedata].present? 24 | request.headers['Content-Type'] = 'multipart/form-data' 25 | request.body = params 26 | else 27 | request.headers['Content-Type'] = 'application/json' 28 | request.body = JSON.generate(params) 29 | end 30 | end 31 | parse_json(response) 32 | end 33 | 34 | def delete(request_string, params = {}) 35 | params[:access_token] = page_access_token 36 | response = connection.delete(request_string) do |request| 37 | request.headers['Content-Type'] = 'application/json' 38 | request.body = JSON.generate(params) 39 | end 40 | parse_json(response) 41 | end 42 | 43 | private 44 | 45 | def parse_json(response) 46 | json = JSON.parse(response.body) 47 | if json['error'] 48 | case json['error']['type'] 49 | when 'OAuthException' 50 | raise OauthException.new(json['error']) 51 | when 'GraphMethodException' 52 | raise GraphMethodException.new(json['error']) 53 | else 54 | raise FacebookApiException.new(json['error']) 55 | end 56 | end 57 | json 58 | end 59 | 60 | def connection(params = {}) 61 | @connection ||= Faraday.new(:url => "https://graph.facebook.com/#{MessageQuickly::FB_MESSENGER_VERSION}") do |faraday| 62 | faraday.request :multipart # if params[:multipart] 63 | faraday.request :url_encoded # form-encode POST params 64 | faraday.response :logger unless Rails.env.test? # log requests to STDOUT 65 | faraday.adapter Faraday.default_adapter # make requests with Net::HTTP 66 | end 67 | end 68 | 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /lib/message_quickly/api/facebook_api_exception.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Api 3 | class FacebookApiException < StandardError 4 | 5 | attr_reader :code, :error_data, :fbtrace_id 6 | 7 | def initialize(params = {}) 8 | @message = params['message'] 9 | @code = params['code'] 10 | @error_data = params['error_data'] 11 | @fbtrace_id = params['fbtrace_id'] 12 | super(message) 13 | end 14 | 15 | def message 16 | "#{@message}: #{@error_data}" 17 | end 18 | 19 | end 20 | end 21 | end 22 | 23 | # { 24 | # "error":{ 25 | # "message":"Invalid parameter", 26 | # "type":"FacebookApiException", 27 | # "code":100, 28 | # "error_data":"No matching user found.", 29 | # "fbtrace_id":"D2kxCybrKVw" 30 | # } 31 | # } 32 | -------------------------------------------------------------------------------- /lib/message_quickly/api/graph_method_exception.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Api 3 | class GraphMethodException < StandardError 4 | 5 | attr_reader :code, :fbtrace_id 6 | 7 | def initialize(params = {}) 8 | @message = params['message'] 9 | @code = params['code'] 10 | @fbtrace_id = params['fbtrace_id'] 11 | super(message) 12 | end 13 | 14 | def message 15 | @message 16 | end 17 | 18 | end 19 | end 20 | end -------------------------------------------------------------------------------- /lib/message_quickly/api/messages.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Api 3 | class Messages < Base 4 | 5 | def self.create(recipient = nil, delivery = nil, &block) 6 | # curl -X POST -H "Content-Type: application/json" -d '{ 7 | # "recipient":{ 8 | # "id":"USER_ID" 9 | # }, 10 | # "message":{ 11 | # "text":"hello, world!" 12 | # } 13 | # }' "https://graph.facebook.com/v2.6/me/messages?access_token=PAGE_ACCESS_TOKEN" 14 | 15 | # { 16 | # "recipient_id": "1008372609250235", 17 | # "message_id": "mid.1456970487936:c34767dfe57ee6e339" 18 | # } 19 | Messages.new.create(recipient, delivery, &block) 20 | end 21 | 22 | def create(recipient = nil, delivery = nil, &block) 23 | delivery ||= MessageQuickly::Messaging::Delivery.new(recipient: recipient) 24 | block.call(delivery.message) if block 25 | delivery.id = perform_call(delivery.to_hash) 26 | delivery 27 | end 28 | 29 | def self.create_from_hash(hash) 30 | Messages.new.create_from_hash(hash) 31 | end 32 | 33 | def create_from_hash(hash) 34 | perform_call(hash) 35 | end 36 | 37 | private 38 | 39 | def perform_call(hash) 40 | json = @client.post("me/messages", hash) 41 | json['message_id'] 42 | end 43 | 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/message_quickly/api/no_matching_user_exception.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Api 3 | class NoMatchingUserException < FacebookApiException 4 | end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/message_quickly/api/not_permitted_exception.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Api 3 | class NotPermittedException < FacebookApiException 4 | end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/message_quickly/api/oauth_exception.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Api 3 | class OauthException < StandardError 4 | 5 | attr_reader :code, :fbtrace_id 6 | 7 | def initialize(params = {}) 8 | @message = params['message'] 9 | @code = params['code'] 10 | @fbtrace_id = params['fbtrace_id'] 11 | super(message) 12 | end 13 | 14 | def message 15 | @message 16 | end 17 | 18 | end 19 | end 20 | end -------------------------------------------------------------------------------- /lib/message_quickly/api/send_message_exception.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Api 3 | class SendMessageException < FacebookApiException 4 | end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/message_quickly/api/thread_settings.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Api 3 | class ThreadSettings < Base 4 | 5 | def self.greeting(text) 6 | # curl -X POST -H "Content-Type: application/json" -d '{ 7 | # "setting_type":"greeting", 8 | # "greeting":{ 9 | # "text":"Welcome to My Company!" 10 | # } 11 | # }' "https://graph.facebook.com/v2.6//thread_settings?access_token=" 12 | ThreadSettings.new.greeting(text) 13 | end 14 | 15 | def self.get_started_button(payload) 16 | # curl -X POST -H "Content-Type: application/json" -d '{ 17 | # "setting_type":"call_to_actions", 18 | # "thread_state":"new_thread", 19 | # "call_to_actions":[ 20 | # { 21 | # "payload":"USER_DEFINED_PAYLOAD" 22 | # } 23 | # ] 24 | # }' "https://graph.facebook.com/v2.6/me/thread_settings?access_token=PAGE_ACCESS_TOKEN" 25 | ThreadSettings.new.get_started_button(payload) 26 | end 27 | 28 | def self.remove_get_started_button 29 | # curl -X DELETE -H "Content-Type: application/json" -d '{ 30 | # "setting_type":"call_to_actions", 31 | # "thread_state":"new_thread" 32 | # }' "https://graph.facebook.com/v2.6/me/thread_settings?access_token=PAGE_ACCESS_TOKEN" 33 | ThreadSettings.new.remove_get_started_button 34 | end 35 | 36 | def self.persistent_menu(payloads = []) 37 | # curl -X POST -H "Content-Type: application/json" -d '{ 38 | # "setting_type" : "call_to_actions", 39 | # "thread_state" : "existing_thread", 40 | # "call_to_actions":[ 41 | # { 42 | # "type":"postback", 43 | # "title":"Help", 44 | # "payload":"DEVELOPER_DEFINED_PAYLOAD_FOR_HELP" 45 | # }, 46 | # { 47 | # "type":"postback", 48 | # "title":"Start a New Order", 49 | # "payload":"DEVELOPER_DEFINED_PAYLOAD_FOR_START_ORDER" 50 | # }, 51 | # { 52 | # "type":"web_url", 53 | # "title":"View Website", 54 | # "url":"http://petersapparel.parseapp.com/" 55 | # } 56 | # ] 57 | # }' "https://graph.facebook.com/v2.6/me/thread_settings?access_token=PAGE_ACCESS_TOKEN" 58 | ThreadSettings.new.persistent_menu(payloads) 59 | end 60 | 61 | def self.remove_persistent_menu 62 | ThreadSettings.new.remove_persistent_menu 63 | end 64 | 65 | def greeting(text) 66 | json = @client.post(request_string, { setting_type: 'greeting', greeting: { text: text } }) 67 | json['result'] == "Successfully updated greeting" 68 | end 69 | 70 | def get_started_button(payload) 71 | json = @client.post(request_string, { setting_type: 'call_to_actions', thread_state: 'new_thread', call_to_actions: [payload: payload] }) 72 | json['result'] == "Successfully added new_thread's CTAs" 73 | end 74 | 75 | def remove_get_started_button 76 | json = @client.delete(request_string, { setting_type: 'call_to_actions', thread_state: 'new_thread' }) 77 | json['result'] == "Successfully deleted all new_thread's CTAs" 78 | end 79 | 80 | def persistent_menu(payloads = []) 81 | json = @client.post(request_string, { setting_type: 'call_to_actions', thread_state: 'existing_thread', call_to_actions: payloads }) 82 | json['result'] == "Successfully added structured menu CTAs" 83 | end 84 | 85 | def remove_persistent_menu(payloads = []) 86 | json = @client.delete(request_string, { setting_type: 'call_to_actions', thread_state: 'existing_thread' }) 87 | json['result'] == "Successfully deleted structured menu CTAs" 88 | end 89 | 90 | private 91 | 92 | def request_string 93 | "#{ENV['FACEBOOK_MESSENGER_PAGE_ID']}/thread_settings" 94 | end 95 | 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /lib/message_quickly/api/user_profile.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Api 3 | class UserProfile < Base 4 | 5 | def self.find(id) 6 | # curl -X GET "https://graph.facebook.com/v2.6/?fields=first_name,last_name,profile_pic&access_token=" 7 | # { 8 | # "first_name": "Peter", 9 | # "last_name": "Chang", 10 | # "profile_pic": "https://fbcdn-profile-a.akamaihd.net/hprofile...70ec9c19b18" 11 | # } 12 | UserProfile.new.find(id) 13 | end 14 | 15 | def find(id) 16 | json = @client.get(id, { fields: 'first_name,last_name,profile_pic,locale,timezone,gender' }) 17 | Messaging::User.new(json) 18 | end 19 | 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/message_quickly/callback.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | class Callback 3 | 4 | attr_reader :event, :json 5 | 6 | def initialize(event, json) 7 | @event ||= event 8 | @json ||= json 9 | self 10 | end 11 | 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/message_quickly/callback_parser.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | require "message_quickly/messaging/base" 4 | require "message_quickly/messaging/attachment" 5 | require "message_quickly/messaging/image_attachment" 6 | require "message_quickly/messaging/video_attachment" 7 | require "message_quickly/messaging/audio_attachment" 8 | require "message_quickly/messaging/template_attachment" 9 | require "message_quickly/messaging/element" 10 | require "message_quickly/messaging/button" 11 | require "message_quickly/messaging/web_url_button" 12 | require "message_quickly/messaging/postback_button" 13 | require "message_quickly/messaging/account_link_button" 14 | require "message_quickly/messaging/button_template_attachment" 15 | require "message_quickly/messaging/generic_template_attachment" 16 | require "message_quickly/messaging/quick_reply" 17 | 18 | require "message_quickly/messaging/receipt/address" 19 | require "message_quickly/messaging/receipt/adjustment" 20 | require "message_quickly/messaging/receipt/element" 21 | require "message_quickly/messaging/receipt/summary" 22 | require "message_quickly/messaging/receipt_template_attachment" 23 | 24 | require "message_quickly/messaging/delivery" 25 | require "message_quickly/messaging/message" 26 | require "message_quickly/messaging/entry" 27 | require "message_quickly/messaging/user" 28 | require "message_quickly/messaging/recipient" 29 | require "message_quickly/messaging/sender" 30 | 31 | require "message_quickly/messaging/event" 32 | require "message_quickly/messaging/optin_event" 33 | require "message_quickly/messaging/delivery_event" 34 | require "message_quickly/messaging/message_event" 35 | require "message_quickly/messaging/postback_event" 36 | require "message_quickly/messaging/read_event" 37 | require "message_quickly/messaging/account_link_event" 38 | require "message_quickly/change_update_event" 39 | 40 | module MessageQuickly 41 | class CallbackParser 42 | 43 | def initialize(json) 44 | @json = json 45 | end 46 | 47 | MESSAGING_WEBHOOK_LOOKUP = { 48 | optin: MessageQuickly::Messaging::OptinEvent, 49 | postback: MessageQuickly::Messaging::PostbackEvent, 50 | delivery: MessageQuickly::Messaging::DeliveryEvent, 51 | account_linking: MessageQuickly::Messaging::AccountLinkEvent, 52 | message: MessageQuickly::Messaging::MessageEvent, 53 | read: MessageQuickly::Messaging::ReadEvent 54 | } 55 | 56 | def parse 57 | events = [] 58 | process_entry_json(@json['entry']) do |params| 59 | if params[:changes].present? 60 | events << MessageQuickly::ChangeUpdateEvent.new(params) 61 | next 62 | end 63 | MESSAGING_WEBHOOK_LOOKUP.keys.each do |key| 64 | if params[:messaging].has_key?(key) 65 | events << MESSAGING_WEBHOOK_LOOKUP[key].new(params[:messaging]) 66 | break 67 | end 68 | end 69 | end 70 | events.each { |event| yield event if block_given? } 71 | events 72 | end 73 | 74 | private 75 | 76 | def process_entry_json(json) 77 | json.each do |entry_json| 78 | entry = Messaging::Entry.new(entry_json) 79 | entry_json['changes']&.each do |event_json| 80 | yield change_update_callback_params(entry, event_json) 81 | end 82 | entry_json['messaging']&.each do |event_json| 83 | yield messaging_callback_params(entry, event_json) 84 | end 85 | end 86 | end 87 | 88 | def change_update_callback_params(entry, event_json) 89 | { 90 | entry: entry, 91 | changes: event_json.deep_symbolize_keys 92 | } 93 | end 94 | 95 | def messaging_callback_params(entry, event_json) 96 | { 97 | entry: entry, 98 | sender: Messaging::Sender.new(event_json['sender']), 99 | recipient: Messaging::Recipient.new(event_json['recipient']), 100 | timestamp: event_json['timestamp'], 101 | messaging: event_json.deep_symbolize_keys 102 | } 103 | end 104 | 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /lib/message_quickly/callback_registry.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | module MessageQuickly 4 | class CallbackRegistry 5 | 6 | module ClassMethods 7 | 8 | attr_reader :callbacks 9 | 10 | def register(klass) 11 | @callbacks[klass.webhook_name] = klass.to_s 12 | end 13 | 14 | def handler_for(webhook_name) 15 | @callbacks[webhook_name]&.constantize 16 | end 17 | 18 | end 19 | 20 | extend ClassMethods 21 | 22 | end 23 | 24 | CallbackRegistry.class_eval do 25 | @callbacks = {} 26 | end 27 | 28 | end 29 | -------------------------------------------------------------------------------- /lib/message_quickly/change_update_event.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | class ChangeUpdateEvent 3 | 4 | attr_reader :changes 5 | 6 | def initialize(params = {}) 7 | @object_id = params[:entry].id 8 | @time = params[:entry].time 9 | @changes = params[:changes] 10 | end 11 | 12 | def webhook_name 13 | :changes 14 | end 15 | 16 | end 17 | end 18 | 19 | # { 20 | # "object":"page", 21 | # "entry":[ 22 | # { 23 | # "id":"PAGE_ID", 24 | # "time":1476077449, 25 | # "changes":[ 26 | # { 27 | # "field":"conversations", 28 | # "value":{ 29 | # "thread_id":"CONVERSATION_ID", 30 | # "page_id":"PAGE_ID" 31 | # } 32 | # } 33 | # ] 34 | # } 35 | # ] 36 | # } 37 | -------------------------------------------------------------------------------- /lib/message_quickly/engine.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | 3 | class Engine < ::Rails::Engine 4 | 5 | isolate_namespace MessageQuickly 6 | 7 | initializer 'message_quickly.action_controller' do |app| 8 | ActiveSupport.on_load :action_controller do 9 | helper MessageQuickly::ApplicationHelper 10 | end 11 | end 12 | 13 | config.generators do |g| 14 | g.test_framework :rspec, :fixture => false 15 | g.assets false 16 | g.helper false 17 | end 18 | 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/account_link_button.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | class AccountLinkButton < Button 4 | 5 | attr_accessor :url 6 | 7 | def initialize(params = {}) 8 | params['type'] ||= 'account_link' 9 | super(params) 10 | end 11 | 12 | def to_hash 13 | { type: type, url: url } 14 | end 15 | 16 | end 17 | end 18 | end 19 | 20 | # "buttons": [{ 21 | # "type": "account_link", 22 | # "url": "https://www.example.com/oauth/authorize" 23 | # }] 24 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/account_link_event.rb: -------------------------------------------------------------------------------- 1 | require "message_quickly/messaging/event" 2 | 3 | module MessageQuickly 4 | module Messaging 5 | class AccountLinkEvent < Event 6 | 7 | attr_reader :status, :authorization_code 8 | 9 | def initialize(params = {}) 10 | 11 | if params.include? :account_linking 12 | @status = params[:account_linking][:status] 13 | @authorization_code = params[:account_linking][:authorization_code] 14 | params.delete(:account_linking) 15 | end 16 | 17 | super(params) 18 | 19 | end 20 | 21 | def webhook_name 22 | :account_linking 23 | end 24 | 25 | end 26 | end 27 | end 28 | 29 | # { 30 | # "sender":{ 31 | # "id":"USER_ID" 32 | # }, 33 | # "recipient":{ 34 | # "id":"PAGE_ID" 35 | # }, 36 | # "timestamp":1234567890, 37 | # "account_linking":{ 38 | # "status":"linked", 39 | # "authorization_code":"PASS_THROUGH_AUTHORIZATION_CODE" 40 | # } 41 | # } 42 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/account_unlink_button.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | class AccountUnlinkButton < Button 4 | 5 | attr_accessor :url 6 | 7 | def initialize(params = {}) 8 | params['type'] ||= 'account_unlink' 9 | super(params) 10 | end 11 | 12 | def to_hash 13 | { type: type } 14 | end 15 | 16 | end 17 | end 18 | end 19 | 20 | # "buttons": [{ 21 | # "type": "account_link" 22 | # }] 23 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/attachment.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | class Attachment < Base 4 | 5 | attr_reader :type, :payload 6 | 7 | def file? 8 | false 9 | end 10 | 11 | def to_hash 12 | { type: type, payload: payload } 13 | end 14 | 15 | end 16 | end 17 | end 18 | 19 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/audio_attachment.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | class AudioAttachment < Attachment 4 | 5 | attr_accessor :url, :file, :file_type 6 | 7 | def initialize(params = {}) 8 | params['type'] ||= 'audio' 9 | super(params) 10 | end 11 | 12 | def file? 13 | file.present? && file_type.present? 14 | end 15 | 16 | def to_hash 17 | if file? 18 | { type: type, payload: { url: '' } } # cannot send empty hash 19 | else 20 | { type: type, payload: { url: url } } 21 | end 22 | end 23 | 24 | end 25 | end 26 | end 27 | 28 | # "attachments":[ 29 | # { 30 | # "type":"audio", 31 | # "payload":{ 32 | # "url":"https://petersapparel.com/bin/clip.mp3" 33 | # } 34 | # } 35 | # ] 36 | 37 | # curl \ 38 | # -F recipient='{"id":"USER_ID"}' \ 39 | # -F message='{"attachment":{"type":"audio", "payload":{}}}' \ 40 | # -F filedata=@/tmp/clip.mp3;type=audio/mp3 \ 41 | # "https://graph.facebook.com/v2.6/me/messages?access_token=PAGE_ACCESS_TOKEN" 42 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/base.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | class Base 4 | 5 | def initialize(params = {}) 6 | params.each { |key, value| instance_variable_set("@#{key}", value) } 7 | self 8 | end 9 | 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/button.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | class Button < Base 4 | 5 | attr_accessor :type, :title 6 | 7 | end 8 | end 9 | end -------------------------------------------------------------------------------- /lib/message_quickly/messaging/button_template_attachment.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | class ButtonTemplateAttachment < TemplateAttachment 4 | 5 | attr_accessor :text, :buttons 6 | 7 | def initialize(params = {}) 8 | self.buttons ||= [] 9 | params['template_type'] ||= 'button' 10 | super(params) 11 | end 12 | 13 | def build_button(button_type) 14 | case button_type 15 | when :web_url 16 | button = WebUrlButton.new 17 | when :postback 18 | button = PostbackButton.new 19 | when :account_link 20 | button = AccountLinkButton.new 21 | end 22 | buttons << button.tap { |button| yield button } 23 | end 24 | 25 | def to_hash 26 | { 27 | type: type, 28 | payload: { 29 | template_type: template_type, 30 | text: text, 31 | buttons: buttons.collect { |button| button.to_hash } 32 | } 33 | } 34 | end 35 | 36 | end 37 | end 38 | end 39 | 40 | # "attachment":{ 41 | # "type":"template", 42 | # "payload":{ 43 | # "template_type":"button", 44 | # "text":"What do you want to do next?", 45 | # "buttons":[ 46 | # { 47 | # "type":"web_url", 48 | # "url":"https://petersapparel.parseapp.com", 49 | # "title":"Show Website" 50 | # }, 51 | # { 52 | # "type":"postback", 53 | # "title":"Start Chatting", 54 | # "payload":"USER_DEFINED_PAYLOAD" 55 | # } 56 | # ] 57 | # } 58 | # } 59 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/delivery.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | class Delivery 4 | 5 | attr_accessor :id, :recipient, :message 6 | attr_accessor :notification_type # must be of values: REGULAR, SILENT_PUSH, NO_PUSH 7 | 8 | def initialize(options = {}) 9 | self.message ||= MessageQuickly::Messaging::Message.new 10 | options.each { |key, value| instance_variable_set("@#{key}", value) } 11 | yield self if block_given? 12 | end 13 | 14 | def build_message 15 | yield self.message 16 | end 17 | 18 | def to_hash 19 | hash = {} 20 | hash.merge!(recipient: recipient.to_hash) if recipient 21 | hash.merge!(message: message.to_hash) if message 22 | hash.merge!(notification_type: notification_type) if notification_type 23 | hash.merge!(filedata: Faraday::UploadIO.new(message.attachment.file, message.attachment.file_type)) if message.attachment && message.attachment.file? 24 | hash 25 | end 26 | 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/delivery_event.rb: -------------------------------------------------------------------------------- 1 | require "message_quickly/messaging/event" 2 | 3 | module MessageQuickly 4 | module Messaging 5 | class DeliveryEvent < Event 6 | 7 | attr_reader :mids, :watermark, :seq 8 | 9 | def initialize(params = {}) 10 | if params.include? :delivery 11 | @mids = params[:delivery][:mids] 12 | @watermark = params[:delivery][:watermark] 13 | @seq = params[:delivery][:seq] 14 | params.delete(:delivery) 15 | end 16 | super(params) 17 | end 18 | 19 | def webhook_name 20 | :message_deliveries 21 | end 22 | 23 | end 24 | end 25 | end 26 | 27 | # { 28 | # "object":"page", 29 | # "entry":[ 30 | # { 31 | # "id":PAGE_ID, 32 | # "time":1458668856451, 33 | # "messaging":[ 34 | # { 35 | # "sender":{ 36 | # "id":USER_ID 37 | # }, 38 | # "recipient":{ 39 | # "id":PAGE_ID 40 | # }, 41 | # "delivery":{ 42 | # "mids":[ 43 | # "mid.1458668856218:ed81099e15d3f4f233" 44 | # ], 45 | # "watermark":1458668856253, 46 | # "seq":37 47 | # } 48 | # } 49 | # ] 50 | # } 51 | # ] 52 | # } 53 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/element.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | class Element < MessageQuickly::Messaging::Base 4 | 5 | attr_accessor :title, :image_url, :subtitle, :buttons 6 | 7 | def initialize(params = {}) 8 | self.buttons ||= [] 9 | super(params) 10 | end 11 | 12 | def build_button(button_type) 13 | case button_type 14 | when :web_url 15 | button = WebUrlButton.new 16 | when :postback 17 | button = PostbackButton.new 18 | when :account_link 19 | button = AccountLinkButton.new 20 | end 21 | buttons << button.tap { |button| yield button } 22 | end 23 | 24 | def to_hash 25 | { 26 | title: title, 27 | image_url: image_url, 28 | subtitle: subtitle, 29 | buttons: buttons.collect { |button| button.to_hash } 30 | } 31 | end 32 | 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/entry.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | class Entry 4 | 5 | attr_accessor :id, :time 6 | 7 | def initialize(params = {}) 8 | @id = params['id'] 9 | @time = params['time'] 10 | end 11 | 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/event.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | class Event < Base 4 | 5 | attr_reader :entry, :sender, :recipient, :timestamp 6 | 7 | def initialize(params = {}) 8 | params.deep_symbolize_keys! 9 | @sender = Sender.new(params.delete(:sender)) if params.include?(:sender) 10 | @recipient = Recipient.new(params.delete(:recipient)) if params.include?(:recipient) 11 | super 12 | end 13 | 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/generic_template_attachment.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | class GenericTemplateAttachment < TemplateAttachment 4 | 5 | attr_accessor :text, :elements 6 | 7 | def initialize(params = {}) 8 | self.elements ||= [] 9 | params['template_type'] ||= 'generic' 10 | super(params) 11 | end 12 | 13 | def build_element 14 | elements << Element.new.tap { |element| yield element } 15 | end 16 | 17 | def to_hash 18 | { 19 | type: type, 20 | payload: { 21 | template_type: template_type, 22 | elements: elements.collect { |element| element.to_hash } 23 | } 24 | } 25 | end 26 | 27 | end 28 | end 29 | end 30 | 31 | # "attachment":{ 32 | # "type":"template", 33 | # "payload":{ 34 | # "template_type":"generic", 35 | # "elements":[ 36 | # { 37 | # "title":"Classic White T-Shirt", 38 | # "image_url":"http://petersapparel.parseapp.com/img/item100-thumb.png", 39 | # "subtitle":"Soft white cotton t-shirt is back in style", 40 | # "buttons":[ 41 | # { 42 | # "type":"web_url", 43 | # "url":"https://petersapparel.parseapp.com/view_item?item_id=100", 44 | # "title":"View Item" 45 | # }, 46 | # { 47 | # "type":"web_url", 48 | # "url":"https://petersapparel.parseapp.com/buy_item?item_id=100", 49 | # "title":"Buy Item" 50 | # }, 51 | # { 52 | # "type":"postback", 53 | # "title":"Bookmark Item", 54 | # "payload":"USER_DEFINED_PAYLOAD_FOR_ITEM100" 55 | # } 56 | # ] 57 | # }, 58 | # { 59 | # "title":"Classic Grey T-Shirt", 60 | # "image_url":"http://petersapparel.parseapp.com/img/item101-thumb.png", 61 | # "subtitle":"Soft gray cotton t-shirt is back in style", 62 | # "buttons":[ 63 | # { 64 | # "type":"web_url", 65 | # "url":"https://petersapparel.parseapp.com/view_item?item_id=101", 66 | # "title":"View Item" 67 | # }, 68 | # { 69 | # "type":"web_url", 70 | # "url":"https://petersapparel.parseapp.com/buy_item?item_id=101", 71 | # "title":"Buy Item" 72 | # }, 73 | # { 74 | # "type":"postback", 75 | # "title":"Bookmark Item", 76 | # "payload":"USER_DEFINED_PAYLOAD_FOR_ITEM101" 77 | # } 78 | # ] 79 | # } 80 | # ] 81 | # } 82 | # } -------------------------------------------------------------------------------- /lib/message_quickly/messaging/image_attachment.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | class ImageAttachment < Attachment 4 | 5 | attr_accessor :url, :file, :file_type 6 | 7 | def initialize(params = {}) 8 | params['type'] ||= 'image' 9 | super(params) 10 | end 11 | 12 | def file? 13 | file.present? && file_type.present? 14 | end 15 | 16 | def to_hash 17 | if file? 18 | { type: type, payload: { url: '' } } # cannot send empty hash 19 | else 20 | { type: type, payload: { url: url } } 21 | end 22 | end 23 | 24 | end 25 | end 26 | end 27 | 28 | # "attachments":[ 29 | # { 30 | # "type":"image", 31 | # "payload":{ 32 | # "url":"IMAGE_URL" 33 | # } 34 | # } 35 | # ] 36 | 37 | # curl \ 38 | # -F recipient='{"id":"USER_ID"}' \ 39 | # -F message='{"attachment":{"type":"image", "payload":{}}}' \ 40 | # -F filedata=@/tmp/testpng.png \ 41 | # "https://graph.facebook.com/v2.6/me/messages?access_token=PAGE_ACCESS_TOKEN" 42 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/location_attachment.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | class LocationAttachment < Attachment 4 | 5 | attr_accessor :latitude, :longitude 6 | 7 | def initialize(params = {}) 8 | params['type'] ||= 'location' 9 | super(params) 10 | end 11 | 12 | def to_hash 13 | { type: type, payload: { coordinates: { lat: latitude, long: longitude } } } 14 | end 15 | 16 | end 17 | end 18 | end 19 | 20 | # "attachments":[ 21 | # { 22 | # "type":"location", 23 | # "payload":{ 24 | # "coordinates":{ 25 | # "lat":123123123, 26 | # "long":123123123 27 | # } 28 | # } 29 | # } 30 | # ] 31 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/message.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | class Message 4 | 5 | attr_accessor :text, :attachment, :quick_replies 6 | 7 | def initialize(options = {}) 8 | options.each { |key, value| instance_variable_set("@#{key}", value) } 9 | yield self if block_given? 10 | end 11 | 12 | def build_attachment(attachment_type) 13 | case attachment_type 14 | when :image 15 | self.attachment = MessageQuickly::Messaging::ImageAttachment.new 16 | when :audio 17 | self.attachment = MessageQuickly::Messaging::AudioAttachment.new 18 | when :video 19 | self.attachment = MessageQuickly::Messaging::VideoAttachment.new 20 | when :generic_template 21 | self.attachment = MessageQuickly::Messaging::GenericTemplateAttachment.new 22 | when :button_template 23 | self.attachment = MessageQuickly::Messaging::ButtonTemplateAttachment.new 24 | when :receipt_template 25 | self.attachment = MessageQuickly::Messaging::ReceiptTemplateAttachment.new 26 | end 27 | yield attachment if block_given? 28 | end 29 | 30 | def build_quick_reply 31 | @quick_replies ||= [] 32 | new_quick_reply = MessageQuickly::Messaging::QuickReply.new 33 | quick_replies << new_quick_reply 34 | yield new_quick_reply 35 | end 36 | 37 | def to_hash 38 | if attachment 39 | hash_buffer = { attachment: attachment.to_hash } 40 | else 41 | hash_buffer = { text: text } 42 | end 43 | hash_buffer.merge!({ quick_replies: quick_replies.collect { |quick_reply| quick_reply.to_hash } }) if quick_replies.present? 44 | hash_buffer 45 | end 46 | 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/message_event.rb: -------------------------------------------------------------------------------- 1 | require "message_quickly/messaging/event" 2 | 3 | module MessageQuickly 4 | module Messaging 5 | class MessageEvent < Event 6 | 7 | attr_reader :mid, :seq, :text, :attachments, :quick_reply 8 | 9 | def initialize(params = {}) 10 | 11 | @attachments = params[:message].delete(:attachments).collect { |attachment_params| Attachment.new(attachment_params) } if params.dig(:message, :attachments) 12 | @attachments ||= [] 13 | 14 | @quick_reply = QuickReply.new(params[:message][:quick_reply]) if params.dig(:message, :quick_reply) 15 | 16 | if params.include? :message 17 | @mid = params[:message][:mid] 18 | @seq = params[:message][:seq] 19 | @is_echo = params[:message][:is_echo] 20 | @text = params[:message][:text] 21 | params.delete(:message) 22 | end 23 | 24 | super(params) 25 | 26 | end 27 | 28 | def webhook_name 29 | :messages 30 | end 31 | 32 | def is_echo? 33 | @is_echo 34 | end 35 | 36 | end 37 | end 38 | end 39 | 40 | # message with attachment 41 | 42 | # { 43 | # "object":"page", 44 | # "entry":[ 45 | # { 46 | # "id":PAGE_ID, 47 | # "time":1458696618911, 48 | # "messaging":[ 49 | # { 50 | # "sender":{ 51 | # "id":USER_ID 52 | # }, 53 | # "recipient":{ 54 | # "id":PAGE_ID 55 | # }, 56 | # "timestamp":1458696618268, 57 | # "message":{ 58 | # "mid":"mid.1458696618141:b4ef9d19ec21086067", 59 | # "seq":51, 60 | # "attachments":[ 61 | # { 62 | # "type":"image", 63 | # "payload":{ 64 | # "url":"IMAGE_URL" 65 | # } 66 | # } 67 | # ] 68 | # } 69 | # } 70 | # ] 71 | # } 72 | # ] 73 | # } 74 | 75 | # message with quick reply 76 | 77 | # { 78 | # "sender":{ 79 | # "id":"USER_ID" 80 | # }, 81 | # "recipient":{ 82 | # "id":"PAGE_ID" 83 | # }, 84 | # "timestamp":1458692752478, 85 | # "message":{ 86 | # "mid":"mid.1457764197618:41d102a3e1ae206a38", 87 | # "seq":73, 88 | # "text":"hello, world!", 89 | # "quick_reply": { 90 | # "payload": "DEVELOPER_DEFINED_PAYLOAD" 91 | # } 92 | # } 93 | # } 94 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/optin_event.rb: -------------------------------------------------------------------------------- 1 | require "message_quickly/messaging/event" 2 | 3 | module MessageQuickly 4 | module Messaging 5 | class OptinEvent < Event 6 | 7 | attr_reader :ref 8 | 9 | def initialize(params = {}) 10 | if params.include? :optin 11 | @ref = params[:optin][:ref] 12 | params.delete(:optin) 13 | end 14 | super(params) 15 | end 16 | 17 | def webhook_name 18 | :messaging_optins 19 | end 20 | 21 | end 22 | end 23 | end 24 | 25 | # { 26 | # "object":"page", 27 | # "entry":[ 28 | # { 29 | # "id":PAGE_ID, 30 | # "time":12341, 31 | # "messaging":[ 32 | # { 33 | # "sender":{ 34 | # "id":USER_ID 35 | # }, 36 | # "recipient":{ 37 | # "id":PAGE_ID 38 | # }, 39 | # "timestamp":1234567890, 40 | # "optin":{ 41 | # "ref":"PASS_THROUGH_PARAM" 42 | # } 43 | # } 44 | # ] 45 | # } 46 | # ] 47 | # } 48 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/postback_button.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | class PostbackButton < Button 4 | 5 | attr_accessor :payload 6 | 7 | def initialize(params = {}) 8 | params['type'] ||= 'postback' 9 | super(params) 10 | end 11 | 12 | def to_hash 13 | { type: type, title: title, payload: payload } 14 | end 15 | 16 | end 17 | end 18 | end 19 | 20 | # { 21 | # "type":"postback", 22 | # "title":"Start Chatting", 23 | # "payload":"USER_DEFINED_PAYLOAD" 24 | # } 25 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/postback_event.rb: -------------------------------------------------------------------------------- 1 | require "message_quickly/messaging/event" 2 | 3 | module MessageQuickly 4 | module Messaging 5 | class PostbackEvent < Event 6 | 7 | attr_reader :payload 8 | 9 | def initialize(params = {}) 10 | if params.include? :postback 11 | @payload = params[:postback][:payload] 12 | params.delete(:postback) 13 | end 14 | super(params) 15 | end 16 | 17 | def webhook_name 18 | :messaging_postbacks 19 | end 20 | 21 | end 22 | end 23 | end 24 | 25 | # { 26 | # "object":"page", 27 | # "entry":[ 28 | # { 29 | # "id":PAGE_ID, 30 | # "time":1458692752478, 31 | # "messaging":[ 32 | # { 33 | # "sender":{ 34 | # "id":USER_ID 35 | # }, 36 | # "recipient":{ 37 | # "id":PAGE_ID 38 | # }, 39 | # "timestamp":1458692752478, 40 | # "postback":{ 41 | # "payload":"USER_DEFINED_PAYLOAD" 42 | # } 43 | # } 44 | # ] 45 | # } 46 | # ] 47 | # } 48 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/quick_reply.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | class QuickReply < Base 4 | 5 | attr_accessor :content_type, :title, :payload 6 | 7 | def initialize(params = {}) 8 | params['content_type'] ||= 'text' 9 | super(params) 10 | end 11 | 12 | def to_hash 13 | { content_type: content_type, title: title, payload: payload } 14 | end 15 | 16 | end 17 | end 18 | end 19 | 20 | # "message":{ 21 | # "text":"Pick a color:", 22 | # "quick_replies":[ 23 | # { 24 | # "content_type":"text", 25 | # "title":"Red", 26 | # "payload":"DEVELOPER_DEFINED_PAYLOAD_FOR_PICKING_RED" 27 | # }, 28 | # { 29 | # "content_type":"text", 30 | # "title":"Green", 31 | # "payload":"DEVELOPER_DEFINED_PAYLOAD_FOR_PICKING_GREEN" 32 | # } 33 | # ] 34 | # } 35 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/read_event.rb: -------------------------------------------------------------------------------- 1 | require "message_quickly/messaging/event" 2 | 3 | module MessageQuickly 4 | module Messaging 5 | class ReadEvent < Event 6 | 7 | attr_reader :watermark, :seq 8 | 9 | def initialize(params = {}) 10 | if params.include? :read 11 | @watermark = params[:read][:watermark] 12 | @seq = params[:read][:seq] 13 | params.delete(:read) 14 | end 15 | super(params) 16 | end 17 | 18 | def webhook_name 19 | :message_reads 20 | end 21 | 22 | end 23 | end 24 | end 25 | 26 | # { 27 | # "object":"page", 28 | # "entry":[ 29 | # { 30 | # "id":PAGE_ID, 31 | # "time":1458668856451, 32 | # "messaging":[ 33 | # { 34 | # "sender":{ 35 | # "id":USER_ID 36 | # }, 37 | # "recipient":{ 38 | # "id":PAGE_ID 39 | # }, 40 | # "timestamp":1458668856463, 41 | # "read":{ 42 | # "watermark":1458668856253, 43 | # "seq":38 44 | # } 45 | # } 46 | # ] 47 | # } 48 | # ] 49 | # } 50 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/receipt/address.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | module Receipt 4 | class Address < MessageQuickly::Messaging::Base 5 | 6 | attr_accessor :street_1, :street_2, :city, :postal_code, :state, :country 7 | 8 | def to_hash 9 | { 10 | street_1: street_1, 11 | street_2: street_2, 12 | city: city, 13 | postal_code: postal_code, 14 | state: state, 15 | country: country 16 | } 17 | end 18 | 19 | end 20 | end 21 | end 22 | end 23 | 24 | # "address":{ 25 | # "street_1":"1 Hacker Way", 26 | # "street_2":"", 27 | # "city":"Menlo Park", 28 | # "postal_code":"94025", 29 | # "state":"CA", 30 | # "country":"US" 31 | # } 32 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/receipt/adjustment.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | module Receipt 4 | class Adjustment < MessageQuickly::Messaging::Base 5 | 6 | attr_accessor :name, :amount 7 | 8 | def to_hash 9 | { 10 | name: name, 11 | amount: amount 12 | } 13 | end 14 | 15 | end 16 | end 17 | end 18 | end 19 | 20 | # { 21 | # "name":"New Customer Discount", 22 | # "amount":20 23 | # } 24 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/receipt/element.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | module Receipt 4 | class Element < MessageQuickly::Messaging::Element 5 | 6 | attr_accessor :quantity, :price, :currency 7 | 8 | def to_hash 9 | { 10 | title: title, 11 | subtitle: subtitle, 12 | quantity: quantity, 13 | price: price, 14 | currency: currency, 15 | image_url: image_url 16 | } 17 | end 18 | 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/receipt/summary.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | module Receipt 4 | class Summary < MessageQuickly::Messaging::Base 5 | 6 | attr_accessor :subtotal, :shipping_cost, :total_tax, :total_cost 7 | 8 | def to_hash 9 | { 10 | subtotal: subtotal, 11 | shipping_cost: shipping_cost, 12 | total_tax: total_tax, 13 | total_cost: total_cost 14 | } 15 | end 16 | 17 | end 18 | end 19 | end 20 | end 21 | 22 | # "summary":{ 23 | # "subtotal":75.00, 24 | # "shipping_cost":4.95, 25 | # "total_tax":6.19, 26 | # "total_cost":56.14 27 | # } 28 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/receipt_template_attachment.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | class ReceiptTemplateAttachment < TemplateAttachment 4 | 5 | attr_accessor :recipient_name, :order_number, :currency, :payment_method, :order_url, :timestamp, :elements, :address, :summary, :adjustments 6 | 7 | def initialize(params = {}) 8 | self.elements ||= [] 9 | self.address ||= Receipt::Address.new 10 | self.summary ||= Receipt::Summary.new 11 | self.adjustments ||= [] 12 | params['template_type'] ||= 'receipt' 13 | super(params) 14 | end 15 | 16 | def build_element 17 | elements << Receipt::Element.new.tap { |element| yield element } 18 | end 19 | 20 | def build_address 21 | yield address 22 | end 23 | 24 | def build_summary 25 | yield summary 26 | end 27 | 28 | def build_adjustment 29 | adjustments << Receipt::Adjustment.new.tap { |adjustment| yield adjustment } 30 | end 31 | 32 | def to_hash 33 | { 34 | type: type, 35 | payload: { 36 | template_type: template_type, 37 | recipient_name: recipient_name, 38 | order_number: order_number, 39 | currency: currency, 40 | payment_method: payment_method, 41 | order_url: order_url, 42 | timestamp: timestamp, 43 | elements: elements.collect { |element| element.to_hash }, 44 | address: address.to_hash, 45 | summary: summary.to_hash, 46 | adjustments: adjustments.collect { |adjustment| adjustment.to_hash } 47 | } 48 | } 49 | end 50 | 51 | end 52 | end 53 | end 54 | 55 | # "attachment":{ 56 | # "type":"template", 57 | # "payload":{ 58 | # "template_type":"receipt", 59 | # "recipient_name":"Stephane Crozatier", 60 | # "order_number":"12345678902", 61 | # "currency":"USD", 62 | # "payment_method":"Visa 2345", 63 | # "order_url":"http://petersapparel.parseapp.com/order?order_id=123456", 64 | # "timestamp":"1428444852", 65 | # "elements":[ 66 | # { 67 | # "title":"Classic White T-Shirt", 68 | # "subtitle":"100% Soft and Luxurious Cotton", 69 | # "quantity":2, 70 | # "price":50, 71 | # "currency":"USD", 72 | # "image_url":"http://petersapparel.parseapp.com/img/whiteshirt.png" 73 | # }, 74 | # { 75 | # "title":"Classic Gray T-Shirt", 76 | # "subtitle":"100% Soft and Luxurious Cotton", 77 | # "quantity":1, 78 | # "price":25, 79 | # "currency":"USD", 80 | # "image_url":"http://petersapparel.parseapp.com/img/grayshirt.png" 81 | # } 82 | # ], 83 | # "address":{ 84 | # "street_1":"1 Hacker Way", 85 | # "street_2":"", 86 | # "city":"Menlo Park", 87 | # "postal_code":"94025", 88 | # "state":"CA", 89 | # "country":"US" 90 | # }, 91 | # "summary":{ 92 | # "subtotal":75.00, 93 | # "shipping_cost":4.95, 94 | # "total_tax":6.19, 95 | # "total_cost":56.14 96 | # }, 97 | # "adjustments":[ 98 | # { 99 | # "name":"New Customer Discount", 100 | # "amount":20 101 | # }, 102 | # { 103 | # "name":"$10 Off Coupon", 104 | # "amount":10 105 | # } 106 | # ] 107 | # } 108 | # } -------------------------------------------------------------------------------- /lib/message_quickly/messaging/recipient.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | class Recipient < User 4 | 5 | attr_accessor :phone_number 6 | 7 | def to_hash 8 | if id.present? 9 | { id: id } 10 | else 11 | { phone_number: phone_number } 12 | end 13 | end 14 | 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/sender.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | class Sender < User 4 | end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/template_attachment.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | class TemplateAttachment < Attachment 4 | 5 | attr_accessor :template_type 6 | 7 | def initialize(params = {}) 8 | params['type'] ||= 'template' 9 | super(params) 10 | end 11 | 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/user.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | class User < Base 4 | 5 | attr_reader :id, :first_name, :last_name, :profile_pic, :locale, :timezone, :gender 6 | 7 | def first_name 8 | @first_name || user_profile.first_name 9 | end 10 | 11 | def last_name 12 | @last_name || user_profile.last_name 13 | end 14 | 15 | private 16 | 17 | def user_profile 18 | @user_profile ||= MessageQuickly::Api::UserProfile.find(id) 19 | end 20 | 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/video_attachment.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | class VideoAttachment < Attachment 4 | 5 | attr_accessor :url, :file, :file_type 6 | 7 | def initialize(params = {}) 8 | params['type'] ||= 'video' 9 | super(params) 10 | end 11 | 12 | def file? 13 | file.present? && file_type.present? 14 | end 15 | 16 | def to_hash 17 | if file? 18 | { type: type, payload: { url: '' } } # cannot send empty hash 19 | else 20 | { type: type, payload: { url: url } } 21 | end 22 | end 23 | 24 | end 25 | end 26 | end 27 | 28 | # "attachments":[ 29 | # { 30 | # "type":"video", 31 | # "payload":{ 32 | # "url":"https://petersapparel.com/bin/clip.mp4" 33 | # } 34 | # } 35 | # ] 36 | 37 | # curl \ 38 | # -F recipient='{"id":"USER_ID"}' \ 39 | # -F message='{"attachment":{"type":"video", "payload":{}}}' \ 40 | # -F filedata=@/tmp/clip.mp4;type=video/mp4 \ 41 | # "https://graph.facebook.com/v2.6/me/messages?access_token=PAGE_ACCESS_TOKEN" 42 | -------------------------------------------------------------------------------- /lib/message_quickly/messaging/web_url_button.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | module Messaging 3 | class WebUrlButton < Button 4 | 5 | attr_accessor :url 6 | 7 | def initialize(params = {}) 8 | params['type'] ||= 'web_url' 9 | super(params) 10 | end 11 | 12 | def to_hash 13 | { type: type, url: url, title: title } 14 | end 15 | 16 | end 17 | end 18 | end 19 | 20 | # { 21 | # "type":"web_url", 22 | # "url":"https://petersapparel.parseapp.com", 23 | # "title":"Show Website" 24 | # } 25 | -------------------------------------------------------------------------------- /lib/message_quickly/version.rb: -------------------------------------------------------------------------------- 1 | module MessageQuickly 2 | VERSION = "1.2.0" 3 | FB_MESSENGER_VERSION = "v2.7" 4 | end 5 | -------------------------------------------------------------------------------- /message_quickly.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path("../lib", __FILE__) 2 | 3 | # Maintain your gem's version: 4 | require "message_quickly/version" 5 | 6 | # Describe your gem and declare its dependencies: 7 | Gem::Specification.new do |s| 8 | s.name = "message_quickly" 9 | s.version = MessageQuickly::VERSION 10 | s.authors = ["Jaryl Sim"] 11 | s.email = ["jaryl@tinkerbox.com.sg"] 12 | s.homepage = "https://github.com/tinkerbox/message_quickly" 13 | s.summary = "Integrate Facebook's messenger platform with your rails app" 14 | s.description = "MessageQuickly includes a mountable engine to process webhooks, and an API wrapper to talk to Facebook." 15 | s.license = "MIT" 16 | 17 | s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.rdoc"] 18 | s.test_files = Dir["spec/**/*"] 19 | 20 | s.add_dependency "rails", "~> 4.2" 21 | s.add_dependency "faraday", "~> 0" 22 | 23 | s.add_development_dependency "rspec-rails", "~> 3.4" 24 | s.add_development_dependency "sqlite3", "~> 1.3" 25 | s.add_development_dependency "codeclimate-test-reporter", "~> 0" 26 | s.add_development_dependency "dotenv", "~> 2.1" 27 | s.add_development_dependency "pry", "~> 0" 28 | end 29 | -------------------------------------------------------------------------------- /spec/callback_parser_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module MessageQuickly 4 | 5 | describe CallbackParser do 6 | 7 | context 'with a optin request' do 8 | 9 | let(:optin_json) { JSON.parse(File.read("spec/fixtures/optin_request.json")) } 10 | 11 | subject { CallbackParser.new(optin_json) } 12 | 13 | it { expect { |b| subject.parse(&b) }.to yield_with_args(Messaging::OptinEvent) } 14 | it { expect(subject.parse).not_to be_empty } 15 | 16 | end 17 | 18 | context 'with a delivery request' do 19 | 20 | let(:delivery_json) { JSON.parse(File.read("spec/fixtures/delivery_request.json")) } 21 | 22 | subject { CallbackParser.new(delivery_json) } 23 | 24 | it { expect { |b| subject.parse(&b) }.to yield_with_args(Messaging::DeliveryEvent) } 25 | it { expect(subject.parse).not_to be_empty } 26 | 27 | end 28 | 29 | context 'with a message request' do 30 | 31 | let(:message_json) { JSON.parse(File.read("spec/fixtures/message_request.json")) } 32 | 33 | subject { CallbackParser.new(message_json) } 34 | 35 | it { expect { |b| subject.parse(&b) }.to yield_with_args(Messaging::MessageEvent) } 36 | it { expect(subject.parse).not_to be_empty } 37 | 38 | end 39 | 40 | context 'with a read event' do 41 | 42 | let(:message_json) { JSON.parse(File.read("spec/fixtures/message_read.json")) } 43 | 44 | subject { CallbackParser.new(message_json) } 45 | 46 | it { expect { |b| subject.parse(&b) }.to yield_with_args(Messaging::ReadEvent) } 47 | it { expect(subject.parse).not_to be_empty } 48 | 49 | end 50 | 51 | context 'with a message request with attachment' do 52 | 53 | let(:message_json) { JSON.parse(File.read("spec/fixtures/message_request_with_attachment.json")) } 54 | 55 | subject { CallbackParser.new(message_json) } 56 | 57 | it { expect { |b| subject.parse(&b) }.to yield_with_args(Messaging::MessageEvent) } 58 | it { expect(subject.parse).not_to be_empty } 59 | it { expect(subject.parse.first.attachments).not_to be_empty } 60 | 61 | end 62 | 63 | context 'with a postback request' do 64 | 65 | let(:postback_json) { JSON.parse(File.read("spec/fixtures/postback_request.json")) } 66 | 67 | subject { CallbackParser.new(postback_json) } 68 | 69 | it { expect { |b| subject.parse(&b) }.to yield_with_args(Messaging::PostbackEvent) } 70 | it { expect(subject.parse).not_to be_empty } 71 | 72 | end 73 | 74 | context 'with an account link event' do 75 | 76 | let(:message_json) { JSON.parse(File.read("spec/fixtures/account_link.json")) } 77 | 78 | subject { CallbackParser.new(message_json) } 79 | 80 | it { expect { |b| subject.parse(&b) }.to yield_with_args(Messaging::AccountLinkEvent) } 81 | it { expect(subject.parse).not_to be_empty } 82 | 83 | end 84 | 85 | context 'with a change notification event' do 86 | 87 | let(:message_json) { JSON.parse(File.read("spec/fixtures/change_notification_request.json")) } 88 | 89 | subject { CallbackParser.new(message_json) } 90 | 91 | it { expect { |b| subject.parse(&b) }.to yield_with_args(ChangeUpdateEvent) } 92 | it { expect(subject.parse).not_to be_empty } 93 | 94 | end 95 | 96 | end 97 | 98 | end 99 | -------------------------------------------------------------------------------- /spec/callback_registry_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module MessageQuickly 4 | describe CallbackRegistry do 5 | 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/controllers/webhooks_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe MessageQuickly::WebhooksController, type: :controller do 4 | 5 | routes { MessageQuickly::Engine.routes } 6 | 7 | describe 'GET #verify' do 8 | 9 | context 'with valid token' do 10 | before { get :verify, 'hub.verify_token': ENV['FACEBOOK_MESSENGER_VERIFICATION_TOKEN'], 'hub.challenge': '123456' } 11 | it { expect(response.body).to eq('123456') } 12 | it { expect(response).to have_http_status(200) } 13 | end 14 | 15 | context 'with invalid token' do 16 | before { get :verify, 'hub.verify_token': '', 'hub.challenge': '123456' } 17 | it { expect(response.body).to eq('Wrong validation token') } 18 | it { expect(response).to have_http_status(500) } 19 | end 20 | 21 | end 22 | 23 | describe 'POST #callback' do 24 | 25 | context 'with an optin request' do 26 | 27 | let(:optin_json) { File.read("spec/fixtures/optin_request.json") } 28 | 29 | context 'with valid params' do 30 | before { post :callback, optin_json } 31 | it { expect(response).to have_http_status(200) } 32 | end 33 | 34 | context 'with invalid params' do 35 | before { post :callback, 'invalid json' } 36 | it { expect(response.body).to eq('Error processing callback') } 37 | it { expect(response).to have_http_status(500) } 38 | end 39 | 40 | end 41 | 42 | context 'with a delivery request' do 43 | 44 | let(:optin_json) { File.read("spec/fixtures/delivery_request.json") } 45 | 46 | context 'with valid params' do 47 | before { post :callback, optin_json } 48 | it { expect(response).to have_http_status(200) } 49 | end 50 | 51 | context 'with invalid params' do 52 | before { post :callback, 'invalid json' } 53 | it { expect(response.body).to eq('Error processing callback') } 54 | it { expect(response).to have_http_status(500) } 55 | end 56 | 57 | end 58 | 59 | context 'with a message request' do 60 | 61 | let(:optin_json) { File.read("spec/fixtures/message_request.json") } 62 | 63 | context 'with valid params' do 64 | before { post :callback, optin_json } 65 | it { expect(response).to have_http_status(200) } 66 | end 67 | 68 | context 'with invalid params' do 69 | before { post :callback, 'invalid json' } 70 | it { expect(response.body).to eq('Error processing callback') } 71 | it { expect(response).to have_http_status(500) } 72 | end 73 | 74 | end 75 | 76 | context 'with a read event' do 77 | 78 | let(:optin_json) { File.read("spec/fixtures/message_read.json") } 79 | 80 | context 'with valid params' do 81 | before { post :callback, optin_json } 82 | it { expect(response).to have_http_status(200) } 83 | end 84 | 85 | context 'with invalid params' do 86 | before { post :callback, 'invalid json' } 87 | it { expect(response.body).to eq('Error processing callback') } 88 | it { expect(response).to have_http_status(500) } 89 | end 90 | 91 | end 92 | 93 | context 'with a postback request' do 94 | 95 | let(:optin_json) { File.read("spec/fixtures/postback_request.json") } 96 | 97 | context 'with valid params' do 98 | before { post :callback, optin_json } 99 | it { expect(response).to have_http_status(200) } 100 | end 101 | 102 | context 'with invalid params' do 103 | before { post :callback, 'invalid json' } 104 | it { expect(response.body).to eq('Error processing callback') } 105 | it { expect(response).to have_http_status(500) } 106 | end 107 | 108 | end 109 | 110 | context 'with an account link event' do 111 | 112 | let(:optin_json) { File.read("spec/fixtures/account_link.json") } 113 | 114 | context 'with valid params' do 115 | before { post :callback, optin_json } 116 | it { expect(response).to have_http_status(200) } 117 | end 118 | 119 | context 'with invalid params' do 120 | before { post :callback, 'invalid json' } 121 | it { expect(response.body).to eq('Error processing callback') } 122 | it { expect(response).to have_http_status(500) } 123 | end 124 | 125 | end 126 | 127 | context 'with a change update event' do 128 | 129 | let(:optin_json) { File.read("spec/fixtures/change_notification_request.json") } 130 | 131 | context 'with valid params' do 132 | before { post :callback, optin_json } 133 | it { expect(response).to have_http_status(200) } 134 | end 135 | 136 | context 'with invalid params' do 137 | before { post :callback, 'invalid json' } 138 | it { expect(response.body).to eq('Error processing callback') } 139 | it { expect(response).to have_http_status(500) } 140 | end 141 | 142 | end 143 | 144 | end 145 | 146 | end 147 | -------------------------------------------------------------------------------- /spec/dummy/README.rdoc: -------------------------------------------------------------------------------- 1 | == README 2 | 3 | This README would normally document whatever steps are necessary to get the 4 | application up and running. 5 | 6 | Things you may want to cover: 7 | 8 | * Ruby version 9 | 10 | * System dependencies 11 | 12 | * Configuration 13 | 14 | * Database creation 15 | 16 | * Database initialization 17 | 18 | * How to run the test suite 19 | 20 | * Services (job queues, cache servers, search engines, etc.) 21 | 22 | * Deployment instructions 23 | 24 | * ... 25 | 26 | 27 | Please feel free to use a different markup language if you do not plan to run 28 | rake doc:app. 29 | -------------------------------------------------------------------------------- /spec/dummy/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /spec/dummy/app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerbox/message_quickly/886227b11459872e59704c61ade55ba2ad4a32eb/spec/dummy/app/assets/images/.keep -------------------------------------------------------------------------------- /spec/dummy/app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require_tree . 14 | -------------------------------------------------------------------------------- /spec/dummy/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any styles 10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new 11 | * file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /spec/dummy/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Prevent CSRF attacks by raising an exception. 3 | # For APIs, you may want to use :null_session instead. 4 | protect_from_forgery with: :exception 5 | end 6 | -------------------------------------------------------------------------------- /spec/dummy/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerbox/message_quickly/886227b11459872e59704c61ade55ba2ad4a32eb/spec/dummy/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /spec/dummy/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /spec/dummy/app/jobs/process_messenger_callback_job.rb: -------------------------------------------------------------------------------- 1 | class ProcessMessengerCallbackJob < ActiveJob::Base 2 | 3 | queue_as :messaging 4 | 5 | def perform(json) 6 | MessageQuickly::CallbackParser.new(json.deep_dup).parse do |event| 7 | callback_handler = MessageQuickly::CallbackRegistry.handler_for(event.webhook_name) 8 | if callback_handler 9 | callback = callback_handler.new(event, json.deep_dup) 10 | callback.run 11 | end 12 | end 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /spec/dummy/app/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerbox/message_quickly/886227b11459872e59704c61ade55ba2ad4a32eb/spec/dummy/app/mailers/.keep -------------------------------------------------------------------------------- /spec/dummy/app/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerbox/message_quickly/886227b11459872e59704c61ade55ba2ad4a32eb/spec/dummy/app/models/.keep -------------------------------------------------------------------------------- /spec/dummy/app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerbox/message_quickly/886227b11459872e59704c61ade55ba2ad4a32eb/spec/dummy/app/models/concerns/.keep -------------------------------------------------------------------------------- /spec/dummy/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dummy 5 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> 6 | <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> 7 | <%= csrf_meta_tags %> 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /spec/dummy/app/webhooks/account_linking_callback.rb: -------------------------------------------------------------------------------- 1 | class AccountLinkingCallback < MessageQuickly::Callback 2 | 3 | def self.webhook_name 4 | :account_linking 5 | end 6 | 7 | def initialize(event, json) 8 | super 9 | end 10 | 11 | def run 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /spec/dummy/app/webhooks/authentication_callback.rb: -------------------------------------------------------------------------------- 1 | class AuthenticationCallback < MessageQuickly::Callback 2 | 3 | def self.webhook_name 4 | :messaging_optins 5 | end 6 | 7 | def initialize(event, json) 8 | super 9 | end 10 | 11 | def run 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /spec/dummy/app/webhooks/change_update_callback.rb: -------------------------------------------------------------------------------- 1 | class ChangeUpdateCallback 2 | 3 | def self.webhook_name 4 | :changes 5 | end 6 | 7 | def initialize(event, json) 8 | super 9 | end 10 | 11 | def run 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /spec/dummy/app/webhooks/message_delivered_callback.rb: -------------------------------------------------------------------------------- 1 | class MessageDeliveredCallback < MessageQuickly::Callback 2 | 3 | def self.webhook_name 4 | :message_deliveries 5 | end 6 | 7 | def initialize(event, json) 8 | super 9 | end 10 | 11 | def run 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /spec/dummy/app/webhooks/message_read_callback.rb: -------------------------------------------------------------------------------- 1 | class MessageReadCallback < MessageQuickly::Callback 2 | 3 | def self.webhook_name 4 | :message_reads 5 | end 6 | 7 | def initialize(event, json) 8 | super 9 | end 10 | 11 | def run 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /spec/dummy/app/webhooks/message_received_callback.rb: -------------------------------------------------------------------------------- 1 | class MessageReceivedCallback < MessageQuickly::Callback 2 | 3 | def self.webhook_name 4 | :messages 5 | end 6 | 7 | def initialize(event, json) 8 | super 9 | end 10 | 11 | def run 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /spec/dummy/app/webhooks/postback_callback.rb: -------------------------------------------------------------------------------- 1 | class PostbackCallback < MessageQuickly::Callback 2 | 3 | def self.webhook_name 4 | :messaging_postbacks 5 | end 6 | 7 | def initialize(event, json) 8 | super 9 | end 10 | 11 | def run 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /spec/dummy/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /spec/dummy/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../../config/application', __FILE__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /spec/dummy/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /spec/dummy/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | 4 | # path to your application root. 5 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 6 | 7 | Dir.chdir APP_ROOT do 8 | # This script is a starting point to setup your application. 9 | # Add necessary setup steps to this file: 10 | 11 | puts "== Installing dependencies ==" 12 | system "gem install bundler --conservative" 13 | system "bundle check || bundle install" 14 | 15 | # puts "\n== Copying sample files ==" 16 | # unless File.exist?("config/database.yml") 17 | # system "cp config/database.yml.sample config/database.yml" 18 | # end 19 | 20 | puts "\n== Preparing database ==" 21 | system "bin/rake db:setup" 22 | 23 | puts "\n== Removing old logs and tempfiles ==" 24 | system "rm -f log/*" 25 | system "rm -rf tmp/cache" 26 | 27 | puts "\n== Restarting application server ==" 28 | system "touch tmp/restart.txt" 29 | end 30 | -------------------------------------------------------------------------------- /spec/dummy/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /spec/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | # Pick the frameworks you want: 4 | require "active_record/railtie" 5 | require "action_controller/railtie" 6 | require "action_mailer/railtie" 7 | require "action_view/railtie" 8 | require "sprockets/railtie" 9 | 10 | Bundler.require(*Rails.groups) 11 | require "message_quickly" 12 | 13 | module Dummy 14 | class Application < Rails::Application 15 | # Settings in config/environments/* take precedence over those specified here. 16 | # Application configuration should go into files in config/initializers 17 | # -- all .rb files in that directory are automatically loaded. 18 | 19 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 20 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 21 | # config.time_zone = 'Central Time (US & Canada)' 22 | 23 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 24 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 25 | # config.i18n.default_locale = :de 26 | 27 | # Do not swallow errors in after_commit/after_rollback callbacks. 28 | config.active_record.raise_in_transactional_callbacks = true 29 | end 30 | end 31 | 32 | -------------------------------------------------------------------------------- /spec/dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__) 3 | 4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 5 | $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__) 6 | -------------------------------------------------------------------------------- /spec/dummy/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: db/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: db/test.sqlite3 22 | 23 | production: 24 | <<: *default 25 | database: db/production.sqlite3 26 | -------------------------------------------------------------------------------- /spec/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /spec/dummy/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send. 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger. 20 | config.active_support.deprecation = :log 21 | 22 | # Raise an error on page load if there are pending migrations. 23 | config.active_record.migration_error = :page_load 24 | 25 | # Debug mode disables concatenation and preprocessing of assets. 26 | # This option may cause significant delays in view rendering with a large 27 | # number of complex assets. 28 | config.assets.debug = true 29 | 30 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 31 | # yet still be able to expire them through the digest params. 32 | config.assets.digest = true 33 | 34 | # Adds additional error checking when serving assets at runtime. 35 | # Checks for improperly declared sprockets dependencies. 36 | # Raises helpful error messages. 37 | config.assets.raise_runtime_errors = true 38 | 39 | # Raises error for missing translations 40 | # config.action_view.raise_on_missing_translations = true 41 | end 42 | -------------------------------------------------------------------------------- /spec/dummy/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like 20 | # NGINX, varnish or squid. 21 | # config.action_dispatch.rack_cache = true 22 | 23 | # Disable serving static files from the `/public` folder by default since 24 | # Apache or NGINX already handles this. 25 | config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present? 26 | 27 | # Compress JavaScripts and CSS. 28 | config.assets.js_compressor = :uglifier 29 | # config.assets.css_compressor = :sass 30 | 31 | # Do not fallback to assets pipeline if a precompiled asset is missed. 32 | config.assets.compile = false 33 | 34 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 35 | # yet still be able to expire them through the digest params. 36 | config.assets.digest = true 37 | 38 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 39 | 40 | # Specifies the header that your server uses for sending files. 41 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 42 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 43 | 44 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 45 | # config.force_ssl = true 46 | 47 | # Use the lowest log level to ensure availability of diagnostic information 48 | # when problems arise. 49 | config.log_level = :debug 50 | 51 | # Prepend all log lines with the following tags. 52 | # config.log_tags = [ :subdomain, :uuid ] 53 | 54 | # Use a different logger for distributed setups. 55 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 56 | 57 | # Use a different cache store in production. 58 | # config.cache_store = :mem_cache_store 59 | 60 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 61 | # config.action_controller.asset_host = 'http://assets.example.com' 62 | 63 | # Ignore bad email addresses and do not raise email delivery errors. 64 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 65 | # config.action_mailer.raise_delivery_errors = false 66 | 67 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 68 | # the I18n.default_locale when a translation cannot be found). 69 | config.i18n.fallbacks = true 70 | 71 | # Send deprecation notices to registered listeners. 72 | config.active_support.deprecation = :notify 73 | 74 | # Use default logging formatter so that PID and timestamp are not suppressed. 75 | config.log_formatter = ::Logger::Formatter.new 76 | 77 | # Do not dump schema after migrations. 78 | config.active_record.dump_schema_after_migration = false 79 | end 80 | -------------------------------------------------------------------------------- /spec/dummy/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static file server for tests with Cache-Control for performance. 16 | config.serve_static_files = true 17 | config.static_cache_control = 'public, max-age=3600' 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Randomize the order test cases are executed. 35 | config.active_support.test_order = :random 36 | 37 | # Print deprecation notices to the stderr. 38 | config.active_support.deprecation = :stderr 39 | 40 | # Raises error for missing translations 41 | # config.action_view.raise_on_missing_translations = true 42 | end 43 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 11 | # Rails.application.config.assets.precompile += %w( search.js ) 12 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.action_dispatch.cookies_serializer = :json 4 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_dummy_session' 4 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/webhooks.rb: -------------------------------------------------------------------------------- 1 | Rails.application.eager_load! 2 | 3 | MessageQuickly::Callback.subclasses.each do |callback| 4 | MessageQuickly::CallbackRegistry.register(callback) 5 | end 6 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /spec/dummy/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /spec/dummy/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | 3 | mount MessageQuickly::Engine => "/message_quickly" 4 | end 5 | -------------------------------------------------------------------------------- /spec/dummy/config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: 39cbb0af383a3b56c96ab9dd5c858245479d64e609138da92b7df2a3459713bf20244f4916ca48c389afa56d6dd9858ed73a660a9622ea0cf3bb29fd0a764ec3 15 | 16 | test: 17 | secret_key_base: 155cf3127e9fc7368cfbbad1f77cc92502dd5e35b68625e67ddab38e5a4133c2178a8cdf794b9382d21e1983d37befacd5e95c2466d2039e08b66d6409473cc5 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 23 | -------------------------------------------------------------------------------- /spec/dummy/lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerbox/message_quickly/886227b11459872e59704c61ade55ba2ad4a32eb/spec/dummy/lib/assets/.keep -------------------------------------------------------------------------------- /spec/dummy/log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerbox/message_quickly/886227b11459872e59704c61ade55ba2ad4a32eb/spec/dummy/log/.keep -------------------------------------------------------------------------------- /spec/dummy/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /spec/dummy/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /spec/dummy/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /spec/dummy/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerbox/message_quickly/886227b11459872e59704c61ade55ba2ad4a32eb/spec/dummy/public/favicon.ico -------------------------------------------------------------------------------- /spec/fixtures/12057251_909506139117248_2059695706_n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerbox/message_quickly/886227b11459872e59704c61ade55ba2ad4a32eb/spec/fixtures/12057251_909506139117248_2059695706_n.png -------------------------------------------------------------------------------- /spec/fixtures/SampleVideo_1280x720_1mb.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerbox/message_quickly/886227b11459872e59704c61ade55ba2ad4a32eb/spec/fixtures/SampleVideo_1280x720_1mb.mp4 -------------------------------------------------------------------------------- /spec/fixtures/account_link.json: -------------------------------------------------------------------------------- 1 | { 2 | "object":"page", 3 | "entry":[ 4 | { 5 | "id":"PAGE_ID", 6 | "time":1458668856451, 7 | "messaging":[ 8 | { 9 | "sender":{ 10 | "id":"USER_ID" 11 | }, 12 | "recipient":{ 13 | "id":"PAGE_ID" 14 | }, 15 | "timestamp":1234567890, 16 | "account_linking":{ 17 | "status":"linked", 18 | "authorization_code":"PASS_THROUGH_AUTHORIZATION_CODE" 19 | } 20 | } 21 | ] 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /spec/fixtures/change_notification_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "object":"page", 3 | "entry":[ 4 | { 5 | "id":"PAGE_ID", 6 | "time":1476077449, 7 | "changes":[ 8 | { 9 | "field":"conversations", 10 | "value":{ 11 | "thread_id":"CONVERSATION_ID", 12 | "page_id":"PAGE_ID" 13 | } 14 | } 15 | ] 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /spec/fixtures/delivery_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "object":"page", 3 | "entry":[ 4 | { 5 | "id":"PAGE_ID", 6 | "time":1458668856451, 7 | "messaging":[ 8 | { 9 | "sender":{ 10 | "id":"USER_ID" 11 | }, 12 | "recipient":{ 13 | "id":"PAGE_ID" 14 | }, 15 | "delivery":{ 16 | "mids":[ 17 | "mid.1458668856218:ed81099e15d3f4f233" 18 | ], 19 | "watermark":1458668856253, 20 | "seq":37 21 | } 22 | } 23 | ] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /spec/fixtures/jailhouse-rock-demo.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkerbox/message_quickly/886227b11459872e59704c61ade55ba2ad4a32eb/spec/fixtures/jailhouse-rock-demo.mp3 -------------------------------------------------------------------------------- /spec/fixtures/message_read.json: -------------------------------------------------------------------------------- 1 | { 2 | "object":"page", 3 | "entry":[ 4 | { 5 | "id":"PAGE_ID", 6 | "time":1458668856451, 7 | "messaging":[ 8 | { 9 | "sender":{ 10 | "id":"USER_ID" 11 | }, 12 | "recipient":{ 13 | "id":"PAGE_ID" 14 | }, 15 | "timestamp":1458668856463, 16 | "read":{ 17 | "watermark":1458668856253, 18 | "seq":38 19 | } 20 | } 21 | ] 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /spec/fixtures/message_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "object":"page", 3 | "entry":[ 4 | { 5 | "id":"PAGE_ID", 6 | "time":1457764198246, 7 | "messaging":[ 8 | { 9 | "sender":{ 10 | "id":"USER_ID" 11 | }, 12 | "recipient":{ 13 | "id":"PAGE_ID" 14 | }, 15 | "timestamp":1457764197627, 16 | "message":{ 17 | "mid":"mid.1457764197618:41d102a3e1ae206a38", 18 | "seq":73, 19 | "text":"hello, world!" 20 | } 21 | } 22 | ] 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /spec/fixtures/message_request_with_attachment.json: -------------------------------------------------------------------------------- 1 | { 2 | "object":"page", 3 | "entry":[ 4 | { 5 | "id":"PAGE_ID", 6 | "time":1458696618911, 7 | "messaging":[ 8 | { 9 | "sender":{ 10 | "id":"USER_ID" 11 | }, 12 | "recipient":{ 13 | "id":"PAGE_ID" 14 | }, 15 | "timestamp":1458696618268, 16 | "message":{ 17 | "mid":"mid.1458696618141:b4ef9d19ec21086067", 18 | "seq":51, 19 | "attachments":[ 20 | { 21 | "type":"image", 22 | "payload":{ 23 | "url":"IMAGE_URL" 24 | } 25 | }, 26 | { 27 | "type":"location", 28 | "payload":{ 29 | "coordinates":{ 30 | "lat": 1.3062306, 31 | "long": 103.85575970000002 32 | } 33 | } 34 | } 35 | ], 36 | "quick_reply": { 37 | "payload": "QUICK_REPLY_VALUE" 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /spec/fixtures/optin_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "object":"page", 3 | "entry":[ 4 | { 5 | "id":"PAGE_ID", 6 | "time":12341, 7 | "messaging":[ 8 | { 9 | "sender":{ 10 | "id":"USER_ID" 11 | }, 12 | "recipient":{ 13 | "id":"PAGE_ID" 14 | }, 15 | "timestamp":1234567890, 16 | "optin":{ 17 | "ref":"PASS_THROUGH_PARAM" 18 | } 19 | } 20 | ] 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /spec/fixtures/postback_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "object":"page", 3 | "entry":[ 4 | { 5 | "id":"PAGE_ID", 6 | "time":1458692752478, 7 | "messaging":[ 8 | { 9 | "sender":{ 10 | "id":"USER_ID" 11 | }, 12 | "recipient":{ 13 | "id":"PAGE_ID" 14 | }, 15 | "timestamp":1458692752478, 16 | "postback":{ 17 | "payload":"USER_DEFINED_PAYLOAD" 18 | } 19 | } 20 | ] 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /spec/helpers/application_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe ApplicationHelper, :type => :helper do 4 | 5 | describe "#send_to_messenger" do 6 | it { expect(helper.send_to_messenger).to eq("
") } 7 | end 8 | 9 | describe "#message_us" do 10 | it { expect(helper.message_us).to eq("
") } 11 | end 12 | 13 | end 14 | -------------------------------------------------------------------------------- /spec/message_quickly/api/account_link_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe MessageQuickly::Api::AccountLink do 4 | 5 | subject { MessageQuickly::Api::AccountLink } 6 | 7 | describe '#page_scoped_id' do 8 | 9 | let(:account_linking_token) { "token expires, so can't use it for testing" } 10 | 11 | context 'with own client' do 12 | let(:client) { MessageQuickly::Api::Client.new(page_access_token: ENV['FACEBOOK_MESSENGER_PAGE_ACCESS_TOKEN'], page_id: ENV['FACEBOOK_MESSENGER_PAGE_ID']) } 13 | # it { expect(subject.new(client).page_scoped_id(account_linking_token)).to eq(true) } 14 | it { expect { subject.page_scoped_id(account_linking_token) }.to raise_exception(MessageQuickly::Api::OauthException) } 15 | end 16 | 17 | context 'with valid params' do 18 | # it { expect(subject.page_scoped_id(account_linking_token)).to eq(true) } 19 | it { expect { subject.page_scoped_id(account_linking_token) }.to raise_exception(MessageQuickly::Api::OauthException) } 20 | end 21 | 22 | context 'with invalid params' do 23 | before { stub_const('ENV', ENV.to_hash.merge('FACEBOOK_MESSENGER_PAGE_ID' => 'invalid')) } 24 | it { expect { subject.page_scoped_id(account_linking_token) }.to raise_exception(MessageQuickly::Api::OauthException) } 25 | end 26 | 27 | end 28 | 29 | describe '#unlink_account' do 30 | 31 | let(:page_scoped_id) { ENV['FACEBOOK_MESSENGER_USER_ID'] } 32 | 33 | context 'with own client' do 34 | let(:client) { MessageQuickly::Api::Client.new(page_access_token: ENV['FACEBOOK_MESSENGER_PAGE_ACCESS_TOKEN'], page_id: ENV['FACEBOOK_MESSENGER_PAGE_ID']) } 35 | it { expect(subject.new(client).unlink_account(page_scoped_id)).to eq(true) } 36 | end 37 | 38 | context 'with valid params' do 39 | it { expect(subject.unlink_account(page_scoped_id)).to eq(true) } 40 | end 41 | 42 | context 'with invalid params' do 43 | before { stub_const('ENV', ENV.to_hash.merge('FACEBOOK_MESSENGER_PAGE_ID' => 'invalid')) } 44 | it { expect { subject.unlink_account('') }.to raise_exception(MessageQuickly::Api::OauthException) } 45 | end 46 | 47 | end 48 | 49 | end 50 | -------------------------------------------------------------------------------- /spec/message_quickly/api/messages_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe MessageQuickly::Api::Messages do 4 | 5 | subject { MessageQuickly::Api::Messages } 6 | 7 | let(:recipient) { MessageQuickly::Messaging::Recipient.new(id: ENV['FACEBOOK_MESSENGER_USER_ID']) } 8 | 9 | describe '#create' do 10 | 11 | context 'with own client' do 12 | 13 | let(:client) { MessageQuickly::Api::Client.new(page_access_token: ENV['FACEBOOK_MESSENGER_PAGE_ACCESS_TOKEN'], page_id: ENV['FACEBOOK_MESSENGER_PAGE_ID']) } 14 | 15 | it 'should be able to send text' do 16 | delivery = subject.new(client).create(recipient) do |message| 17 | message.text = 'Hello' 18 | end 19 | expect(delivery.id).not_to be_nil 20 | end 21 | 22 | end 23 | 24 | context 'with invalid recipient' do 25 | 26 | let(:recipient) { MessageQuickly::Messaging::Recipient.new(id: 'invalid') } 27 | 28 | it 'should raise an exception' do 29 | expect do 30 | subject.create(recipient) do |message| 31 | message.text = 'Hello' 32 | end 33 | end.to raise_exception(MessageQuickly::Api::OauthException) 34 | end 35 | 36 | end 37 | 38 | context 'with a phone number recipient' do 39 | 40 | context "that's valid" do 41 | let(:recipient) { MessageQuickly::Messaging::Recipient.new(phone: '+1(212)555-2368') } 42 | 43 | # it 'should be able to send text' do 44 | # pending 45 | # delivery = subject.create(recipient) do |message| 46 | # message.text = 'Hello' 47 | # end 48 | # expect(delivery.id).not_to be_nil 49 | # end 50 | end 51 | 52 | context "that's invalid" do 53 | let(:recipient) { MessageQuickly::Messaging::Recipient.new(phone: '+1(212)555-2368') } 54 | 55 | it 'should raise an exception' do 56 | expect do 57 | subject.create(recipient) do |message| 58 | message.text = 'Hello' 59 | end 60 | end.to raise_exception(MessageQuickly::Api::OauthException) 61 | end 62 | end 63 | 64 | end 65 | 66 | context 'with a valid recipient' do 67 | 68 | it 'should be able to send text' do 69 | delivery = subject.create(recipient) do |message| 70 | message.text = 'Hello' 71 | end 72 | expect(delivery.id).not_to be_nil 73 | end 74 | 75 | it 'should be able to send with an image url attachment' do 76 | delivery = subject.create(recipient) do |message| 77 | message.build_attachment(:image) { |attachment| attachment.url = 'http://placehold.it/350x150' } 78 | end 79 | expect(delivery.id).not_to be_nil 80 | end 81 | 82 | it 'should be able to send with an image file attachment' do 83 | delivery = subject.create(recipient) do |message| 84 | message.build_attachment(:image) do |attachment| 85 | attachment.file = "spec/fixtures/12057251_909506139117248_2059695706_n.png" 86 | attachment.file_type = 'image/png' 87 | end 88 | end 89 | expect(delivery.id).not_to be_nil 90 | end 91 | 92 | it 'should be able to send with an video url attachment' do 93 | delivery = subject.create(recipient) do |message| 94 | message.build_attachment(:video) { |attachment| attachment.url = 'http://techslides.com/demos/sample-videos/small.mp4' } 95 | end 96 | expect(delivery.id).not_to be_nil 97 | end 98 | 99 | it 'should be able to send with an video file attachment' do 100 | delivery = subject.create(recipient) do |message| 101 | message.build_attachment(:video) do |attachment| 102 | attachment.file = "spec/fixtures/SampleVideo_1280x720_1mb.mp4" 103 | attachment.file_type = 'video/mp4' 104 | end 105 | end 106 | expect(delivery.id).not_to be_nil 107 | end 108 | 109 | it 'should be able to send with an audio url attachment' do 110 | delivery = subject.create(recipient) do |message| 111 | message.build_attachment(:audio) { |attachment| attachment.url = 'http://www.stephaniequinn.com/Music/Commercial%20DEMO%20-%2010.mp3' } 112 | end 113 | expect(delivery.id).not_to be_nil 114 | end 115 | 116 | it 'should be able to send with an audio file attachment' do 117 | delivery = subject.create(recipient) do |message| 118 | message.build_attachment(:audio) do |attachment| 119 | attachment.file = "spec/fixtures/jailhouse-rock-demo.mp3" 120 | attachment.file_type = 'audio/png' 121 | end 122 | end 123 | expect(delivery.id).not_to be_nil 124 | end 125 | 126 | it 'should be able to send with a generic template attachment' do 127 | delivery = subject.create(recipient) do |message| 128 | message.build_attachment(:generic_template) do |template| 129 | 130 | template.build_element do |element| 131 | 132 | element.title = "Classic White T-Shirt" 133 | element.image_url = 'http://petersapparel.parseapp.com/img/item100-thumb.png' 134 | element.subtitle = 'Soft white cotton t-shirt is back in style' 135 | 136 | element.build_button(:web_url) do |button| 137 | button.url = "https://petersapparel.parseapp.com/view_item?item_id=100" 138 | button.title = "View Item" 139 | end 140 | 141 | element.build_button(:web_url) do |button| 142 | button.url = "https://petersapparel.parseapp.com/buy_item?item_id=100" 143 | button.title = "Buy Item" 144 | end 145 | 146 | element.build_button(:postback) do |button| 147 | button.payload = "USER_DEFINED_PAYLOAD_FOR_ITEM100" 148 | button.title = "Bookmark Item" 149 | end 150 | 151 | end 152 | 153 | template.build_element do |element| 154 | 155 | element.title = "Classic Grey T-Shirt" 156 | element.image_url = 'http://petersapparel.parseapp.com/img/item101-thumb.png' 157 | element.subtitle = 'Soft gray cotton t-shirt is back in style' 158 | 159 | element.build_button(:web_url) do |button| 160 | button.url = "https://petersapparel.parseapp.com/view_item?item_id=101" 161 | button.title = "View Item" 162 | end 163 | 164 | element.build_button(:web_url) do |button| 165 | button.url = "https://petersapparel.parseapp.com/buy_item?item_id=101" 166 | button.title = "Buy Item" 167 | end 168 | 169 | element.build_button(:postback) do |button| 170 | button.payload = "USER_DEFINED_PAYLOAD_FOR_ITEM101" 171 | button.title = "Bookmark Item" 172 | end 173 | 174 | end 175 | 176 | end 177 | end 178 | expect(delivery.id).not_to be_nil 179 | end 180 | 181 | it 'should be able to send with a button template attachment' do 182 | delivery = subject.create(recipient) do |message| 183 | message.build_attachment(:button_template) do |template| 184 | 185 | template.text = 'How are you doing today?' 186 | 187 | template.build_button(:web_url) do |button| 188 | button.url = 'https://petersapparel.parseapp.com' 189 | button.title = 'Show Website' 190 | end 191 | 192 | template.build_button(:postback) do |button| 193 | button.payload = 'USER_DEFINED_PAYLOAD' 194 | button.title = 'Start Chatting' 195 | end 196 | 197 | end 198 | end 199 | expect(delivery.id).not_to be_nil 200 | end 201 | 202 | it 'should be able to send with a receipt template attachment' do 203 | delivery = subject.create(recipient) do |message| 204 | message.build_attachment(:receipt_template) do |template| 205 | 206 | template.recipient_name = 'Stephane Crozatier' 207 | template.order_number = (0...50).map { ('a'..'z').to_a[rand(26)] }.join 208 | template.currency = 'USD' 209 | template.payment_method = 'Visa 2345' 210 | template.order_url = 'http://petersapparel.parseapp.com/order?order_id=123456' 211 | template.timestamp = '1428444852' 212 | 213 | template.build_element do |element| 214 | element.title = 'Classic White T-Shirt' 215 | element.subtitle = '100% Soft and Luxurious Cotton' 216 | element.quantity = 2 217 | element.price = 50 218 | element.currency = 'USD' 219 | element.image_url = 'http://petersapparel.parseapp.com/img/whiteshirt.png' 220 | end 221 | 222 | template.build_element do |element| 223 | element.title = 'Classic Gray T-Shirt' 224 | element.subtitle = '100% Soft and Luxurious Cotton' 225 | element.quantity = 1 226 | element.price = 25 227 | element.currency = 'USD' 228 | element.image_url = 'http://petersapparel.parseapp.com/img/grayshirt.png' 229 | end 230 | 231 | template.build_address do |address| 232 | address.street_1 = "1 Hacker Way" 233 | address.street_2 = "" 234 | address.city = "Menlo Park" 235 | address.postal_code = "94025" 236 | address.state = "CA" 237 | address.country = "US" 238 | end 239 | 240 | template.build_summary do |summary| 241 | summary.subtotal = 75.00 242 | summary.shipping_cost = 4.95 243 | summary.total_tax = 6.19 244 | summary.total_cost = 56.14 245 | end 246 | 247 | template.build_adjustment do |adjustment| 248 | adjustment.name = "New Customer Discount" 249 | adjustment.amount = 20 250 | end 251 | 252 | template.build_adjustment do |adjustment| 253 | adjustment.name = "$10 Off Coupon" 254 | adjustment.amount = 10 255 | end 256 | 257 | end 258 | end 259 | expect(delivery.id).not_to be_nil 260 | end 261 | 262 | it 'should be able to send quick replies' do 263 | delivery = subject.create(recipient) do |message| 264 | 265 | message.text = "Pick a color:" 266 | 267 | message.build_quick_reply do |quick_reply| 268 | quick_reply.title = 'Green' 269 | quick_reply.payload = 'DEVELOPER_DEFINED_PAYLOAD_FOR_PICKING_GREEN' 270 | end 271 | 272 | message.build_quick_reply do |quick_reply| 273 | quick_reply.title = 'Red' 274 | quick_reply.payload = 'DEVELOPER_DEFINED_PAYLOAD_FOR_PICKING_RED' 275 | end 276 | 277 | end 278 | expect(delivery.id).not_to be_nil 279 | end 280 | 281 | end 282 | 283 | it 'should be able to send account link buttons' do 284 | delivery = subject.create(recipient) do |message| 285 | 286 | message.build_attachment(:button_template) do |template| 287 | 288 | template.text = 'Please log in' 289 | 290 | template.build_button(:account_link) do |button| 291 | button.url = 'https://www.example.com/oauth/authorize' 292 | end 293 | 294 | end 295 | 296 | end 297 | expect(delivery.id).not_to be_nil 298 | end 299 | 300 | end 301 | 302 | describe '#create_from_hash' do 303 | 304 | let(:client) { MessageQuickly::Api::Client.new(page_access_token: ENV['FACEBOOK_MESSENGER_PAGE_ACCESS_TOKEN'], page_id: ENV['FACEBOOK_MESSENGER_PAGE_ID']) } 305 | let(:recipient) { MessageQuickly::Messaging::Recipient.new(id: ENV['FACEBOOK_MESSENGER_USER_ID']) } 306 | 307 | let(:hash) do 308 | MessageQuickly::Messaging::Delivery.new(recipient: recipient) do |delivery| 309 | delivery.build_message do |message| 310 | message.text = 'Hello' 311 | end 312 | end.to_hash 313 | end 314 | 315 | it 'should be able to send text' do 316 | expect(subject.new(client).create_from_hash(hash)).not_to be_nil 317 | end 318 | 319 | end 320 | 321 | end 322 | -------------------------------------------------------------------------------- /spec/message_quickly/api/thread_settings_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe MessageQuickly::Api::ThreadSettings do 4 | 5 | subject { MessageQuickly::Api::ThreadSettings } 6 | 7 | describe '#greeting' do 8 | 9 | let(:text) { 'Hello' } 10 | 11 | context 'with own client' do 12 | let(:client) { MessageQuickly::Api::Client.new(page_access_token: ENV['FACEBOOK_MESSENGER_PAGE_ACCESS_TOKEN'], page_id: ENV['FACEBOOK_MESSENGER_PAGE_ID']) } 13 | it { expect(subject.new(client).greeting(text)).to eq(true) } 14 | end 15 | 16 | context 'with valid params' do 17 | it { expect(subject.greeting(text)).to eq(true) } 18 | end 19 | 20 | context 'with invalid params' do 21 | before { stub_const('ENV', ENV.to_hash.merge('FACEBOOK_MESSENGER_PAGE_ID' => 'invalid')) } 22 | it { expect { subject.greeting(text) }.to raise_exception(MessageQuickly::Api::OauthException) } 23 | end 24 | 25 | end 26 | 27 | describe '#get_started_button' do 28 | 29 | let(:payload) { 'Payload 1' } 30 | 31 | context 'with own client' do 32 | let(:client) { MessageQuickly::Api::Client.new(page_access_token: ENV['FACEBOOK_MESSENGER_PAGE_ACCESS_TOKEN'], page_id: ENV['FACEBOOK_MESSENGER_PAGE_ID']) } 33 | it { expect(subject.new(client).get_started_button(payload)).to eq(true) } 34 | end 35 | 36 | context 'with valid params' do 37 | it { expect(subject.get_started_button(payload)).to eq(true) } 38 | end 39 | 40 | context 'with invalid params' do 41 | before { stub_const('ENV', ENV.to_hash.merge('FACEBOOK_MESSENGER_PAGE_ID' => 'invalid')) } 42 | it { expect { subject.get_started_button(payload) }.to raise_exception(MessageQuickly::Api::OauthException) } 43 | end 44 | 45 | end 46 | 47 | describe '#remove_get_started_button' do 48 | 49 | context 'with own client' do 50 | let(:client) { MessageQuickly::Api::Client.new(page_access_token: ENV['FACEBOOK_MESSENGER_PAGE_ACCESS_TOKEN'], page_id: ENV['FACEBOOK_MESSENGER_PAGE_ID']) } 51 | it { expect(subject.new(client).remove_get_started_button).to eq(true) } 52 | end 53 | 54 | context 'with valid params' do 55 | it { expect(subject.remove_get_started_button).to eq(true) } 56 | end 57 | 58 | context 'with invalid params' do 59 | before { stub_const('ENV', ENV.to_hash.merge('FACEBOOK_MESSENGER_PAGE_ID' => 'invalid')) } 60 | it { expect { subject.remove_get_started_button }.to raise_exception(MessageQuickly::Api::OauthException) } 61 | end 62 | 63 | end 64 | 65 | describe '#persistent_menu' do 66 | 67 | let(:payloads) { [ 68 | { type: 'postback', title: 'Help', payload: 'DEVELOPER_DEFINED_PAYLOAD_FOR_HELP' }, 69 | { type: 'postback', title: 'Start a New Order', payload: 'DEVELOPER_DEFINED_PAYLOAD_FOR_START_ORDER' }, 70 | { type: 'web_url', title: 'View Website', url: 'http://petersapparel.parseapp.com/' } 71 | ] } 72 | 73 | context 'with own client' do 74 | let(:client) { MessageQuickly::Api::Client.new(page_access_token: ENV['FACEBOOK_MESSENGER_PAGE_ACCESS_TOKEN'], page_id: ENV['FACEBOOK_MESSENGER_PAGE_ID']) } 75 | it { expect(subject.new(client).persistent_menu(payloads)).to eq(true) } 76 | end 77 | 78 | context 'with valid params' do 79 | it { expect(subject.persistent_menu(payloads)).to eq(true) } 80 | end 81 | 82 | context 'with invalid params' do 83 | before { stub_const('ENV', ENV.to_hash.merge('FACEBOOK_MESSENGER_PAGE_ID' => 'invalid')) } 84 | it { expect { subject.persistent_menu(payloads) }.to raise_exception(MessageQuickly::Api::OauthException) } 85 | end 86 | 87 | end 88 | 89 | describe '#remove_persistent_menu' do 90 | 91 | context 'with own client' do 92 | let(:client) { MessageQuickly::Api::Client.new(page_access_token: ENV['FACEBOOK_MESSENGER_PAGE_ACCESS_TOKEN'], page_id: ENV['FACEBOOK_MESSENGER_PAGE_ID']) } 93 | it { expect(subject.new(client).remove_persistent_menu).to eq(true) } 94 | end 95 | 96 | context 'with valid params' do 97 | it { expect(subject.remove_persistent_menu).to eq(true) } 98 | end 99 | 100 | context 'with invalid params' do 101 | before { stub_const('ENV', ENV.to_hash.merge('FACEBOOK_MESSENGER_PAGE_ID' => 'invalid')) } 102 | it { expect { subject.remove_persistent_menu }.to raise_exception(MessageQuickly::Api::OauthException) } 103 | end 104 | 105 | end 106 | 107 | end 108 | -------------------------------------------------------------------------------- /spec/message_quickly/api/user_profile_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe MessageQuickly::Api::UserProfile do 4 | 5 | subject { MessageQuickly::Api::UserProfile } 6 | 7 | describe '#find' do 8 | 9 | let(:fbid) { ENV['FACEBOOK_MESSENGER_USER_ID'] } 10 | let(:fbid_invalid) { '091283908123' } 11 | 12 | context 'with own client' do 13 | let(:client) { MessageQuickly::Api::Client.new(page_access_token: ENV['FACEBOOK_MESSENGER_PAGE_ACCESS_TOKEN'], page_id: ENV['FACEBOOK_MESSENGER_PAGE_ID']) } 14 | it { expect(subject.new(client).find(fbid)).to be_kind_of(MessageQuickly::Messaging::User) } 15 | end 16 | 17 | context 'with matching user' do 18 | it { expect(subject.find(fbid)).to be_kind_of(MessageQuickly::Messaging::User) } 19 | it { expect(subject.find(fbid).first_name).to eq(ENV['FACEBOOK_MESSENGER_USER_FIRST_NAME']) } 20 | it { expect(subject.find(fbid).last_name).to eq(ENV['FACEBOOK_MESSENGER_USER_LAST_NAME']) } 21 | end 22 | 23 | context 'with no matching user' do 24 | it { expect { subject.find(fbid_invalid) }.to raise_exception(MessageQuickly::Api::GraphMethodException) } 25 | end 26 | 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /spec/message_quickly/messaging/user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe MessageQuickly::Messaging::User do 4 | 5 | subject { MessageQuickly::Messaging::User.new(id: ENV['FACEBOOK_MESSENGER_USER_ID']) } 6 | 7 | it { expect(subject.first_name).to eq(ENV['FACEBOOK_MESSENGER_USER_FIRST_NAME']) } 8 | it { expect(subject.last_name).to eq(ENV['FACEBOOK_MESSENGER_USER_LAST_NAME']) } 9 | 10 | end 11 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "codeclimate-test-reporter" 2 | CodeClimate::TestReporter.start 3 | 4 | ENV['RAILS_ENV'] ||= 'test' 5 | 6 | require File.expand_path("../dummy/config/environment.rb", __FILE__) 7 | require 'rspec/rails' 8 | 9 | Rails.backtrace_cleaner.remove_silencers! 10 | 11 | require 'dotenv' 12 | Dotenv.load 13 | 14 | require 'pry' 15 | 16 | # Load support files 17 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } 18 | 19 | RSpec.configure do |config| 20 | config.mock_with :rspec 21 | config.use_transactional_fixtures = true 22 | config.infer_base_class_for_anonymous_controllers = false 23 | config.order = "random" 24 | end 25 | --------------------------------------------------------------------------------