こんにちは。tampopo256です。
普段は教育系のWebサービスの開発をしていました。(過去形)
🏁 はじめに 〜サークル運営で直面したLINE連絡地獄〜
僕は大学で大会運営を担当するサークルに所属しています。
大会準備では、広報チーム・会場チーム・受付チームなど、チームごとにLINEグループを作って運営しているのですが…
ある時、全チームに「明日の集合時間」などの連絡を一斉に送りたいシーンがありました。
そのときの現場の様子がこちら:
A「全チームに送った?」
Me「たぶん送ったけど、何チームに送ったか忘れた」
A「誰がどこに送ったか確認できる?」
Me「……できません……😇」
はい、めちゃくちゃ非効率です。
しかも大会直前の慌ただしい中で、「送信ミス」や「連絡漏れ」は命取り。
そこで、
LINEグループ間で一斉送信できて、誰がいつどこに何を送ったかもログとして残せるBotをGAS(Google Apps Script)で開発しました。
🔧 使用技術と前提条件
| 技術 | 用途 |
|---|---|
| Google Apps Script (GAS) | Botの処理・ログ記録 |
| LINE Messaging API | グループへのPush送信 |
| Google Spreadsheet | グループ管理・ログ保存 |
前提条件:
- Botを各グループに招待済みであること
- 送信元グループと送信先グループを
/register-source,/register-targetコマンドで登録しておくこと
🌀 どうやって動いてるのか?
グループ間の一斉送信
- 送信元グループでメッセージを送る
- Botが事前に登録された送信先グループすべてに一斉送信
- 同時にスプレッドシートにログを記録
ログに記録される内容
| ログ項目 | 内容例 |
|---|---|
| 日時 | 2025/06/25 15:30:21 |
| 送信者(userId) | Uxxxxxxxxxxxxx |
| 送信元グループ | グループ(C12345…) |
| メッセージ本文 | 集合時間は10:00です! |
| 送信先グループ | 広報チーム, 受付チーム |
🧾 スプレッドシート構成
| シート名 | 役割 |
|---|---|
SourceGroups |
送信元グループのIDと名前 |
TargetGroups |
送信先グループのIDと名前 |
Logs |
メッセージ送信ログ |
✅ GASスクリプト
こちら
const CHANNEL_ACCESS_TOKEN = 'YOUR_LINE_CHANNEL_ACCESS_TOKEN';
const SHEET_ID = 'YOUR_SPREADSHEET_ID';
function doPost(e) {
const json = JSON.parse(e.postData.contents);
const event = json.events[0];
if (!event || event.type !== 'message' || event.message.type !== 'text') return;
const text = event.message.text;
const sourceGroupId = event.source.groupId;
const sourceGroupName = `グループ(${sourceGroupId.slice(0, 6)}...)`;
const senderName = event.source.userId || '不明';
if (text === '/register-source') {
registerGroup(sourceGroupId, sourceGroupName, 'SourceGroups');
replyToGroup(event.replyToken, `✅ このグループを送信元に登録しました。`);
return;
}
if (text === '/register-target') {
registerGroup(sourceGroupId, sourceGroupName, 'TargetGroups');
replyToGroup(event.replyToken, `✅ このグループを送信先に登録しました。`);
return;
}
if (!isGroupRegistered(sourceGroupId, 'SourceGroups')) return;
const targetGroups = getGroups('TargetGroups');
const results = [];
targetGroups.forEach(group => {
const success = pushMessageToGroup(group.id, text);
results.push({ name: group.name, id: group.id, success });
});
const sentGroups = results.filter(r => r.success);
const resultText =
`✅ 一斉送信完了\n送信先:${sentGroups.length}グループ\n` +
sentGroups.map(r => `・${r.name}`).join('\n');
replyToGroup(event.replyToken, resultText);
logSendEvent({
timestamp: new Date(),
senderName,
sourceGroupId,
sourceGroupName,
message: text,
targets: sentGroups.map(g => g.name)
});
}
function registerGroup(groupId, groupName, sheetName) {
const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(sheetName);
const values = sheet.getDataRange().getValues();
const alreadyRegistered = values.some(row => row[0] === groupId);
if (!alreadyRegistered) {
sheet.appendRow([groupId, groupName]);
}
}
function isGroupRegistered(groupId, sheetName) {
const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(sheetName);
const values = sheet.getDataRange().getValues();
return values.some(row => row[0] === groupId);
}
function getGroups(sheetName) {
const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(sheetName);
const values = sheet.getDataRange().getValues();
return values.map(row => ({ id: row[0], name: row[1] }));
}
function pushMessageToGroup(groupId, message) {
const url = 'https://api.line.me/v2/bot/message/push';
const payload = {
to: groupId,
messages: [{ type: 'text', text: message }],
};
const options = {
method: 'post',
contentType: 'application/json',
headers: { Authorization: `Bearer ${CHANNEL_ACCESS_TOKEN}` },
payload: JSON.stringify(payload),
muteHttpExceptions: true,
};
const res = UrlFetchApp.fetch(url, options);
return res.getResponseCode() === 200;
}
function replyToGroup(replyToken, message) {
const url = 'https://api.line.me/v2/bot/message/reply';
const payload = {
replyToken: replyToken,
messages: [{ type: 'text', text: message }],
};
const options = {
method: 'post',
contentType: 'application/json',
headers: { Authorization: `Bearer ${CHANNEL_ACCESS_TOKEN}` },
payload: JSON.stringify(payload),
};
UrlFetchApp.fetch(url, options);
}
function logSendEvent(log) {
const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName('Logs');
sheet.appendRow([
Utilities.formatDate(log.timestamp, 'Asia/Tokyo', 'yyyy/MM/dd HH:mm:ss'),
log.senderName,
log.sourceGroupName,
log.message,
log.targets.join(', ')
]);
}
⚠️ 注意点:月200件制限あり(重要)
LINEのMessaging API(無料プラン)には以下の制限があります:
- 月間のPushメッセージ送信数は200件まで(無料枠)
- 送信先グループが複数の場合、それぞれ1件としてカウントされる
→ 例:5グループに同じメッセージ送信 = 5件としてカウント
つまり、月に40回程度しか5グループへの一斉送信はできません。
解決策(必要であれば)
- 有料プランへのアップグレード
- メールやSlackへの切り替え
- LINE通知は重要連絡のみに限定し、通常連絡は別媒体に分離
💡 実際使って感じたメリット
- 全体通達の**「送ったつもり」事故がゼロに**
- チーム間連携の情報伝達が高速化
- 運営メンバー間での役割明確化とトラブル回避
✅ 学びまとめ
- サークル運営でも業務改善Botは大活躍する
- GAS × LINE Bot の相性は抜群
- ログを残すだけで「責任の見える化」ができる
- 表示名取得には制約あり(OAuth必須)
- LINEの無料枠では月200件の送信制限に注意!
📣 この仕組み、他のサークルやゼミでも使えるはず!
「こういう機能追加したい!」などあればぜひコメントで教えてください!