Деджаваскриптизиция (4)
Пора заканчивать эпопею про избавление от Js. Чтобы не утомлять, расскажу о последнем штрихе — как внедрил капчу для комментариев.
Как только я убрал Disqus, полезли спамные комментарии. Каждый день приходят два-три предложения купить виагру, надувную лодку или просто левые ссылки. Поскольку каждый комментарий открывает PR в репозиторий, все остается в истории Гитхаба. Посмотреть на это добро можно по ссылке.
Разгребать подобные комментарии нет желания, поэтому должна быть минимальная защита от спама. С условием — без Js. Надумал такую схему:
- капча генерируется на этапе сборки блога. На выходе получается HTML-форма с полем captcha и значением 2 × 5.
- В форму добавляется поле для решения.
- Сервер парсит капчу, решает и сверяет с ответом. Если что-то не так, заворачивает комментарий.
Как ни странно, даже на таком примитиве боты отваливаются. Разве что с оговоркой: когда был оператор +, боты решали капчу. Как только заменил на × (знак умножения в юникоде), стала тишь да благодать. Надеюсь, читатель не забыл таблицу умножения! Тестируя форму, сам подвис с примером 8 × 9.
Техническая сторона: вот построить капчу в шаблоне:
{% assign val1 = '1 2 3 4 5 6 7 8 9' | split: ' ' | sample %}
{% assign val2 = '1 2 3 4 5 6 7 8 9' | split: ' ' | sample %}
{% assign op   = '×'            | split: ' ' | sample %}
{% assign captcha = val1 | append: " " | append: op | append: " " | append: val2 %}
Замечу, что при каждой сборке блога значения будут разные.
Скрытое поле в форме:
<input required name="captcha" type="hidden" value="{{ captcha }}">
Виджет для ввода решения:
<div class="block">
    <span class="comment-form-label"><small>{{ captcha }} = </small></span>
    <input required id="comment-form-solution" name="solution" type="text">
</div>
Наконец, серверный код проверки капчи:
(dеfn validate-captcha [captcha solution]
  (when-let [[_ val1-raw op-raw val2-raw]
             (re-find #"^(-?\d+) (.+?) (-?\d+)$" captcha)]
    (let [val1
          (Integer/parseInt val1-raw)
          val2
          (Integer/parseInt val2-raw)
          op
          (case op-raw
            ("+" "+") +
            ("*" "×" "×") *
            nil)]
      (when (and val1 val2 op)
        (= (str (op val1 val2))
           (str/trim solution))))))
Грубо, неуклюже, но работает.
На этом я закончу тему с избавлением от Js. На мой взгляд, цели достигнуты, жить с новыми комментариями можно. Это был интересный опыт, в будущем пригодится.
Нашли ошибку? Выделите мышкой и нажмите Ctrl/⌘+Enter
 
            
Internet User, 4th Oct 2022, link
Чтож, неплохо!
P.S. диагностировал у себя некоторые проблемы с таблицей умножения
Alex, 5th Oct 2022, link
#Супер комент
Прикольно что можно использовать MD