From 35da0aa2eb7ba9d7c5f60351fa4af83b492f8cb2 Mon Sep 17 00:00:00 2001 From: mikuto Date: Thu, 13 Nov 2025 17:35:09 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=E9=80=9A=E7=9F=A5=E6=A9=9F=E8=83=BD?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SETUP.md | 90 ++++++++++++++++++++++++++++++++++++++++++++++ src/App.ts | 61 +++++++++++++++++++++++++++---- src/application.ts | 31 ++++++++++++++++ src/config.ts | 29 +++++++++++++++ src/discord.ts | 60 +++++++++++++++++++++++++++++++ src/main.ts | 8 +++-- 6 files changed, 270 insertions(+), 9 deletions(-) create mode 100644 SETUP.md create mode 100644 src/application.ts create mode 100644 src/config.ts create mode 100644 src/discord.ts diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000..3677fd7 --- /dev/null +++ b/SETUP.md @@ -0,0 +1,90 @@ +# セットアップ手順 + +## 1. スクリプトプロパティ(環境変数)の設定 + +GASエディタで環境変数を設定します。 + +### 設定方法 + +1. `npm run deploy` でデプロイ +2. `npm run open` でGASエディタを開く +3. 左メニューの「プロジェクトの設定」(⚙️アイコン)をクリック +4. 下部の「スクリプト プロパティ」セクションまでスクロール +5. 「スクリプト プロパティを追加」をクリック +6. 以下の2つのプロパティを追加: + +| プロパティ | 値 | 説明 | +|---------|-----|------| +| `DISCORD_WEBHOOK_URL` | `https://discord.com/api/webhooks/...` | Discord Webhook URL | +| `DISCORD_MENTION_ID` | `755410747042955294` | メンションするユーザーID | + +### Discord Webhook URLの取得方法 + +1. Discordサーバーの設定を開く +2. 「連携サービス」→「ウェブフック」 +3. 「新しいウェブフック」をクリック +4. チャンネルを選択してウェブフックURLをコピー + +### メンションIDの取得方法 + +1. Discord開発者モードを有効化(ユーザー設定→詳細設定→開発者モード) +2. ユーザーを右クリック→「IDをコピー」 + +## 2. トリガーの設定 + +GASエディタで以下の手順でトリガーを設定します。 + +1. GASエディタを開く +2. 左メニューの「トリガー」(時計アイコン)をクリック +3. 「トリガーを追加」 +4. 以下のように設定: + - 実行する関数: `onEdit` + - イベントのソース: `スプレッドシートから` + - イベントの種類: `編集時` + - 保存 + +## 3. テスト + +### 手動テスト + +1. スプレッドシートの任意のデータ行を選択 +2. GASエディタで `testNotification` 関数を実行 +3. Discordに通知が届くことを確認 + +### 実際の動作テスト + +1. スプレッドシートに新しい行を追加 +2. C列に申請者名を入力 +3. D列に申請種類を入力 +4. Discordに通知が届くことを確認 + +## トラブルシューティング + +### 通知が届かない場合 + +1. GASエディタの「実行ログ」を確認 +2. スクリプトプロパティが正しく設定されているか確認 + - 左メニュー「プロジェクトの設定」→「スクリプト プロパティ」 +3. Webhook URLが正しいか確認 + +### 環境変数が設定されているか確認 + +GASエディタで以下のスクリプトを実行: + +1. GASエディタのツールバーで関数選択を「testNotification」から「デバッグ用」に変更 +2. 以下のコードをエディタに貼り付けて実行: + +```javascript +function checkConfig() { + const props = PropertiesService.getScriptProperties(); + Logger.log('DISCORD_WEBHOOK_URL: ' + props.getProperty('DISCORD_WEBHOOK_URL')); + Logger.log('DISCORD_MENTION_ID: ' + props.getProperty('DISCORD_MENTION_ID')); +} +``` + +3. 「実行ログ」で値を確認 + +## 列の対応 + +- **C列**: 申請者名 +- **D列**: 申請種類 diff --git a/src/App.ts b/src/App.ts index 2de4aed..39c700d 100644 --- a/src/App.ts +++ b/src/App.ts @@ -1,14 +1,63 @@ +import { getApplicationFromRow } from './application'; +import { sendDiscordNotification } from './discord'; + /** - * メインのアプリケーション関数 + * スプレッドシートに行が追加されたときのトリガー */ -export const App = () => { - console.log('App started!'); +export const onEdit = (e: GoogleAppsScript.Events.SheetsOnEdit) => { + try { + const range = e.range; + const sheet = range.getSheet(); + const row = range.getRow(); + + // ヘッダー行(1行目)は無視 + if (row <= 1) { + return; + } + + // 申請情報を取得 + const application = getApplicationFromRow(sheet, row); + + if (!application) { + Logger.log(`行${row}: 申請情報が不完全のため通知をスキップ`); + return; + } + + // Discord通知を送信 + sendDiscordNotification(application); + Logger.log(`行${row}: 通知送信完了`); + } catch (error) { + Logger.log(`エラー: ${error}`); + throw error; + } +}; +/** + * テスト用: 手動で通知を送信 + */ +export const testNotification = () => { const ss = SpreadsheetApp.getActiveSpreadsheet(); const sheet = ss.getActiveSheet(); - Logger.log('スプレッドシート名: ' + ss.getName()); - Logger.log('シート名: ' + sheet.getName()); + // アクティブセルの行で通知テスト + const activeRow = sheet.getActiveCell().getRow(); + + if (activeRow <= 1) { + SpreadsheetApp.getUi().alert('データ行(2行目以降)を選択してください'); + return; + } + + const application = getApplicationFromRow(sheet, activeRow); + + if (!application) { + SpreadsheetApp.getUi().alert('申請情報が取得できませんでした'); + return; + } - SpreadsheetApp.getUi().alert('接続成功!'); + try { + sendDiscordNotification(application); + SpreadsheetApp.getUi().alert('通知送信成功!Discordを確認してください。'); + } catch (error) { + SpreadsheetApp.getUi().alert(`エラー: ${error}`); + } }; diff --git a/src/application.ts b/src/application.ts new file mode 100644 index 0000000..f44a2a5 --- /dev/null +++ b/src/application.ts @@ -0,0 +1,31 @@ +/** + * 申請情報の型定義 + */ +export interface Application { + applicantName: string; // 申請者名(C列) + applicationType: string; // 申請種類(D列) + rowNumber: number; // 行番号 +} + +/** + * 指定された行から申請情報を取得 + */ +export function getApplicationFromRow( + sheet: GoogleAppsScript.Spreadsheet.Sheet, + rowNumber: number +): Application | null { + // C列とD列のデータを取得 + const applicantName = sheet.getRange(rowNumber, 3).getValue() as string; // C列 + const applicationType = sheet.getRange(rowNumber, 4).getValue() as string; // D列 + + // 両方の値が存在する場合のみ有効な申請として扱う + if (!applicantName || !applicationType) { + return null; + } + + return { + applicantName: applicantName.toString().trim(), + applicationType: applicationType.toString().trim(), + rowNumber, + }; +} diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..fa18257 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,29 @@ +/** + * 環境変数の型定義 + */ +export interface Config { + DISCORD_WEBHOOK_URL: string; + DISCORD_MENTION_ID: string; +} + +/** + * スクリプトプロパティから設定を取得 + */ +export function getConfig(): Config { + const props = PropertiesService.getScriptProperties(); + const webhookUrl = props.getProperty('DISCORD_WEBHOOK_URL'); + const mentionId = props.getProperty('DISCORD_MENTION_ID'); + + if (!webhookUrl) { + throw new Error('DISCORD_WEBHOOK_URL が設定されていません'); + } + + if (!mentionId) { + throw new Error('DISCORD_MENTION_ID が設定されていません'); + } + + return { + DISCORD_WEBHOOK_URL: webhookUrl, + DISCORD_MENTION_ID: mentionId, + }; +} diff --git a/src/discord.ts b/src/discord.ts new file mode 100644 index 0000000..bb2048a --- /dev/null +++ b/src/discord.ts @@ -0,0 +1,60 @@ +import { Application } from './application'; +import { getConfig } from './config'; + +/** + * Discord Webhookのペイロード型 + */ +interface DiscordWebhookPayload { + content: string; + username?: string; + avatar_url?: string; +} + +/** + * Discordに通知を送信 + */ +export function sendDiscordNotification(application: Application): void { + const config = getConfig(); + + // メッセージ内容を構築 + const message = createNotificationMessage(config.DISCORD_MENTION_ID, application); + + const payload: DiscordWebhookPayload = { + content: message, + username: '申請通知Bot', + }; + + const options: GoogleAppsScript.URL_Fetch.URLFetchRequestOptions = { + method: 'post', + contentType: 'application/json', + payload: JSON.stringify(payload), + muteHttpExceptions: true, + }; + + try { + const response = UrlFetchApp.fetch(config.DISCORD_WEBHOOK_URL, options); + const responseCode = response.getResponseCode(); + + if (responseCode !== 204 && responseCode !== 200) { + throw new Error(`Discord API error: ${responseCode} - ${response.getContentText()}`); + } + + Logger.log(`Discord通知送信成功: 行${application.rowNumber}`); + } catch (error) { + Logger.log(`Discord通知送信失敗: ${error}`); + throw error; + } +} + +/** + * 通知メッセージを作成 + */ +function createNotificationMessage(mentionId: string, application: Application): string { + return `<@${mentionId}> + +📝 **新しいエクスプレッション申請が追加されました** + +**申請種類:** ${application.applicationType} +**申請者 :** ${application.applicantName} +**行番号 :** ${application.rowNumber}`; +} diff --git a/src/main.ts b/src/main.ts index ae7bb31..b0dd27f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,9 +1,11 @@ -import { App } from './App'; +import { onEdit, testNotification } from './App'; interface Global { - App: typeof App; + onEdit: typeof onEdit; + testNotification: typeof testNotification; } declare const global: Global; // entryPoints -global.App = App; +global.onEdit = onEdit; +global.testNotification = testNotification; From fe271ba4bb0f68423819e4110161162c34695ee3 Mon Sep 17 00:00:00 2001 From: mikuto Date: Thu, 13 Nov 2025 17:45:24 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat:=E7=94=B3=E8=AB=8B=E3=83=95=E3=82=A9?= =?UTF-8?q?=E3=83=BC=E3=83=A0=E3=81=AE=E3=83=AA=E3=83=B3=E3=82=AF=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E3=82=92=E8=A1=8C=E3=81=88=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SETUP.md | 4 +++- src/config.ts | 37 ++++++++++++++++++++++--------------- src/discord.ts | 18 ++++++++++++++---- src/main.ts | 4 ++-- 4 files changed, 41 insertions(+), 22 deletions(-) diff --git a/SETUP.md b/SETUP.md index 3677fd7..910964a 100644 --- a/SETUP.md +++ b/SETUP.md @@ -11,12 +11,13 @@ GASエディタで環境変数を設定します。 3. 左メニューの「プロジェクトの設定」(⚙️アイコン)をクリック 4. 下部の「スクリプト プロパティ」セクションまでスクロール 5. 「スクリプト プロパティを追加」をクリック -6. 以下の2つのプロパティを追加: +6. 以下の3つのプロパティを追加: | プロパティ | 値 | 説明 | |---------|-----|------| | `DISCORD_WEBHOOK_URL` | `https://discord.com/api/webhooks/...` | Discord Webhook URL | | `DISCORD_MENTION_ID` | `755410747042955294` | メンションするユーザーID | +| `APPLICATION_FORM_URL` | `https://forms.gle/...` | 申請フォームのURL | ### Discord Webhook URLの取得方法 @@ -79,6 +80,7 @@ function checkConfig() { const props = PropertiesService.getScriptProperties(); Logger.log('DISCORD_WEBHOOK_URL: ' + props.getProperty('DISCORD_WEBHOOK_URL')); Logger.log('DISCORD_MENTION_ID: ' + props.getProperty('DISCORD_MENTION_ID')); + Logger.log('APPLICATION_FORM_URL: ' + props.getProperty('APPLICATION_FORM_URL')); } ``` diff --git a/src/config.ts b/src/config.ts index fa18257..f700a66 100644 --- a/src/config.ts +++ b/src/config.ts @@ -2,28 +2,35 @@ * 環境変数の型定義 */ export interface Config { - DISCORD_WEBHOOK_URL: string; - DISCORD_MENTION_ID: string; + DISCORD_WEBHOOK_URL: string; + DISCORD_MENTION_ID: string; + APPLICATION_FORM_URL: string; } /** * スクリプトプロパティから設定を取得 */ export function getConfig(): Config { - const props = PropertiesService.getScriptProperties(); - const webhookUrl = props.getProperty('DISCORD_WEBHOOK_URL'); - const mentionId = props.getProperty('DISCORD_MENTION_ID'); + const props = PropertiesService.getScriptProperties(); + const webhookUrl = props.getProperty('DISCORD_WEBHOOK_URL'); + const mentionId = props.getProperty('DISCORD_MENTION_ID'); + const formUrl = props.getProperty('APPLICATION_FORM_URL'); - if (!webhookUrl) { - throw new Error('DISCORD_WEBHOOK_URL が設定されていません'); - } + if (!webhookUrl) { + throw new Error('DISCORD_WEBHOOK_URL が設定されていません'); + } - if (!mentionId) { - throw new Error('DISCORD_MENTION_ID が設定されていません'); - } + if (!mentionId) { + throw new Error('DISCORD_MENTION_ID が設定されていません'); + } - return { - DISCORD_WEBHOOK_URL: webhookUrl, - DISCORD_MENTION_ID: mentionId, - }; + if (!formUrl) { + throw new Error('APPLICATION_FORM_URL が設定されていません'); + } + + return { + DISCORD_WEBHOOK_URL: webhookUrl, + DISCORD_MENTION_ID: mentionId, + APPLICATION_FORM_URL: formUrl, + }; } diff --git a/src/discord.ts b/src/discord.ts index bb2048a..d354b9f 100644 --- a/src/discord.ts +++ b/src/discord.ts @@ -17,7 +17,11 @@ export function sendDiscordNotification(application: Application): void { const config = getConfig(); // メッセージ内容を構築 - const message = createNotificationMessage(config.DISCORD_MENTION_ID, application); + const message = createNotificationMessage( + config.DISCORD_MENTION_ID, + config.APPLICATION_FORM_URL, + application + ); const payload: DiscordWebhookPayload = { content: message, @@ -49,12 +53,18 @@ export function sendDiscordNotification(application: Application): void { /** * 通知メッセージを作成 */ -function createNotificationMessage(mentionId: string, application: Application): string { +function createNotificationMessage( + mentionId: string, + formUrl: string, + application: Application +): string { return `<@${mentionId}> 📝 **新しいエクスプレッション申請が追加されました** **申請種類:** ${application.applicationType} -**申請者 :** ${application.applicantName} -**行番号 :** ${application.rowNumber}`; +**申請者:** ${application.applicantName} +**行番号:** ${application.rowNumber} + +🔗 **【Discord】絵文字/スタンプ/サウンドボード申請フォーム:** ${formUrl}`; } diff --git a/src/main.ts b/src/main.ts index b0dd27f..47256a9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,8 @@ import { onEdit, testNotification } from './App'; interface Global { - onEdit: typeof onEdit; - testNotification: typeof testNotification; + onEdit: typeof onEdit; + testNotification: typeof testNotification; } declare const global: Global;