ryoma's note

マイペース ੯•́ʔ̋ ͙͛*͛ ͙͛*͛ ͙͛̋و

GAS で特定の列の最終行の値を取得する

GAS でスプレッドシートの特定の列の最終行にある値を取得したかった。

getNextDataCell を利用すると連続するセルの端を取得できるようで、SpreadsheetApp.Direction.DOWN を引数に渡すと下方向の行インデックスを返してくれる。

// 特定の列(A列)
const columnIndex = 1;

// 特定の列の最終行の行インデックスを取得する
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('シート1');
const lastLineIndex = sheet.getRange(1, columnIndex).getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow();

// 特定の列の最終行の値を取得する
const lastLineValue = sheet.getRange(lastLineIndex, columnIndex).getValue();

注意点として、対象の列に1行しか値が入っていなかったり、連続するセルの途中に空欄が含まれていると意図した行インデックスを返してくれない場合がある。

参考

MySQL 接続の TLS プロトコルを確認する

MySQL 接続の TLS プロトコルを確認する方法を調べた。

現在の接続の暗号化で使用している TLS プロトコルと暗号スイートを知りたかったので、Ssl_versionSsl_cipher のサーバーステータス変数の値を確認する。

mysql> SHOW SESSION STATUS LIKE 'Ssl_version';
+---------------+---------+
| Variable_name | Value   |
+---------------+---------+
| Ssl_version   | TLSv1.2 |
+---------------+---------+

mysql> SHOW SESSION STATUS LIKE 'Ssl_cipher';
+---------------+-----------------------------+
| Variable_name | Value                       |
+---------------+-----------------------------+
| Ssl_cipher    | ECDHE-RSA-AES128-GCM-SHA256 |
+---------------+-----------------------------+

サーバーがサポートする SSL 暗号のリストは Ssl_cipher_list のステータス変数で確認できる。

mysql> SHOW SESSION STATUS LIKE 'Ssl_cipher_list';
+-----------------+------------------------------------------------------------------+
| Variable_name   | Value                                                            |
+-----------------+------------------------------------------------------------------+
| Ssl_cipher_list | ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384: ... |
+-----------------+------------------------------------------------------------------+

Rails アプリケーションから接続している場合は、console で生 SQL を実行することで確認できた。

ActiveRecord::Base.connection.select_one("SHOW SESSION STATUS LIKE 'Ssl_version';")

参考

教育評価について調べていた

ひさしぶりに研修設計に関わる機会があり、企業内人材育成入門 の本に頼って再読をしていた。

教育評価の章を読んでいると、評価の手法と特徴が紹介されていた。

たとえばアンケートは、内面を広く捉えることができるものの深く捉えにくい特徴があるようで、利用する目的を間違えるとあまり効果が見込めないかもしれない。しかし、観察やインタビューなど複数の手法を組み合わせることで、実態をより明らかにすることができるようだ。

アンケートだけに頼るのではなく、研修生の日々のアウトプットや雑談の中からも気付けるように行動していかないとな〜と思った。

評価の対象については、学習者だけでなく教師や教育内容も含んだ、教育活動全体とする

研修生に対してだけでなく、講師や研修内容も対等に評価されることが大事だと教わることができた。今後の管理や運営が改善されたり、教育活動をよりよくすることにも価値を見出しながら取り組んでいこうと思う。

GAS で URL Fetch に失敗したときに再試行する

GAS の URL Fetch Service を利用して Web API に接続する処理を書いたところ、ときどき接続に失敗してデータを取得できない場合があった。

トリガーを利用して定期実行していたので、リクエストの内容に問題がないにも関わらずデータの取得に失敗したときは、自動的に再取得を試行してもらいたかった。

UrlFetchApp のドキュメントを眺めてみたところ、 getResponseCode() を利用すると HTTP ステータスコードを取得できるようなので、200 以外のときに数回再取得を試みてもらうようにした。2回目以降は Utilities.sleep() を利用して、数秒の間隔を空けてから実行してもらう。

function fetchApi() {
...
  // 200のステータスコードが返されるまで3回実行する
  for (let i=0; i<3; i++) {
    try {
      response = UrlFetchApp.fetch(endpoint, options);
    } catch(e) {
      Logger.log(e);
    }

    if (getStatusCode(response) === 200) {
      break;
    }

    if (i >= 2) {
      return;
    }
    Utilities.sleep(5000);
  }
  return JSON.parse(response);
}

function getStatusCode(response) {
  if (response) {
    return response.getResponseCode();
  } else {
    return 400;
  }
}

今のところデータを取得できない事象は再発していないので、引き続き様子をみたいと思います。

参考

GAS で特定のトリガーを削除する

GAS を触る機会があって、スクリプトから特定のトリガーを削除する方法について調べていた。

削除対象を Trigger クラスから区別する方法として、実行する関数名を返してくれるgetHandlerFunction() やソース固有の ID を返してくれる getTriggerSourceId() を利用する方法もあるようだが、今回は同じ関数を実行する時間主導型のトリガーを複数設定する予定だったので、残したいトリガーまで削除対象に含まれてしまう。

ドキュメントによると、一意の ID を返してくれる getUniqueId() という関数があり、今回の用途に適していたので利用することにした。自分で ID を管理する必要があるので、トリガーを設定するときにスプレッドシートに ID を保存する。

