Skip to content

Instantly share code, notes, and snippets.

@ttlg
Created May 27, 2025 06:46
Show Gist options
  • Save ttlg/31815dd121efcfd806f69bc257951ca1 to your computer and use it in GitHub Desktop.
Save ttlg/31815dd121efcfd806f69bc257951ca1 to your computer and use it in GitHub Desktop.
/**
* LINE → GitHub Issue → Google Sheets Logger (signature-less robust版)
* ※ Google Apps Script Webアプリでは HTTP ヘッダーを直接取得できないため、
* LINE 署名検証は割愛しています(Webhook URL を秘匿できる環境前提)。
* @license MIT
*/
/* ---------- 設定 ---------- */
const CONF = {
LINE_ACCESS_TOKEN: PropertiesService.getScriptProperties().getProperty('LINE_ACCESS_TOKEN'),
GH_OWNER: PropertiesService.getScriptProperties().getProperty('GH_OWNER'),
GH_REPO: PropertiesService.getScriptProperties().getProperty('GH_REPO'),
GH_TOKEN: PropertiesService.getScriptProperties().getProperty('GH_TOKEN'),
SPREADSHEET_ID: PropertiesService.getScriptProperties().getProperty('SPREADSHEET_ID'),
};
/* ---------- エントリポイント ---------- */
function doPost(e) {
try {
if (!e || !e.postData || !e.postData.contents) throw new Error('Empty POST body');
const payload = JSON.parse(e.postData.contents);
if (!payload.events || !Array.isArray(payload.events)) throw new Error('Invalid LINE payload');
payload.events
.filter(evt => evt.type === 'message' && evt.message && evt.message.type === 'text')
.forEach(evt => handleTextMessage(evt));
return ContentService.createTextOutput('OK'); // 200 OK
} catch (err) {
console.error(`doPost error: ${err.message}\n${err.stack}`);
logToSheet({ status: 'ERROR', message: err.message });
return ContentService.createTextOutput('Error').setHttpStatusCode(500);
}
}
/* ---------- メッセージ処理 ---------- */
function handleTextMessage(event) {
const userText = (event.message.text || '').trim() || 'Hello!';
const issueTitle = `@claude ${userText}`;
const issueUrl = createGithubIssue(issueTitle, issueTitle);
logToSheet({
userId: event.source && event.source.userId,
message: userText,
url: issueUrl,
status: issueUrl ? 'OK' : 'NG',
});
const replyText = issueUrl
? `✅ Issue を作成しました:\n${issueUrl}`
: `❌ Issue 作成に失敗しました`;
replyToLine(event.replyToken, replyText);
}
/* ---------- GitHub Issue 作成 ---------- */
function createGithubIssue(title, body) {
try {
const url = `https://api.github.com/repos/${CONF.GH_OWNER}/${CONF.GH_REPO}/issues`;
const res = UrlFetchApp.fetch(url, {
method: 'post',
contentType: 'application/json',
headers: { Authorization: `token ${CONF.GH_TOKEN}` },
payload: JSON.stringify({ title, body }),
muteHttpExceptions: true,
});
if (res.getResponseCode() !== 201) {
throw new Error(`GitHub ${res.getResponseCode()}: ${res.getContentText()}`);
}
return JSON.parse(res.getContentText()).html_url;
} catch (err) {
console.error(`createGithubIssue error: ${err.message}`);
return null;
}
}
/* ---------- LINE 返信 ---------- */
function replyToLine(replyToken, text) {
try {
UrlFetchApp.fetch('https://api.line.me/v2/bot/message/reply', {
method: 'post',
contentType: 'application/json',
headers: { Authorization: `Bearer ${CONF.LINE_ACCESS_TOKEN}` },
payload: JSON.stringify({
replyToken,
messages: [{ type: 'text', text }],
}),
muteHttpExceptions: true,
});
} catch (err) {
console.error(`replyToLine error: ${err.message}`);
}
}
/* ---------- Google Sheets へのログ ---------- */
function logToSheet(obj) {
try {
if (!CONF.SPREADSHEET_ID) return;
const ss = SpreadsheetApp.openById(CONF.SPREADSHEET_ID);
const sheet = ss.getSheets()[0];
if (sheet.getLastRow() === 0) {
sheet.appendRow(['Timestamp', 'UserId', 'Message', 'IssueURL', 'Status']);
}
sheet.appendRow([
new Date(),
obj.userId || '',
obj.message || '',
obj.url || '',
obj.status || 'NG',
]);
} catch (err) {
console.error(`logToSheet error: ${err.message}`);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment