マスターシートからメールアドレス一覧を取得しGoogle カレンダーのゲスト一括追加

# 1.overview
Google カレンダーのゲストに、別シートのメールアドレスのリスト情報を紐づけて一括追加することで
作成したスケジュールを他のgoogle accountに紐づけます

# 2.spreadsheet(form)
- 個人スケジュールの場合は連携に何も入力しない
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497973/9ffc1b7b-42f9-d99d-42b0-b0b8be19abe0.png)

- ゲストに追加するときは「連携」セルから選択
  あらかじめ対象セルにプルダウンリストを作成しておきます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497973/87484fe9-b977-b582-d7f3-b7bdae9fe1ef.png)

- 複数のスケジュールを登録するときは入力した後に日付け順にソートする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497973/481a4705-9cac-854a-5173-607c41834d00.png)


# 3.gas
```code.gs
/**
 * スプレッドシート表示の際に呼出し
 */
function onOpen() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  //スプレッドシートのメニューにカスタムメニュー「カレンダー連携 > 実行」を作成
  var subMenus = [];
  subMenus.push({
    name: "実行",
    functionName: "createSchedule"  //実行で呼び出す関数を指定
  });
  ss.addMenu("カレンダー連携", subMenus);
}

/**
 * メーリングリストからメールアドレスリスト取得
 */
function getMailArray(){
  // メーリングリストマスターシート取得
  const sheet = SpreadsheetApp.openByUrl(★★★メーリングリストのマスターシート★★★);
  // マスターシートからメールアドレスの一覧を取得
  const sheetData = sheet.getRange(★★★対象セル範囲★★★).getValues();
  // 対象データが存在する行番号の配列を取得
  const fullYearSettlementIndexes = getFullYearSettlementIndexes(sheetData);
  // 対象データが存在する行をログに出力
  const mailArray = showFullYearSettlement(sheetData, fullYearSettlementIndexes);
  // 2次元配列を返す
  return mailArray;
}

/**
 * 予定を作成する
 */
function createSchedule() {
  // 連携するマスターアカウント
  const gAccount = Session.getActiveUser().getEmail()
  
  // 読み取り範囲（表の始まり行と終わり列）
  const topRow = 6;
  const lastCol = 9;

  // 0始まりで列を指定しておく
  const statusCellNum = 1;
  const dayCellNum = 2;
  const startCellNum = 4;
  const endCellNum = 5;
  const titleCellNum = 6;
  const locationCellNum = 7;
  const descriptionCellNum = 8;

  // シートを取得
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();

  // 予定の最終行を取得
  var lastRow = sheet.getLastRow();
  
  //予定の一覧を取得
  var contents = sheet.getRange(topRow, 1, sheet.getLastRow(), lastCol).getValues();

  // googleカレンダーの取得
  var calender = CalendarApp.getCalendarById(gAccount);

  //順に予定を作成（今回は正しい値が来ることを想定）
  for (i = 0; i <= lastRow - topRow; i++) {

    //「済」っぽいのか、空の場合は飛ばす
    var status = contents[i][statusCellNum];
    if (
      status == "済" ||
      status == "済み" ||
      status == "OK" ||
      contents[i][dayCellNum] == ""
    ) {
      continue;
    }

    // 各値をセット 日時はフォーマットして保持
    var day = new Date(contents[i][dayCellNum]);
    var startTime = contents[i][startCellNum];
    var endTime = contents[i][endCellNum];
    var title = contents[i][titleCellNum];

    const mailArray = getMailArray();
    Logger.log(mailArray);

    // 場所と詳細を追加
    if (status == "YD") {
      var options = {location: contents[i][locationCellNum], description: contents[i][descriptionCellNum], guests:mailarray.toString()};
    } else{
      // それ以外の場合はゲストを追加しない
      var options = {location: contents[i][locationCellNum], description: contents[i][descriptionCellNum]};
    }
    try {
      // 開始終了が無ければ終日で設定
      if (startTime == '' || endTime == '') {
        //予定を作成
        calender.createAllDayEvent(
          title,
          new Date(day),
          options
        );
        
      // 開始終了時間があれば範囲で設定
      } else {
        // 開始日時をフォーマット
        var startDate = new Date(day);
        startDate.setHours(startTime.getHours())
        startDate.setMinutes(startTime.getMinutes());
        // 終了日時をフォーマット
        var endDate = new Date(day);
        endDate.setHours(endTime.getHours())
        endDate.setMinutes(endTime.getMinutes());
        // 予定を作成
        calender.createEvent(
          title,
          startDate,
          endDate,
          options
        );
      }

      //無事に予定が作成されたら「済」にする
      sheet.getRange(topRow + i, 2).setValue("済");

    // エラーの場合（今回はログ出力のみ）
    } catch(e) {
      Logger.log(e);
    }
    
  }
  // ブラウザへ完了通知
  Browser.msgBox("完了");

}

/**
 * マスターシートから対象列の行番号配列を格納する関数
 */
function getFullYearSettlementIndexes(data) {
  const settlementColumnIndex = 2; // H列
  const settlementRows = convertTwoDimensionToOneDimension(data, settlementColumnIndex); // H列のみの一次元配列を取得
  const fullYearSettlementIndexes = []; // 行番号の格納用配列

  settlementRows.forEach(function(value, index) {
      fullYearSettlementIndexes.push(index);
  });
  return fullYearSettlementIndexes;
}

/**
 * 2次元配列 -> 1次元配列に変換し格納する関数
 */
function showFullYearSettlement(data, indexes) {
    oneDimensionalArray = []
    for (var i = 0; i < indexes.length; i++) { // jは列番号
      // 要素が空文字の場合は配列に追加しない
      if(data[i][0]!=""){
        oneDimensionalArray.push(data[i][0]);
      }
    }
    return oneDimensionalArray;
}

/**
 * 第一引数の二次元配列を第二引数のインデックスの値の一次元配列に変換する関数
 */
function convertTwoDimensionToOneDimension(twoDimensionalArray, targetIndex) {
  oneDimensionalArray = []
  twoDimensionalArray.forEach(function(value) {
    oneDimensionalArray.push(value[targetIndex]);
  });
  return oneDimensionalArray;
}
```

