見出し画像

【GAS】Google Apps Script活用事例 Slack APIを駆使してFindyの情報をスプレッドシートに吐き出せるようにするスクリプト


Findyを使い始めたが、HRMOSと連携してねェ….。

最近、Findyという採用媒体を使用しており、Slack APIを使用してFindyのBot メッセージから定期的に送信されてくる情報をスプシに吐き出せないかなぁーと四苦八苦して、ようやく出来たのでその備忘録です。

Slack Appの作り方のポイント

年収などの個人情報を扱う鍵付きのプライベートチャンネルからメッセージを取得しなければいけないため、User OAuth Tokenを使用します。

設定の一例

英語なのでいい加減に読み飛ばしていた内容をしっかり読み込むと、今回のケースではDMを使用しないので、groups:historyのみ追加していれば、動くと思います。

OAuth Scopeを追加した後は、ワークスペースへの再インストールを忘れずに。インストールが完了すると、User OAuth Tokenが生成されるので、コピーしてスクリプトに張り付ければOK

スクリプト

function setValuesFindyInfo(){
  const twoWeeksAgo = getTwoWeeksAgo_();
  const messages    = getSlackMessages();
  const newValues   = convertToArray_(messages, twoWeeksAgo);

  // あとは重複を考慮したり転記箇所を工夫するシート処理を加筆してもらえると、なお良し
  SpreadsheetApp.getActiveSheet().getRange(1, 1, newValues.length, newValues[0].length).setValues(newValues);
}



/**
 *
 * プライベートチャンネルのSlack内容を取得
 * Slack App Name: Findy情報取得
 * 
 */
function getSlackMessages() {
  
  const token   =  '*********'; // User OAuth Token
  const payload = { 'channel' : '***********'}; // チャンネルID
  const options = {
    'method':      'GET',
    'headers':     { 'Authorization': 'Bearer ' + token },
    'contentType': 'application/json; charset=utf-8',
    'payload':     JSON.stringify(payload)
  }

  const webhook  = 'https://slack.com/api/conversations.history';
  const response = UrlFetchApp.fetch(webhook, options);
  const data     = JSON.parse(response);
  const messages = data.messages;
  console.log(messages);

  return messages  
}



/**
 * API経由で取得した内容をtimestampで2週間前より過去の投稿を除外し、2次元配列に変換する
 * 
 * @param  {Object.<Object.<string>>} messages - Slack APIで取得した内容
 * @param  {Date} dateToCompare - 2週間前の日付
 * @return {Array.<Array.<string>>}
 * 
 */