const time = new Date();
trigger = ScriptApp.newTrigger('myFunction').timeBased().at(time).create();

// スプレッドシートに uniqueId を出力する
trigger.getUniqueId();
...

トリガーの削除には deleteTrigger() の関数を利用できる。トリガーが実行する関数の中に含めることもできるが、今回はトリガーを削除するための関数を別に用意して、時間主導型のトリガーとして設定した。

const triggers = ScriptApp.getProjectTriggers();

// 削除対象の uniqueId を配列に詰める
let deleteTriggerIds = [];
...

for (const deleteTriggerId of deleteTriggerIds) {
  for (let i = 0; i < triggers.length; i++) {
    if (triggers[i].getUniqueId() === deleteTriggerId) {
      ScriptApp.deleteTrigger(triggers[i]);
    }
  }
}

特定のトリガーを削除できるようになったけど、もっといい方法があるかもしれない。

参考

Twitter API v2 で private metrics にアクセスする

Twitter API のドキュメントによると、所有または承認されたアカウントに対して、以下の非公開情報を取得できるとあったので試してみる。

  • 各ツイートのインプレッション数、URL リンクのクリック数、プロフィールリンクのクリック数、動画視聴の四分位データ(0%、25%、50%、75%、100%)
    • 過去30日以内に作成されたツイートのみ

デベロッパーアカウントとプロジェクトは事前に作成しておく。

アクセストークンは、PKCE を利用した OAuth 2.0 の認可コードフローで取得する。今回は試すだけなので、認可エンドポイントに渡すcode_challenge_method には plain を指定する。スコープにはoffline.access を指定してリフレッシュトークンも取得する。

https://twitter.com/i/oauth2/authorize?response_type=code&client_id=<CLIENT_ID>&redirect_uri=<REDIRECT_URI>&scope=tweet.read%20users.read%20offline.access&state=state&code_challenge=challenge&code_challenge_method=plain

アプリを許可すると認証コードが発行されるので、30秒以内にアクセストークンと交換する。

$ curl --location --request POST 'https://api.twitter.com/2/oauth2/token' \
--basic -u '<CLIENT_ID>:<CLIENT_SECRET>' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'code=<CODE>' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'redirect_uri=<REDIRECT_URI>' \
--data-urlencode 'code_verifier=challenge'

発行されたアクセストークンを利用して、単一のツイート情報を取得するエンドポイントにリクエストすると、前述の非公開情報を取得することができた。

$ curl "https://api.twitter.com/2/tweets/<ID>?tweet.fields=non_public_metrics,organic_metrics&expansions=attachments.media_keys&media.fields=non_public_metrics,organic_metrics" -H "Authorization: Bearer $ACCESS_TOKEN"

参考

Invisible reCAPTCHA を試してみる

reCAPTCHA v2 ではバッジを非表示にする Invisible reCAPTCHA が提供されていることを知ったので、試しにローカル環境で動かしてみる。

By default only the most suspicious traffic will be prompted to solve a captcha.

通常のトラフィックではユーザーの操作を伴わないが、疑わしいと判定された場合のみ画像認証によるチャレンジを要求するみたい。reCAPTCHA v3 と似ているが、v3 がスコアに基づいてリクエストを検証するのに対して、チャレンジでの検証方法も提供したい事情がある場合などに選択するといいかもしれない。既存の送信ボタンを押してから検証が行われるので、トークンの有効期限を意識せずに実装できるところも便利に感じた。

サイトの登録画面で reCAPTCHA v2 の「非表示 reCAPTCHA バッジ」を選択して API キー(サイトキーとシークレットキー)を発行する。ローカル環境の場合、ドメインには「localhost」を登録する。Ruby on Rails の Web アプリケーションへの導入を検討しているので、yasslab/sample_apps のサンプルアプリケーションと ambethia/recaptcha の Gem をお借りする。

# .env
RECAPTCHA_SITE_KEY=***
RECAPTCHA_SECRET_KEY=***
# config/initializers/recaptcha.rb
Recaptcha.configure do |config|
  config.site_key = ENV['RECAPTCHA_SITE_KEY']
  config.secret_key = ENV['RECAPTCHA_SECRET_KEY']
end
# app/controllers/sessions_controller.rb
def create
  user = User.find_by(email: params[:session][:email].downcase)
  recaptcha_valid = verify_recaptcha
  if recaptcha_valid
    if user && user.authenticate(params[:session][:password])
      # ログイン成功
    else
      # ログイン失敗
    end
  else
    render 'new'
  end
end
# app/views/sessions/new.html.erb
<%= form_with(url: login_path, scope: :session, local: true) do |f| %>
  …
  <%= invisible_recaptcha_tags ui: :input, text: 'Log in', class: "btn btn-primary" %>
<% end %>

f:id:ryoma123:20220221005031p:plainf:id:ryoma123:20220221004707p:plain
Invisible reCAPTCHA の表示例(画像認証は疑わしい場合のみ表示される)

Invisible reCAPTCHA  |  Google Developers のドキュメントによると data-size="invisible" 属性を利用してプログラム的に検証を行う方法も紹介されていた。 JavaScript のコールバック関数を独自に用意して検証後の処理を走らせる実装になる。アプリケーションの事情にあわせてこちらも利用してみたい。

ryoma123.hatenablog.com