# 4.detail
getRange().getValues()で取得できるセルのデータは2次元配列になっています....。

```
// マスターシートからメールアドレスの一覧を取得
const sheetData = sheet.getRange(★★★対象セル範囲★★★).getValues();
```

なのでこれを1次元配列に変換してあげる必要があります

```
/**
 * マスターシートから対象列の行番号配列を格納する関数
 */
function getFullYearSettlementIndexes(data) {
  const settlementColumnIndex = 2; // H列
  const settlementRows = convertTwoDimensionToOneDimension(data, settlementColumnIndex); // H列のみの一次元配列を取得
  const fullYearSettlementIndexes = []; // 行番号の格納用配列

  settlementRows.forEach(function(value, index) {
      fullYearSettlementIndexes.push(index);
  });
  return fullYearSettlementIndexes;
}

/**
 * 2次元配列 -> 1次元配列に変換し格納する関数
 */
function showFullYearSettlement(data, indexes) {
    oneDimensionalArray = []
    for (var i = 0; i < indexes.length; i++) { // jは列番号
      // 要素が空文字の場合は配列に追加しない
      if(data[i][0]!=""){
        oneDimensionalArray.push(data[i][0]);
      }
    }
    return oneDimensionalArray;
}

/**
 * 第一引数の二次元配列を第二引数のインデックスの値の一次元配列に変換する関数
 */
function convertTwoDimensionToOneDimension(twoDimensionalArray, targetIndex) {
  oneDimensionalArray = []
  twoDimensionalArray.forEach(function(value) {
    oneDimensionalArray.push(value[targetIndex]);
  });
  return oneDimensionalArray;
}
```

googleカレンダーの予定は以下のフォーマットで作成できます

```
calender.createEvent(
  title,
  startDate,
  endDate,
  options
);
```

optionsの各パラメータは以下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497973/1d4ef32a-1ef4-4308-19d6-67d64b6a682d.png)

guests typeがStringになっているので1次元配列に変換したメールアドレスをStringに変換します

```
    // 場所と詳細を追加
    if (status == "YD") {
      var options = {location: contents[i][locationCellNum], description: contents[i][descriptionCellNum], guests:mailarray.toString()};
    } else{
      // それ以外の場合はゲストを追加しない
      var options = {location: contents[i][locationCellNum], description: contents[i][descriptionCellNum]};
    }
```

# 5.result

カスタムメニューに追加された「カレンダー連携」を選択し「実行」
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497973/69d7250c-360e-c350-b194-c2409e6a64dc.png)

「済」になっていることを確認します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497973/8caa91ac-45c0-73a6-cc35-6911f1222202.png)

google カレンダーに連携されました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/497973/ee462c30-250e-bb33-d0db-a513669dd1a8.png)