Skip to content

Commit 6ef1c0b

Browse files
authored
Merge pull request #4 from lc-tut/feat/notify
feat: 新規申請が来たとき、Discordに通知を送る
2 parents f5266dd + fe271ba commit 6ef1c0b

File tree

6 files changed

+289
-9
lines changed

6 files changed

+289
-9
lines changed

SETUP.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# セットアップ手順
2+
3+
## 1. スクリプトプロパティ(環境変数)の設定
4+
5+
GASエディタで環境変数を設定します。
6+
7+
### 設定方法
8+
9+
1. `npm run deploy` でデプロイ
10+
2. `npm run open` でGASエディタを開く
11+
3. 左メニューの「プロジェクトの設定」(⚙️アイコン)をクリック
12+
4. 下部の「スクリプト プロパティ」セクションまでスクロール
13+
5. 「スクリプト プロパティを追加」をクリック
14+
6. 以下の3つのプロパティを追加:
15+
16+
| プロパティ || 説明 |
17+
|---------|-----|------|
18+
| `DISCORD_WEBHOOK_URL` | `https://discord.com/api/webhooks/...` | Discord Webhook URL |
19+
| `DISCORD_MENTION_ID` | `755410747042955294` | メンションするユーザーID |
20+
| `APPLICATION_FORM_URL` | `https://forms.gle/...` | 申請フォームのURL |
21+
22+
### Discord Webhook URLの取得方法
23+
24+
1. Discordサーバーの設定を開く
25+
2. 「連携サービス」→「ウェブフック」
26+
3. 「新しいウェブフック」をクリック
27+
4. チャンネルを選択してウェブフックURLをコピー
28+
29+
### メンションIDの取得方法
30+
31+
1. Discord開発者モードを有効化(ユーザー設定→詳細設定→開発者モード)
32+
2. ユーザーを右クリック→「IDをコピー」
33+
34+
## 2. トリガーの設定
35+
36+
GASエディタで以下の手順でトリガーを設定します。
37+
38+
1. GASエディタを開く
39+
2. 左メニューの「トリガー」(時計アイコン)をクリック
40+
3. 「トリガーを追加」
41+
4. 以下のように設定:
42+
- 実行する関数: `onEdit`
43+
- イベントのソース: `スプレッドシートから`
44+
- イベントの種類: `編集時`
45+
- 保存
46+
47+
## 3. テスト
48+
49+
### 手動テスト
50+
51+
1. スプレッドシートの任意のデータ行を選択
52+
2. GASエディタで `testNotification` 関数を実行
53+
3. Discordに通知が届くことを確認
54+
55+
### 実際の動作テスト
56+
57+
1. スプレッドシートに新しい行を追加
58+
2. C列に申請者名を入力
59+
3. D列に申請種類を入力
60+
4. Discordに通知が届くことを確認
61+
62+
## トラブルシューティング
63+
64+
### 通知が届かない場合
65+
66+
1. GASエディタの「実行ログ」を確認
67+
2. スクリプトプロパティが正しく設定されているか確認
68+
- 左メニュー「プロジェクトの設定」→「スクリプト プロパティ」
69+
3. Webhook URLが正しいか確認
70+
71+
### 環境変数が設定されているか確認
72+
73+
GASエディタで以下のスクリプトを実行:
74+
75+
1. GASエディタのツールバーで関数選択を「testNotification」から「デバッグ用」に変更
76+
2. 以下のコードをエディタに貼り付けて実行:
77+
78+
```javascript
79+
function checkConfig() {
80+
const props = PropertiesService.getScriptProperties();
81+
Logger.log('DISCORD_WEBHOOK_URL: ' + props.getProperty('DISCORD_WEBHOOK_URL'));
82+
Logger.log('DISCORD_MENTION_ID: ' + props.getProperty('DISCORD_MENTION_ID'));
83+
Logger.log('APPLICATION_FORM_URL: ' + props.getProperty('APPLICATION_FORM_URL'));
84+
}
85+
```
86+
87+
3. 「実行ログ」で値を確認
88+
89+
## 列の対応
90+
91+
- **C列**: 申請者名
92+
- **D列**: 申請種類

