0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

BloggerにGoogleフォーム×reCAPTCHAでお問い合わせフォームを作成する

Posted at

Bloggerで連絡フォームウィジェットがエラーで送信できなかったのでGoogleフォームを使ってカスタムフォームを実装したはなし。

お問い合わせページ

image.png

連絡フォームウィジェットのエラー原因

フォーム送信先のエンドポイントhttps://www.blogger.com/contact-form.doがオリジンhttps://blog.bigbridge.work対して、Access-Control-Allow-Origin許可してくれないためCORSエラーが発生していた。

image 2.png

image 4.png

リクエスト

# 概要
リクエスト URL:https://www.blogger.com/contact-form.do
リクエスト メソッド:POST
ステータス コード:200 OK
参照ポリシー:strict-origin-when-cross-origin

# リクエストヘッダ
:authority:www.blogger.com
:method:POST
:path:/contact-form.do
:scheme:https
Accept:*/*
Accept-Encoding:gzip, deflate, br, zstd
Accept-Language:ja,en-US;q=0.9,en;q=0.8
Content-Length:170
Content-Type:application/x-www-form-urlencoded;charset=UTF-8
Origin:https://blog.bigbridge.work
Referer:https://blog.bigbridge.work/
Sec-Ch-Ua:"Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"
Sec-Ch-Ua-Mobile:?0
Sec-Ch-Ua-Platform:"macOS"
Sec-Fetch-Dest:empty
Sec-Fetch-Mode:cors
Sec-Fetch-Site:cross-site
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36

# ペイロード部
## フォーム
name: テスト
email: test@bigbridge.work
message: テスト
## トークン
blogID: ******************************
token: AOuZoY6iKedo_5ezKrVuDX5Zx7eK_7l4xg:1711659541683

レスポンス

Access-Control-Allow-Originヘッダがない

# レスポンスヘッダ
Alt-Svc:h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
Cache-Control:no-cache, no-store, max-age=0, must-revalidate
Content-Encoding:gzip
Content-Length:70
Content-Security-Policy:script-src 'self' *.google.com *.google-analytics.com 'unsafe-inline' 'unsafe-eval' *.gstatic.com *.googlesyndication.com *.blogger.com *.googleapis.com uds.googleusercontent.com https://s.ytimg.com https://i18n-cloud.appspot.com https://www.youtube.com www-onepick-opensocial.googleusercontent.com www-bloggervideo-opensocial.googleusercontent.com www-blogger-opensocial.googleusercontent.com https://www.blogblog.com; report-uri /cspreport
Content-Type:text/javascript; charset=UTF-8
Date:Thu, 28 Mar 2024 20:59:49 GMT
Expires:Mon, 01 Jan 1990 00:00:00 GMT
P3p:CP="This is not a P3P policy! See https://www.google.com/support/accounts/bin/answer.py?hl=en&answer=151657 for more info."
Pragma:no-cache
Server:GSE
X-Content-Type-Options:nosniff
X-Frame-Options:SAMEORIGIN
X-Xss-Protection:1; mode=block

Bloggerフォーラムに送信トークンについて仕様変更を示唆するコメントがあった。Contact us Stopped Working - Blogger Community

一応リクエストのペイロードにはトークンらしきものは添加されているが、そもそも使用テーマWrite Simpleの公開時期が2016年とかなり古いため現在の要件を満たしていないのかもしれない。使用テーマは変えたくなかったのでGoogleフォームを利用してフォームを実装する。

お問い合わせフォームの要件

お問い合わせに求める要件は次の通り

  • お問い合わせを受信出来て、履歴を一元的に管理できること
  • フォームをスタリングできること
  • reCAPTCHAでボット対策できること
  • 入力内容に対してバリデーションできること
  • 送信完了後は完了画面にフォワードできること

お問い合わせを受信出来て、履歴を一元的に管理できること

Google formコンソール
お問い合わせのスプレッドシート

お問い合わせはGoogleフォームで受信し、管理する。すべての受信履歴はスプレッドシートにスタックされる。

image 8.png

回答タブでメール通知はONにしておく

image 9.png

フォームをスタリングできること

Googleフォームをサイトにインラインフレームで組み込むと、入力から完了までGoogleフォームまるだしだ。これはいかん。是が非でもサイトのスタイリングに統一したい。

image 6.png

image 7.png

Googleフォームのaction属性とフォームパーツのname属性を拝借して、自前で組んだフォームにそれぞれ充てる。

image 10.png

こんな感じで、GoogleフォームにPOSTが出来る。
(が、送信後Googleフォームの送信完了画面にリダイレクトするため、自前の完了画面に遷移する実装を後述)

html
<form action="https://docs.google.com/forms/u/0/d/e/1FAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/formResponse" method="POST">
    <div>
        <div><span>お名前</span></div>
        <div><input type="text" name="entry.2XXXXXXXX"></div>
    </div>
    <div>
        <div><span>メールアドレス</span></div>
        <div><input type="email" name="entry.3XXXXXXXX"></div>
    </div>
    <div>
        <div><span>お問い合わせ内容</span></div>
        <div><textarea name="entry.1XXXXXXXX" rows="10"></textarea></div>
    </div>
    <div>
		<input type="submit" value="送信">
    </div>
</form>

reCAPTCHAでボット対策できること

reCAPTCHAコンソール

reCAPTCHA-V2を設置して、reCAPTCHA検証イベントで送信ボタンを活性状態に切り替える。
フォームの送信タイミングでバリデーションなど追加実装を割り込ませるためにsubmitボタンをbuttonに変更して初期状態はdisabled

html
<script src="https://www.google.com/recaptcha/api.js"></script>
<form action="https://docs.google.com/forms/u/0/d/e/1FAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/formResponse" method="POST">
	...
	<!-- それらしく送信ボタンの前にreCAPTCHAを組み込む -->
	<div class="g-recaptcha" data-callback="gValidatedCallback" data-sitekey="6XXXXXXXXXXXXXXX_FXXXXXXXXXXXXXXXXXXXXXX" disabled></div>
	<!-- 送信ボタン 初期状態は非活性-->
    <div>
		<button id="id-contact-form-submit" class="submit" disabled>送信</button>
    </div>
</form>

reCAPTCHA検証イベントで送信ボタンを活性状態にスイッチする。disabledフラグだけではhtml改ざんが出来るのでJSで追加検証のフラグも実装する。

javascript
/************************************************
 * reCAPTCHA検証のコールバック
 * コードがあれば=判定通過で、ボタンのdisableを解除して通過フラグを立てる
 ************************************************/
