{"id":16882432,"url":"https://github.com/matthutchinson/acts_as_textcaptcha","last_synced_at":"2025-07-27T04:37:08.505Z","repository":{"id":849084,"uuid":"577775","full_name":"matthutchinson/acts_as_textcaptcha","owner":"matthutchinson","description":"Text-based logic question captcha's for Rails 🚫🤖","archived":false,"fork":false,"pushed_at":"2025-01-01T00:19:02.000Z","size":449,"stargazers_count":56,"open_issues_count":1,"forks_count":12,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-06-24T10:49:58.162Z","etag":null,"topics":["activerecord","captcha","captcha-alternative","captcha-generator","rails","ruby","spam-protection"],"latest_commit_sha":null,"homepage":"https://acts-as-textcaptcha.hiddenloop.dev","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"doorkeeper-gem/doorkeeper","license":"lgpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/matthutchinson.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2010-03-24T17:32:08.000Z","updated_at":"2025-04-09T01:52:00.000Z","dependencies_parsed_at":"2024-10-26T21:17:32.317Z","dependency_job_id":"fe3087aa-91a9-4bbd-93e2-a4f78ccfd471","html_url":"https://github.com/matthutchinson/acts_as_textcaptcha","commit_stats":{"total_commits":333,"total_committers":5,"mean_commits":66.6,"dds":0.06906906906906907,"last_synced_commit":"1ff9e00042ecc0c98871712fc67e159f32d830ca"},"previous_names":[],"tags_count":40,"template":false,"template_full_name":null,"purl":"pkg:github/matthutchinson/acts_as_textcaptcha","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matthutchinson%2Facts_as_textcaptcha","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matthutchinson%2Facts_as_textcaptcha/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matthutchinson%2Facts_as_textcaptcha/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matthutchinson%2Facts_as_textcaptcha/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/matthutchinson","download_url":"https://codeload.github.com/matthutchinson/acts_as_textcaptcha/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matthutchinson%2Facts_as_textcaptcha/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267300061,"owners_count":24066097,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-27T02:00:11.917Z","response_time":82,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["activerecord","captcha","captcha-alternative","captcha-generator","rails","ruby","spam-protection"],"created_at":"2024-10-13T16:07:13.708Z","updated_at":"2025-07-27T04:37:08.452Z","avatar_url":"https://github.com/matthutchinson.png","language":"Ruby","funding_links":[],"categories":["Captchas and anti-spam"],"sub_categories":[],"readme":"## ActAsTextcaptcha\n\n[![CI](https://img.shields.io/github/actions/workflow/status/matthutchinson/acts_as_textcaptcha/ci.yml?branch=main\u0026style=flat)](https://github.com/matthutchinson/acts_as_textcaptcha/actions/workflows/ci.yml)\n[![Gem](https://img.shields.io/gem/v/acts_as_textcaptcha.svg?style=flat)](http://rubygems.org/gems/acts_as_textcaptcha)\n[![Depfu](https://img.shields.io/depfu/matthutchinson/acts_as_textcaptcha.svg?style=flat)](https://depfu.com/github/matthutchinson/acts_as_textcaptcha)\n\nActsAsTextcaptcha provides spam protection for Rails models with text-based\nlogic question captchas. Questions are fetched from [Rob\nTuley's](https://twitter.com/robtuley)\n[textcaptcha.com](https://textcaptcha.com/). They can be solved easily by humans\nbut are tough for robots to crack.\n\nThe gem can also be configured with your own questions; as an alternative, or as\na fallback to handle any API issues. For reasons on why logic based captchas\nare a good idea visit [textcaptcha.com](https://textcaptcha.com).\n\n## Requirements\n\n* [Ruby](http://ruby-lang.org/) \u003e= 3.1\n* [Rails](http://github.com/rails/rails) \u003e= 6.1\n* A valid [Rails.cache](http://guides.rubyonrails.org/caching_with_rails.html#cache-stores) (not `:null_store`)\n\n## Demo\n\nTry a [working demo here](https://acts-as-textcaptcha.hiddenloop.dev)!\n\n## Installation\n\nAdd this line to your Gemfile and run `bundle install`:\n\n```ruby\ngem 'acts_as_textcaptcha'\n```\n\nAdd this to models you'd like to protect:\n\n```ruby\nclass Comment \u003c ApplicationRecord\n  acts_as_textcaptcha api_key: 'TEXTCAPTCHA_API_IDENTITY'\n  # see below for more config options\nend\n```\n\n[Rob](https://twitter.com/robtuley) requests that your\n`TEXTCAPTCHA_API_IDENTITY` be some reference to yourself (e.g. an email address,\ndomain or similar) so you can be contacted should any usage problem occur.\n\nIn your controller's `new` action call the `textcaptcha` method:\n\n```ruby\ndef new\n  @comment = Comment.new\n  @comment.textcaptcha   \nend\n```\n\nMake sure these textcaptcha params\n(`:textcaptcha_answer, :textcaptcha_key`) are permitted in your create (or update) action:\n\n```ruby\ndef create\n  @comment = Comment.create(commment_params)\n  # ... etc\nend\n\nprivate\n\ndef commment_params\n  params.require(:comment).permit(:textcaptcha_answer, :textcaptcha_key, :name, :comment_text)\nend\n```\n\n**NOTE**: if the captcha is submitted incorrectly, a new captcha will be\nautomatically generated on the `@comment` object.\n\nNext, add the question and answer fields to your form using the\n`textcaptcha_fields` helper. Arrange the HTML within this block as you like.\n\n```ruby\n\u003c%= textcaptcha_fields(f) do %\u003e\n  \u003cdiv class=\"field\"\u003e\n    \u003c%= f.label :textcaptcha_answer, @comment.textcaptcha_question %\u003e\u003cbr/\u003e\n    \u003c%= f.text_field :textcaptcha_answer, :value =\u003e '' %\u003e\n  \u003c/div\u003e\n\u003c% end %\u003e\n```\n\nIf you'd prefer to construct your own form elements, take a look at the HTML\nproduced\n[here](https://github.com/matthutchinson/acts_as_textcaptcha/blob/master/lib/acts_as_textcaptcha/textcaptcha_helper.rb).\n\nFinally set a valid [cache\nstore](https://guides.rubyonrails.org/caching_with_rails.html#cache-stores) (not `:null_store`) for your environments:\n\n```ruby\n# e.g. in config/environments/*.rb\nconfig.cache_store = :memory_store\n```\n\nYou can run `rails dev:cache` on to enable a memory store cache in the development environment.\n\n## Configuration\n\nThe following options are available (only `api_key` is required):\n\n* **api_key** (_required_) - a reference to yourself (e.g. your email or domain).\n* **questions** (_optional_) - array of your own questions and answers (see below).\n* **cache_expiry_minutes** (_optional_) - how long valid answers are cached for (default 10 minutes).\n* **raise_errors** (_optional_) - if true, API or network errors are raised (default false, errors logged).\n* **api_endpoint** (_optional_) - set your own JSON API endpoint to fetch from (see below).\n\nFor example:\n\n```ruby\nclass Comment \u003c ApplicationRecord\n  acts_as_textcaptcha api_key: 'TEXTCAPTCHA_API_IDENTITY_KEY',\n                      raise_errors: false,\n                      cache_expiry_minutes: 10,\n                      questions: [\n                        { 'question' =\u003e '1+1', 'answers' =\u003e '2,two' },\n                        { 'question' =\u003e 'The green hat is what color?', 'answers' =\u003e 'green' }\n                      ]\nend\n```\n\n### YAML config\n\nYou can apply an app wide config with a `config/textcaptcha.yml` file. Use this\nrake task to add one from a\n[template](https://github.com/matthutchinson/acts_as_textcaptcha/blob/master/lib/acts_as_textcaptcha/textcaptcha_config.rb):\n\n    $ bundle exec rake textcaptcha:config\n\n**NOTE**: Any options set in models take preference over this config.\n\n### Config without the TextCaptcha service\n\nTo use __only__ your own logic questions, omit the `api_key` and set them in the\nconfig (see above). Multiple answers to the same question must be comma\nseparated e.g. `2,two` (so do not include commas in answers).\n\nYou can optionally set your own `api_endpoint` to fetch questions and answers\nfrom. The URL must respond with a JSON object like this:\n\n```ruby\n{\n  \"q\": \"What number is 4th in the series 39, 11, 31 and nineteen?\",\n  \"a\": [\"1f0e3dad99908345f7439f8ffabdffc4\",\"1d56cec552bf111de57687e4b5f8c795\"]\n}\n```\n\nWith `\"a\"` being an array of answer strings, MD5'd, and lower-cased. The\n`api_key` option is ignored if an `api_endpoint` is set.\n\n### Toggling TextCaptcha\n\nEnable or disable captchas by overriding the `perform_textcaptcha?` method (in\nmodels). By default the method checks if the object is a new (unsaved) record.\nSo spam protection is __only__ enabled for creating new records (not updating).\n\nHere's an example overriding the default behaviour but maintaining the new\nrecord check.\n\n```ruby\nclass Comment \u003c ApplicationRecord\n  acts_as_textcaptcha :api_key =\u003e 'TEXTCAPTCHA_API_IDENTITY'\n\n  def perform_textcaptcha?\n    super \u0026\u0026 user.admin?\n  end\nend\n```\n\n## Translations\n\nThe following strings are translatable (with Rails I18n translations):\n\n```yaml\nen:\n  activerecord:\n    errors:\n      models:\n        comment:\n          attributes:\n            textcaptcha_answer:\n              incorrect: \"is incorrect, try another question instead\"\n              expired: \"was not submitted quickly enough, try another question instead\"\n  activemodel:\n    attributes:\n      comment:\n        textcaptcha_answer: \"TextCaptcha answer\"\n```\n\n**NOTE**: The textcaptcha.com API only provides logic questions in English.\n\n## Handling Errors\n\nThe API may be unresponsive or return an unexpected response. If you've set\n`raise_errors: true`, consider handling these errors:\n\n* `ActsAsTextcaptcha::ResponseError`\n* `ActsAsTextcaptcha::ParseError`\n* `ActsAsTextcaptcha::ApiKeyError`\n\n## Development\n\nCheck out this repo and run `bin/setup`, this will install gem dependencies and\ngenerate docs. Use `bundle exec rake` to run tests.\n\nYou can also run `bin/console` for an interactive prompt to experiment with the\ncode.\n\n## Tests\n\nMiniTest is used for testing. Run the test suite with:\n\n    $ rake test\n\nThis gem uses [appraisal](https://github.com/thoughtbot/appraisal) to test\nagainst multiple versions of Rails.\n\n* `appraisal rake test` (run tests against all Gemfile variations)\n* `appraisal rails-3 rake test` (run tests against a specific Gemfile)\n\n## Docs\n\nGenerate docs for this gem with:\n\n    $ rake rdoc\n\n## Troubles?\n\nIf you think something is broken or missing, please raise a new\n[issue](https://github.com/matthutchinson/acts_as_textcaptcha/issues). Please\nremember to check it hasn't already been raised.\n\n## Contributing\n\nBug [reports](https://github.com/matthutchinson/acts_as_textcaptcha/issues) and\n[pull requests](https://github.com/matthutchinson/acts_as_textcaptcha/pulls) are\nwelcome on GitHub. When submitting pull requests, remember to add tests covering\nany new behaviour, and ensure all tests are passing. Read the [contributing\nguidelines](https://github.com/matthutchinson/acts_as_textcaptcha/blob/master/CONTRIBUTING.md)\nfor more details.\n\nThis project is intended to be a safe, welcoming space for collaboration, and\ncontributors are expected to adhere to the [Contributor\nCovenant](http://contributor-covenant.org) code of conduct. See\n[here](https://github.com/matthutchinson/acts_as_textcaptcha/blob/master/CODE_OF_CONDUCT.md)\nfor more details.\n\n## Ideas\n\n* Check if AI models can beat this approach\n* Allow translatable user supplied questions and answers in config\n* Allow `Net::HTTP` to be swapped out for any HTTP client.\n\n## License\n\nThe code is available as open source under the terms of\n[LGPL-3](https://opensource.org/licenses/LGPL-3.0).\n\n## Who's who?\n\n* [ActsAsTextcaptcha](http://github.com/matthutchinson/acts_as_textcaptcha) and [little robot drawing](http://www.flickr.com/photos/hiddenloop/4541195635/) by [Matthew Hutchinson](http://matthewhutchinson.net)\n* [TextCaptcha](https://textcaptcha.com) API and service by [Rob Tuley](https://twitter.com/robtuley)\n\n## Links\n\n* [Demo](https://acts-as-textcaptcha.hiddenloop.dev)\n* [Gem](http://rubygems.org/gems/acts_as_textcaptcha)\n* [GitHub](http://github.com/matthutchinson/acts_as_textcaptcha)\n* [CI](https://github.com/matthutchinson/acts_as_textcaptcha/actions/workflows/ci.yml)\n* [RDoc](http://rdoc.info/projects/matthutchinson/acts_as_textcaptcha)\n* [Issues](http://github.com/matthutchinson/acts_as_textcaptcha/issues)\n* [Report a bug](http://github.com/matthutchinson/acts_as_textcaptcha/issues/new)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmatthutchinson%2Facts_as_textcaptcha","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmatthutchinson%2Facts_as_textcaptcha","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmatthutchinson%2Facts_as_textcaptcha/lists"}