Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# セットアップ手順

## 1. スクリプトプロパティ(環境変数)の設定

GASエディタで環境変数を設定します。

### 設定方法

1. `npm run deploy` でデプロイ
2. `npm run open` でGASエディタを開く
3. 左メニューの「プロジェクトの設定」(⚙️アイコン)をクリック
4. 下部の「スクリプト プロパティ」セクションまでスクロール
5. 「スクリプト プロパティを追加」をクリック
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の取得方法

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'));
Logger.log('APPLICATION_FORM_URL: ' + props.getProperty('APPLICATION_FORM_URL'));
}
```

3. 「実行ログ」で値を確認

## 列の対応

- **C列**: 申請者名
- **D列**: 申請種類
61 changes: 55 additions & 6 deletions src/App.ts
Original file line number Diff line number Diff line change
@@ -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}`);
}
};
31 changes: 31 additions & 0 deletions src/application.ts
Original file line number Diff line number Diff line change
@@ -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,
};
}
36 changes: 36 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* 環境変数の型定義
*/
export interface Config {
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 formUrl = props.getProperty('APPLICATION_FORM_URL');

if (!webhookUrl) {
throw new Error('DISCORD_WEBHOOK_URL が設定されていません');
}

if (!mentionId) {
throw new Error('DISCORD_MENTION_ID が設定されていません');
}

if (!formUrl) {
throw new Error('APPLICATION_FORM_URL が設定されていません');
}

return {
DISCORD_WEBHOOK_URL: webhookUrl,
DISCORD_MENTION_ID: mentionId,
APPLICATION_FORM_URL: formUrl,
};
}
70 changes: 70 additions & 0 deletions src/discord.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
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,
config.APPLICATION_FORM_URL,
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,
formUrl: string,
application: Application
): string {
return `<@${mentionId}>

📝 **新しいエクスプレッション申請が追加されました**

**申請種類:** ${application.applicationType}
**申請者:** ${application.applicantName}
**行番号:** ${application.rowNumber}

🔗 **【Discord】絵文字/スタンプ/サウンドボード申請フォーム:** ${formUrl}`;
}
8 changes: 5 additions & 3 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -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;