fuction convertToArray_(messages, dateToCompare){

  let targetRegExp   = new RegExp(/.*いいねしたユーザーさんとマッチングしました。\n\n.*/);
  let replacedRegExp = new RegExp(/.*いいねしたユーザーさんとマッチングしました。\n\n/);

  // 置換される文字列、置換後の文字列
  const lists = [
    {target: /<|>/g,  replaced: ''},
    {target: /\n/g,   replaced: ''},
    {target: /'/g,    replaced: ''},
    {target: 'URL: ', replaced: ''},
  ];

  const values = messages.map(message => {
    const urlMatched = message.text.match(/URL:.*\n/);
    const timestamp  = convertTimestamp_(message.ts);

    // Slackの投稿日時 > 2週間前の日付 && Bot Messageのみに限定する
    if(timestamp > dateToCompare && message.subtype === 'bot_message'){
      const info = {
        date: Utilities.formatDate(timestamp, 'JST', 'yyyy/MM/dd HH:mm:ss'),
        applicantName: extractTargetWord_(message.text, targetRegExp, replacedRegExp, /さん.*/, '  '),
        url: urlMatched !== null ? replaceElementsInArray_(urlMatched[0], lists) : '-',
        position: extractTargetWord_(message.text, /求人タイトル:.*/, /\n.*/, /.*:/)
      }
      
      if(info.applicantName === ''){
        targetRegExp   = new RegExp(/.*さんからメッセージを受信しました!.*\n\n/);
        replacedRegExp = new RegExp(/さんからメッセージを受信しました!.*\n\n/);

        // 応募者名が取得されない場合、再代入する
        info.applicantName = extractTargetWord_(message.text, targetRegExp, replacedRegExp, /さん.*/, '  ');
      }

      console.log(info);
      
      // オブジェクトを配列に変換する
      return Object.values(info);
      
    }
  }).filter(item => item !== undefined);
  console.log(values);

  return values
}



/**
 * 2週間前の日付を取得
 * 
 * 
 */
function getTwoWeeksAgo_(){
  const targetDate = new Date();
  targetDate.setDate(targetDate.getDate() - 14);
  const twoweeksAgo = targetDate;
  console.log(`2週間前の日付:${Utilities.formatDate(today, 'JST', 'yyyy/MM/dd')}`);

  return twoweeksAgo
}



/**
 * 指定した配列を元に文字列を全て置き換える 
 * 
 * @param {string} 置換前の文字列
 * @param {Array.<Object.<string>>} 置換対象、置換する文字列を含む配列 [{target: /a/g, replaced: 'A'},]
 * @return {string} 
 * 
 */
function replaceElementsInArray_(string, lists){
  const replaced = lists.reduce((acc, list) => acc.replace(list.target, list.replaced), string);
  console.log(`置換後:${replaced}`);
  return replaced
}



/**
 * 指定した配列を元に文字列を全て置き換える 
 * 
 * @param {string} 置換前の文字列
 * @param {string} 正規表現 or 文字列
 * @return {string} 残余引数 カンマ区切りでいくつでも指定可
 * 
 */
function extractTargetWord_(string, regex, ...replacedWords){
  const result = string.match(regex);
  return (result !== null) ? replacedWords.reduce((acc, current) => acc.replace(current, ''), result[0]) : '';
}



/**
 * UNIXyyyy/MM/dd HH:MM:ss形式に変換する
 * 
 * 
 */
function convertTimestamp_(original){
  const date = new Date(original * 1000);
  console.log(`置換前のタイムスタンプ: ${original}`);
  console.log(`置換後のタイムスタンプ: ${date}`);
  return date;
}

replaceElementsInArray_(string, lists)

こちらの関数は、全角を半角に変えたり、小文字を大文字に変換するなど規則性がある置換に向いています。1本記事を書いているので、併せて参考にしてみてください。

[{target: /a/g, replaced: 'A'},]

残余引数とは?

残余引数構文により、関数が不定数の引数を配列として受け入れることができ、可変長引数関数を JavaScript で表すことができます。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/rest_parameters
function sum(...theArgs) {
  let total = 0;
  for (const arg of theArgs) {
    total += arg;
  }
  return total;
}

console.log(sum(1, 2, 3));
// Expected output: 6

console.log(sum(1, 2, 3, 4));
// Expected output: 10

重複を考慮する関数(転記済みかどうかを判断する)

// あとは重複を考慮したり転記箇所を工夫するシート処理を加筆してもらえると、なお良し

自分の場合は、シートに転記したタイムスタンプを文字列で取得して、その配列の中にまったく同じものがあるかどうかを検索するやり方を採用しています。indexOfは、配列の中から文字列や値の位置を調べるメソッドですが、見つからなかった時に-1が返ってくる特性を活用しています。

// 1列目にタイムスタンプがある場合
const timestampArray = values.map(record => record[0]).filter(value => value);
const index = timestampArray.indexOf('2024/04/12 09:22:44');
if(index === -1){
  // 転記対象として配列に加える
}

転記箇所を工夫する処理

自作ライブラリに登録しているgetLastRowWithTextを使用しています。


この記事が参加している募集

仕事のコツ

with 日本経済新聞

この記事が気に入ったらサポートをしてみませんか?