let gValidated = false;
function gValidatedCallback(code) {
    if(code !== ''){
  		$('#id-contact-form-submit').removeAttr("disabled");
		gValidated = true;
	}
}

/************************************************
 * 送信ボタンのコールバック
 * - reCAPTCHA検証フラグを追加検証する
 ************************************************/
$(document).on('click', '#id-contact-form-submit', function(){  
	try{
        if( !gValidated ) throw new Error('reCAPTCHA検証を完了してください。');
	}catch(error){
    	alert(error.message);
	}
	return false;
});

送信完了後は完了画面にフォワードできること

GoogleフォームのactionにPOSTするとGoogleフォーム完了画面にリダイレクトしてしまうので、formのtarget属性にダミーの非表示インラインフレームを指定してGoogleフォームのレスポンスを葬りつつ、ロケーションを送信完了ページに移動する。

html
<form id="id-contact-form" action="https://docs.google.com/forms/u/0/d/e/1FAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/formResponse" method="post" target="hidden_iframe">
	...
</form>
<iframe name="hidden_iframe" style="display:none;"></iframe>
javascript
...
/************************************************
* 送信ボタンのコールバック
* - reCAPTCHA検証フラグを追加検証する
* - Googleフォームに送信する
* - ロケーションを完了画面に移動する
 ************************************************/
$(document).on('click', '#id-contact-form-submit', function(e){
	try{
		// バリデーション
        if( !gValidated ) throw new Error('reCAPTCHA検証を完了してください。');

		// フォーム送信 => レスポンスは"target="hidden_iframe"に葬られる
		$('#id-contact-form').submit();
		// ロケーションを完了画面に移動する
		window.location='/p/been-contact.html';      
	}catch(error){
    	alert(error.message);
	}
	return false;
});

入力内容に対してバリデーションできること

Googleフォームの送信結果はインラインフレームに葬るため、送信結果を受け取ることが出来ないのでフロントでフォーム入力値のバリデーションを実装する。
※なおajaxによるフォーム送信ではCORSエラーである

javascript
/************************************************
 * メールアドレスのフォーマット検証
 * @return bool true: 合格 / false: 不合格
 ************************************************/
function isValidEmail(email) {
  const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
  return emailRegex.test(email);
}

/************************************************
 * 送信ボタンのコールバック
 * - reCAPTCHA検証フラグを追加検証する
 * - フォーム入力値を検証する
 * - Googleフォームに送信する
 * - ロケーションを完了画面に移動する
 ************************************************/
