みやもとさんに機能を追加して雑務を自動化！

## 背景

現在所属している会社では出退勤管理に「[みやもとさん](https://github.com/masuidrive/miyamoto)」を使っています。

導入から5ヶ月が経ち、雑務の自動化に「みやもとさん」で管理しているデータを活用できることに気がつきました。

今回私が雑務を自動化するにあたり、「みやもとさん」にどのように機能を追加したかを共有させていただきます！

## どういった機能を追加したか？

### 日直当番

今いる会社では週3回、ランチをみんなで一緒に食べます！（めちゃ良い文化 ♪）
日直当番の「いただきます！」で食べ始めるのですが、日直を手動で決めていました。
日直決めを忘れると、目の前に美味しそうなご飯があるのに日直待ちの時間ができてしまいます。。

そこでランチの前までに、出社している人の中からランダムで日直を決め Slack で通知する機能を追加しました！

### 掃除場所の担当振り分け

週に2回、みんなでオフィスの掃除もしています！
どの場所を誰か掃除するかというのを、今までは「あみだくじ」のアプリを使って決めていました。
しかし、そのアプリを持っている方が休んだり、会議中だったりすると、どこを掃除するか考えるコストが発生してしまいます。。

そこで掃除が始まる前までに、掃除場所の担当が自動で振り分けられ、 Slack で通知する機能を追加しました！


## どこを修正すれば何ができるの？

「みやもとさん」に機能を追加するにあたり、どの部分を修正すれば、何ができるかを調べるのに時間がかかったので、共有させていただきます！

### timesheets.gs

#### commands

```js:timesheets.gs
// コマンド集
var commands = [
  ['actionSignOut', /(バ[ー〜ァ]*イ|ば[ー〜ぁ]*い|おやすみ|お[つっ]ー|おつ|さらば|お先|お疲|帰|乙|bye|night|(c|see)\s*(u|you)|退勤|ごきげんよ|グ[ッ]?バイ)/],
  ['actionWhoIsOff', /(だれ|誰|who\s*is).*(休|やす(ま|み|む))/],
  ['actionWhoIsIn', /(だれ|誰|who\s*is)/],
  ['actionCancelOff', /(休|やす(ま|み|む)|休暇).*(キャンセル|消|止|やめ|ません)/],
  ['actionOff', /(休|やす(ま|み|む)|休暇)/],
  ['actionSignIn', /(モ[ー〜]+ニン|も[ー〜]+にん|おっは|おは|へろ|はろ|ヘロ|ハロ|hi|hello|morning|出勤)/],
  ['confirmSignIn', /__confirmSignIn__/],
  ['confirmSignOut', /__confirmSignOut__/],
];
```

timesheets.gs には上記のような `commands` という変数があります。
Slack で送信した文言から、どのコマンドに対応する処理を実行するかが書かれています。

例えば、「おつ」と Slack で送信すると、 `actionSignOut` コマンドに対応する処理が実行されます。

つまり `commands` という変数に任意の配列を追加することで **どういう文言が送信されたときに、どのコマンドに対応する処理を実行するか** ということを設定することができます。

#### prototype

```js:timesheets.gs
// 退勤
Timesheets.prototype.actionSignOut = function(username, message) {
  if(this.datetime) {
    var data = this.storage.get(username, this.datetime);
    if(!data.signOut || data.signOut === '-') {
      this.storage.set(username, this.datetime, {signOut: this.datetime});
      this.responder.template("退勤", username, this.datetimeStr);
    }
    else {
      // 更新の場合は時間を明示する必要がある
      if(!!this.time) {
        this.storage.set(username, this.datetime, {signOut: this.datetime});
        this.responder.template("退勤更新", username, this.datetimeStr);
      }
    }
  }
};
```

timesheets.gs では　Timesheets の　prototype プロパティにメソッドが定義されてます。
Slack で送信した文言から、あるコマンドに対応する処理が実行されます。
対応する処理は　prototype プロパティに定義されている、コマンド名と同名のメソッドに書かれています。

例えば、「おつ」と Slack で送信すると、 `actionSignOut` コマンドに対応する処理が実行されます。
すると Timesheets.prototype.actionSignOut 内の処理が実行されます。

つまり Timesheets の prototype プロパティに新たなメソッドを定義することで、任意の処理を実行できるようになります。

### main.gs

```js:main.gs
// Time-based triggerで実行
function confirmSignIn() {
  var miyamoto = init();
  miyamoto.timesheets.confirmSignIn();
}
```

main.gs には上記のように関数が書かれています。
定期的に実行する処理は、上記のように関数を定義し、関数内で Timesheets の prototype プロパティに定義したメソッドを呼び出します。

つまり関数を新たに定義して Timesheets で定義したメソッドを呼び出せば、定期的に処理を実行することができます。

## 実装例

雑務を自動化するために、実際どのような修正を行ったか共有させていただきます！

### 日直当番

#### timesheets.gs

```diff:timesheets.gs
 var commands = [
   ['actionSignOut', /(バ[ー〜ァ]*イ|ば[ー〜ぁ]*い|おやすみ|お[つっ]ー|おつ|さらば|お先|お疲|帰|乙|bye|night|(c|see)\s*(u|you)|退勤|ごきげんよ|グ[ッ]?バイ)/],
   ['actionWhoIsOff', /(だれ|誰|who\s*is).*(休|やす(ま|み|む))/],
   ['actionWhoIsIn', /(だれ|誰|who\s*is)/],
   ['actionCancelOff', /(休|やす(ま|み|む)|休暇).*(キャンセル|消|止|やめ|ません)/],
   ['actionOff', /(休|やす(ま|み|む)|休暇)/],
   ['actionSignIn', /(モ[ー〜]+ニン|も[ー〜]+にん|おっは|おは|へろ|はろ|ヘロ|ハロ|hi|hello|morning|出勤)/],
   ['confirmSignIn', /__confirmSignIn__/],
   ['confirmSignOut', /__confirmSignOut__/],
+  ['actionWhoIsNicchoku', /(にっちょく|日直)/],
 ];

+ // 日直当番
+ Timesheets.prototype.actionWhoIsNicchoku = function(username, message) {
+   var dateObj = DateUtils.toDate(DateUtils.now());
+   var result =  _.compact(_.map(this.storage.getByDate(dateObj), function(row) {
+     return _.isDate(row.signIn) && !_.isDate(row.signOut) ? row.user : undefined;
+   }));
+
+   if(_.isEmpty(result)) {
+     this.responder.template("出勤なし");
+   }
+   else {
+     this.responder.send("<!here> 今日の日直は <@" + _.shuffle(result)[0] + "> さんでーす！");
+   }
+ };
```

#### main.gs

```diff:main.gs
+ function actionWhoIsNicchoku() {
+   var miyamoto = init();
+   miyamoto.timesheets.actionWhoIsNicchoku();
+ }
```


#### 実行結果

![スクリーンショット 2018-09-01 12.37.42.png](https://qiita-image-store.s3.amazonaws.com/0/186009/fa084738-937c-bf35-c185-461eadcf83a8.png)

（攻殻機動隊の熱烈なファンがいたので、 `miyamoto` ではなく `tachikoma` になっています笑）


### 掃除場所の担当振り分け

#### timesheets.gs

```diff:timesheets.gs
 var commands = [
   ['actionSignOut', /(バ[ー〜ァ]*イ|ば[ー〜ぁ]*い|おやすみ|お[つっ]ー|おつ|さらば|お先|お疲|帰|乙|bye|night|(c|see)\s*(u|you)|退勤|ごきげんよ|グ[ッ]?バイ)/],
   ['actionWhoIsOff', /(だれ|誰|who\s*is).*(休|やす(ま|み|む))/],
   ['actionWhoIsIn', /(だれ|誰|who\s*is)/],
   ['actionCancelOff', /(休|やす(ま|み|む)|休暇).*(キャンセル|消|止|やめ|ません)/],
   ['actionOff', /(休|やす(ま|み|む)|休暇)/],
   ['actionSignIn', /(モ[ー〜]+ニン|も[ー〜]+にん|おっは|おは|へろ|はろ|ヘロ|ハロ|hi|hello|morning|出勤)/],
   ['confirmSignIn', /__confirmSignIn__/],
   ['confirmSignOut', /__confirmSignOut__/],
+  ['devideCleaningArea', /(そうじ|掃除)/],
 ];

+ // 日直当番
+ Timesheets.prototype.devideCleaningArea = function(username, message) {
+   var dateObj = DateUtils.toDate(DateUtils.now());
+   var attendances =  _.compact(_.map(this.storage.getByDate(dateObj), function(row) {
+     return _.isDate(row.signIn) && !_.isDate(row.signOut) ? row.user : undefined;
+   }));
+   
+   var places = [
+     'エリア1',
+     'エリア2',
+     'エリア3',
+   ];
+
+   var message = "<!here> 今日はお掃除ですよ〜！役割分担しました！\n";
+   attendances = _.shuffle(attendances);
+ 
+   if(places.length > attendances.length) {
+     for (var i = 0; i < attendances.length; i++) {
+       message += ( places[i] + ": <@" + attendances[i] + ">\n");
+     }
+     message += "\n *以下のエリアは手分けして掃除してください〜！* \n";
+     for (var i; i < places.length; i++) {
+       message += ( places[i] + "\n" );
+     }
+   } else if(places.length < attendances.length) {
+     for (var i = 0; i < places.length; i++) {
+       message += ( places[i] + ": <@" + attendances[i] + ">\n");
+     }
+     for (var i; i < attendances.length; i++) {
+       message += ( "<@" + attendances[i] + ">," );
+     }
+     message += "は大変そうな人を手伝ってくださいね〜！";
+   } else {
+     for (var i = 0; i < places.length; i++) {
+       message += ( places[i] + ": <@" + attendances[i] + ">\n");
+     }    
+   }
+   
+   this.responder.send(message);
+ };
```

#### main.gs

```diff:main.gs
+ function devideCleaningArea() {
+   var miyamoto = init();
+   miyamoto.timesheets.devideCleaningArea();
+ }
```

#### 実行結果

![スクリーンショット 2018-09-01 12.43.39.png](https://qiita-image-store.s3.amazonaws.com/0/186009/7f5568f1-39d2-fd10-70e3-dc811f58c6bb.png)


## 実装以外に必要な作業

### 定期処理のトリガーを設定

定期的に処理を実行するには、以下のようにトリガーを設定する必要があるので、注意してください！

１. スクリプトエディタの **編集** -> **現在のプロジェクトのトリガー** をクリック

![スクリーンショット 2018-09-01 13.07.36.png](https://qiita-image-store.s3.amazonaws.com/0/186009/045eafbc-cfa3-b3de-344e-fc0fef2ffff3.png)

２. **新しいトリガーを追加** をクリック

![スクリーンショット 2018-09-01 13.07.51.png](https://qiita-image-store.s3.amazonaws.com/0/186009/b078c73b-5630-43e1-9b66-866323a831da.png)

３. 定期的に実行した関数を選択し、保存をクリック

![スクリーンショット 2018-09-01 13.10.24.png](https://qiita-image-store.s3.amazonaws.com/0/186009/887f0c85-8fbd-c6ea-3a89-67bd7b188659.png)


### ウェブアプリケーションの更新

コードの修正を終えたらウェブアプリケーションの更新をする必要があります。
公式サイトに更新の仕方が書いてありますので、そちらをご確認ください！

公式サイト: https://developers.google.com/apps-script/guides/web#deploying_a_script_as_a_web_app