GAS(Google Apps Script)とLINE Messaging APIでWebhookを使ってPOSTで受け取ったデータを処理するプログラムを作成したときに、以下のようなエラーが発生することがあります。
ここではこの原因と対処法を解説しています。
発生原因
このエラーが発生する原因は、WebhookでdoPost()を実行した場合、doPost関数の処理に時間がかかりすぎているためです。
例えば以下のようなプログラムがあるとします。
イベントを発生させたユーザーのユーザー名やプロフィール画像のURLを取得して、スプレッドシートに書き出す処理は成功します。
ところが、その次の「sendFirstMail()」という関数を実行するところでアクセス権がなく、タイムアウトのエラーが発生します。
//ssid, channelAccessToken //グローバル変数で定義
// LINEのWebhookからイベントデータを受信する
function doPost(e) {
let sheetName = 'LINEフォローリスト';
const eventData = JSON.parse(e.postData.contents);
// スプレッドシートのシートを取得
const sheet = SpreadsheetApp.openById(ssid).getSheetByName(sheetName);
// イベントごとに処理を行う
eventData.events.forEach(event => {
const eventType = event.type; // イベントタイプ(例: message, follow, unfollowなど)
const userId = event.source.userId || '不明なユーザー';
let eventContent = '';
// 指定のイベントタイプのみ処理
if (eventType === 'follow') {
eventContent = '友だち追加';
} else if (eventType === 'unfollow') {
eventContent = '友だち削除';
} else if (eventType === 'join') {
eventContent = 'グループ参加';
} else if (eventType === 'leave') {
eventContent = 'グループ退出';
} else {
// その他のイベントは処理しない
return;
}
let userProfile;
let displayName, pictureUrl;
// ユーザープロファイルを取得
// unfollowではユーザープロファイルを取得できないため、スキップ
if (eventType === 'unfollow') {
userProfile = { displayName: '削除済みユーザー', pictureUrl: '' };
}else{
userProfile = getUserProfile(userId);
displayName = userProfile.displayName;
pictureUrl = userProfile.pictureUrl;
}
// スプレッドシートに書き出す
sheet.appendRow([new Date(), userId, displayName, pictureUrl, eventType, eventContent]);
});
//初回メッセージ送信を実行
sendFirstLINE() //ここでアクセスできずタイムアウトが発生
}
// LINE APIからユーザー情報を取得
function getUserProfile(userId) {
try{
const url = `https://api.line.me/v2/bot/profile/${userId}`;
const options = {
"headers": {
"Authorization": "Bearer " + channelAccessToken
},
"method": "GET",
};
const response = UrlFetchApp.fetch(url, options);
return JSON.parse(response.getContentText());
}
catch{console.log(e)}
}
対処法
対処法は、重い処理をトリガーでセットしてしまうことです。
つまり、doPost関数自体の処理はトリガーの設定のみとなります。また、トリガーの実行が完了したら、そのトリガーを削除するようにします。
プログラムを変更したら、新しいデプロイとWebhook URLのアップデートをお忘れなく。
トリガーのセット
例えば、上記のようにsendFirstLINE関数を実行したい場合は、sendFirstLINE()の部分を以下のように変更します。
//トリガーを作成(Webhookがタイムアウトするため)
ScriptApp.newTrigger('sendFirstLINE')
.timeBased()
.after(1 * 1000) // 1秒後にセット
.create();
ScriptApp.newTrigger(関数名)で、指定した関数をトリガーにセットします。
timebasedの後に時間を指定することで、いつ実行するかを指定します。ここでは、after(1 * 1000)で1000ms後、つまり1秒後を指定しています。
最後に、create()でトリガーをセットしています。
(参考)Google公式 トリガーの設定で使えるメソッド|Class ClockTriggerBuilder
トリガーの削除
このままではPOSTの送信がある度にトリガーが無限に増え続けてしまいます。時間を指定しているので実行は1度きりですが、トリガーが分かりにくくなってしまいます。
そこで、指定の関数を実行したらトリガーを削除する処理を追加します。
ScriptApp.getProjectTriggers()
で全てのトリガーを取得して、getHandlerFunction()
で関数名が指定した関数のときに、ScriptApp.deleteTrigger()関数でトリガーの削除を行います。
例えば、トリガーでセットした関数が「SendFirstLINE」であれば、以下のようにします。
const triggers = ScriptApp.getProjectTriggers();
triggers.forEach(trigger => {
if (trigger.getHandlerFunction() === 'sendFirstMail') {
ScriptApp.deleteTrigger(trigger);
}
こうすることでトリガーをセットすることができます。
中身を確認すると日時がセットされています。
注意点
上記のようにWebhookの実行でトリガーをセットすることで、LINE Messaging API経由で関数を実行することができるようになります。
ところがこの状態でも、検証を実行すると以下のようにタイムアウトが発生します。
ただ、トリガーは問題なくセットできているので、ここではこれで良しとしておきます。
例えば、3回「検証」をクリックすると、3回分トリガーが設定されます。
スプレッドシートのアクセス権は関係ない
当初、スプレッドシートのアクセス権の問題かと思いましたが、アクセス権は関係ありません。
「制限付き」で問題なくプログラムを実行することができます。