$(document).on('click', '#id-contact-form-submit', function(e){
    
	try{
		// バリデーション
        if( !gValidated ) throw new Error('reCAPTCHA検証を完了してください。');

		// 必須フォームフィールド
		const nm_name	= 'entry.2XXXXXXXX';
		const nm_email	= 'entry.3XXXXXXXX';
		const nm_body	= 'entry.1XXXXXXXX';

		if( !$(`input[name="${nm_name}"]`).val() ) throw new Error('お名前を入力してください');
		if( !$(`input[name="${nm_email}"]`).val() ) throw new Error('メールアドレスを入力してください');
		if( !isValidEmail( $(`input[name="${nm_email}"]`).val() ) ) throw new Error('メールアドレスのフォーマットが不正です');
		if( !$(`textarea[name="${nm_body}"]`).val() ) throw new Error('お問い合わせ内容を入力してください');
            
		// フォーム送信
		$('#id-contact-form').submit();
		// ロケーション移動
		window.location='/p/been-contact.html';
      
	}catch(error){
    	alert(error.message);
	}
	return false;
});

結果

要件を満たした最終的なお問い合わせページの実装

html
<script src="https://www.google.com/recaptcha/api.js"></script>
<script>
let gValidated = false;
/************************************************
 * reCAPTCHA検証のコールバック
 * コードがあれば=判定通過で、ボタンのdisableを解除して通過フラグを立てる
 ************************************************/
function gValidatedCallback(code) {
    if(code !== ''){
  		$('#id-contact-form-submit').removeAttr("disabled");
		gValidated = true;
	}
}

/************************************************
 * メールアドレスのフォーマット検証
 * @return bool true: 合格 / false: 不合格
 ************************************************/
function isValidEmail(email) {
  const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
  return emailRegex.test(email);
}

/************************************************
 * 送信ボタンのコールバック
 * - reCAPTCHA検証フラグを追加検証する
 * - フォーム入力値を検証する
 * - Googleフォームに送信する
 * - ロケーションを完了画面に移動する
 ************************************************/
$(document).on('click', '#id-contact-form-submit', function(e){
	try{      
        if( !gValidated ) throw new Error('reCAPTCHA検証を完了してください。');

		const nm_name = 'entry.2XXXXXXXX';
		const nm_email = 'entry.3XXXXXXXX';
		const nm_body = 'entry.1XXXXXXXX';
		if( !$(`input[name="${nm_name}"]`).val() ) throw new Error('お名前を入力してください');
		if( !$(`input[name="${nm_email}"]`).val() ) throw new Error('メールアドレスを入力してください');
		if( !isValidEmail( $(`input[name="${nm_email}"]`).val() ) ) throw new Error('メールアドレスのフォーマットが不正です');
		if( !$(`textarea[name="${nm_body}"]`).val() ) throw new Error('お問い合わせ内容を入力してください');
      
		$('#id-contact-form').submit();
		window.location='/p/been-contact.html';
	}catch(error){
    	alert(error.message);
	}
	return false;
});
</script>

<style>
  
  .formgroup{}
  .formgroup:not(last-child){
    margin-bottom: 2em;
  }
  .formgroup__label{
    color: #000;
  }
  .formgroup__form > * {
    	width: 100%;
  }
  .formgroup .require{
  		font-size: 0.8em;
    	color: #f00;
  }
  .formgroup .require:before{
    	margin-left: 0.8em;
    	content: '*';
  }
  
  button {
  		text-shadow: none;
  }
    
  .submit{
    	color: #fff;
  		background-color: #000;
  }
  .submit:hover{
    	color: #000;
  		background-color: #fff;
  }
  .submit:disabled{
    	color: #000;
  		background-color: #ccc;
  }
  
  .g-recaptcha{
		margin: 1em 0;
  }

</style>

<p>フォームにお問い合わせ内容を入力して送信してください。</p>

<form id="id-contact-form" action="https://docs.google.com/forms/u/0/d/e/1FAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/formResponse" method="post" target="hidden_iframe">
	<div>
    	<div class="formgroup">
        	<div class="formgroup__label">
          		<span>お名前</span>
          	</div>
        	<div class="formgroup__form">
            	<input type="text" name="entry.2XXXXXXXX" >
          	</div>
    	</div>
    	<div class="formgroup">
        	<div class="formgroup__label">
              	<span>メールアドレス</span>
          	</div>
        	<div class="formgroup__form">
              	<input type="email" name="entry.3XXXXXXXX" >
        	</div>
    	</div>
    	<div class="formgroup">
        	<div class="formgroup__label">
              	<span>お問い合わせ内容</span>
          	</div>
        	<div class="formgroup__form">
              	<textarea name="entry.1XXXXXXXX" rows="10"></textarea>
          	</div>
    	</div>
  	</div>
	<div class="g-recaptcha" data-callback="gValidatedCallback" data-sitekey="6XXXXXXXXXXXXXXX_FXXXXXXXXXXXXXXXXXXXXXX" disabled></div>
	<button id="id-contact-form-submit" class="submit" disabled>送信</button>
</form>
<iframe name="hidden_iframe" style="display:none;"></iframe>
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?