src/App.ts

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,63 @@
1+
import { getApplicationFromRow } from './application';
2+
import { sendDiscordNotification } from './discord';
3+
14
/**
2-
* メインのアプリケーション関数
5+
* スプレッドシートに行が追加されたときのトリガー
36
*/
4-
export const App = () => {
5-
console.log('App started!');
7+
export const onEdit = (e: GoogleAppsScript.Events.SheetsOnEdit) => {
8+
try {
9+
const range = e.range;
10+
const sheet = range.getSheet();
11+
const row = range.getRow();
12+
13+
// ヘッダー行(1行目)は無視
14+
if (row <= 1) {
15+
return;
16+
}
17+
18+
// 申請情報を取得
19+
const application = getApplicationFromRow(sheet, row);
20+
21+
if (!application) {
22+
Logger.log(`行${row}: 申請情報が不完全のため通知をスキップ`);
23+
return;
24+
}
25+
26+
// Discord通知を送信
27+
sendDiscordNotification(application);
28+
Logger.log(`行${row}: 通知送信完了`);
29+
} catch (error) {
30+
Logger.log(`エラー: ${error}`);
31+
throw error;
32+
}
33+
};
634

35+
/**
36+
* テスト用: 手動で通知を送信
37+
*/
38+
export const testNotification = () => {
739
const ss = SpreadsheetApp.getActiveSpreadsheet();
840
const sheet = ss.getActiveSheet();
941

10-
Logger.log('スプレッドシート名: ' + ss.getName());
11-
Logger.log('シート名: ' + sheet.getName());
42+
// アクティブセルの行で通知テスト
43+
const activeRow = sheet.getActiveCell().getRow();
44+
45+
if (activeRow <= 1) {
46+
SpreadsheetApp.getUi().alert('データ行(2行目以降)を選択してください');
47+
return;
48+
}
49+
50+
const application = getApplicationFromRow(sheet, activeRow);
51+
52+
if (!application) {
53+
SpreadsheetApp.getUi().alert('申請情報が取得できませんでした');
54+
return;
55+
}
1256

13-
SpreadsheetApp.getUi().alert('接続成功!');
57+
try {
58+
sendDiscordNotification(application);
59+
SpreadsheetApp.getUi().alert('通知送信成功!Discordを確認してください。');
60+
} catch (error) {
61+
SpreadsheetApp.getUi().alert(`エラー: ${error}`);
62+
}
1463
};

src/application.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* 申請情報の型定義
3+
*/
4+
export interface Application {
5+
applicantName: string; // 申請者名(C列)
6+
applicationType: string; // 申請種類(D列)
7+
rowNumber: number; // 行番号
8+
}
9+
10+
/**
11+
* 指定された行から申請情報を取得
12+
*/
13+
export function getApplicationFromRow(
14+
sheet: GoogleAppsScript.Spreadsheet.Sheet,
15+
rowNumber: number
16+
): Application | null {
17+
// C列とD列のデータを取得
18+
const applicantName = sheet.getRange(rowNumber, 3).getValue() as string; // C列
19+
const applicationType = sheet.getRange(rowNumber, 4).getValue() as string; // D列
20+
21+
// 両方の値が存在する場合のみ有効な申請として扱う
22+
if (!applicantName || !applicationType) {
23+
return null;
24+
}
25+
26+
return {
27+
applicantName: applicantName.toString().trim(),
28+
applicationType: applicationType.toString().trim(),
29+
rowNumber,
30+
};
31+
}

src/config.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* 環境変数の型定義
3+
*/
4+
export interface Config {
5+
DISCORD_WEBHOOK_URL: string;
6+
DISCORD_MENTION_ID: string;
7+
APPLICATION_FORM_URL: string;
8+
}
9+
10+
/**
11+
* スクリプトプロパティから設定を取得
12+
*/
13+
export function getConfig(): Config {
14+
const props = PropertiesService.getScriptProperties();
15+
const webhookUrl = props.getProperty('DISCORD_WEBHOOK_URL');
16+
const mentionId = props.getProperty('DISCORD_MENTION_ID');
17+
const formUrl = props.getProperty('APPLICATION_FORM_URL');
18+
19+
if (!webhookUrl) {
20+
throw new Error('DISCORD_WEBHOOK_URL が設定されていません');
21+
}
22+
23+
if (!mentionId) {
24+
throw new Error('DISCORD_MENTION_ID が設定されていません');
25+
}
26+
27+
if (!formUrl) {
28+
throw new Error('APPLICATION_FORM_URL が設定されていません');
29+
}
30+
31+
return {
32+
DISCORD_WEBHOOK_URL: webhookUrl,
33+
DISCORD_MENTION_ID: mentionId,
34+
APPLICATION_FORM_URL: formUrl,
35+
};
36+
}

src/discord.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { Application } from './application';
2+
import { getConfig } from './config';
3+
4+
/**
5+
* Discord Webhookのペイロード型
6+
*/
7+
interface DiscordWebhookPayload {
8+
content: string;
9+
username?: string;
10+
avatar_url?: string;
11+
}
12+
13+
/**
14+
* Discordに通知を送信
15+
*/
16+
export function sendDiscordNotification(application: Application): void {
17+
const config = getConfig();
18+
19+
// メッセージ内容を構築
20+
const message = createNotificationMessage(
21+
config.DISCORD_MENTION_ID,
22+
config.APPLICATION_FORM_URL,
23+
application
24+
);
25+
26+
const payload: DiscordWebhookPayload = {
27+
content: message,
28+
username: '申請通知Bot',
29+
};
30+
31+
const options: GoogleAppsScript.URL_Fetch.URLFetchRequestOptions = {
32+
method: 'post',
33+
contentType: 'application/json',
34+
payload: JSON.stringify(payload),
35+
muteHttpExceptions: true,
36+
};
37+
38+
try {
39+
const response = UrlFetchApp.fetch(config.DISCORD_WEBHOOK_URL, options);
40+
const responseCode = response.getResponseCode();
41+
42+
if (responseCode !== 204 && responseCode !== 200) {
43+
throw new Error(`Discord API error: ${responseCode} - ${response.getContentText()}`);
44+
}
45+
46+
Logger.log(`Discord通知送信成功: 行${application.rowNumber}`);
47+
} catch (error) {
48+
Logger.log(`Discord通知送信失敗: ${error}`);
49+
throw error;
50+
}
51+
}
52+
53+
/**
54+
* 通知メッセージを作成
55+
*/
56+
function createNotificationMessage(
57+
mentionId: string,
58+
formUrl: string,
59+
application: Application
60+
): string {
61+
return `<@${mentionId}>
62+
63+
📝 **新しいエクスプレッション申請が追加されました**
64+
65+
**申請種類:** ${application.applicationType}
66+
**申請者:** ${application.applicantName}
67+
**行番号:** ${application.rowNumber}
68+
69+
🔗 **【Discord】絵文字/スタンプ/サウンドボード申請フォーム:** ${formUrl}`;
70+
}

src/main.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import { App } from './App';
1+
import { onEdit, testNotification } from './App';
22

33
interface Global {
4-
App: typeof App;
4+
onEdit: typeof onEdit;
5+
testNotification: typeof testNotification;
56
}
67
declare const global: Global;
78

89
// entryPoints
9-
global.App = App;
10+
global.onEdit = onEdit;
11+
global.testNotification = testNotification;

0 commit comments

Comments
 (0)