GAS의 Language App으로 슬랙의 정보를 번역하고 답장하는 Bot을 만들었습니다.

슬랙으로 쉽게 번역하고 싶어요.


당사는 해외 고객과 슬랙을 통해 대화를 진행합니다.
역외지의 브리지 SE는 모두 우수한 사람들이기 때문에 일본어로 연락해도 문제가 없지만, 미세한 어감을 전달하지 못해 실제 일하는 엔지니어가 영어를 더 잘한다.
그래서 슬랙에 영어를 끼고 대화하는 경우도 있다.
또한 주로 GCP를 사용하는 경우가 많기 때문에 GCP의 서비스 운행 상황을 통지하지만 서비스 정지와 지연이 발생할 때의 통지는 기본적으로 영어로 발송된다.
서비스 이름은 좌우로 읽을 수 있고, 최악은 구글 번역과 DeepL 흐름이면 좋겠지만, 시원하게 번역하고 싶어요.
조사 결과 이 분은 자신이 하고 싶은 일을 이룬 것으로 밝혀졌다.
https://qiita.com/hotpepsi/items/3862618b38b463d37b53
다만, 이것을 가져올 때 몇 가지 문제가 생겨서 일부 코드를 수정했다.
이번에는 그 코드를 공개하기 위해 기사를 썼어요.
(주로 Slack 사양 변경에 대응하는 형태가 추가됨)

동작 개요


다음 동작을 만드는bot.
  • 번역할 채널에 bot
  • 초대
  • 번역하고 싶은 정보 중 국기의 그림문자(🇺🇸🇯🇵🇨🇳)반응을 보내면 그 언어 회선으로 번역
  • 스레드의 중간도 사용 가능
  • 대응 주요 언어(일본어, 영어, 중국어, 한국어, 독일어, 스페인어, 프랑스어, 이탈리아어, 러시아어)
  • 또 폐회사의 이안지가 미얀마인 만큼 미얀마어도 추가했다.
    動作イメージ
    동작 이미지

    소스 코드


    소스 코드는 다음과 같습니다.
    소스 코드
    script.gs
    var TOKEN = PropertiesService.getScriptProperties().getProperty("TOKEN");
    const flag_map = {
      "cn": { translateTo: "zh", languagePrefix: ":cn:" }, // 中国:中国語
      "flag-cn": { translateTo: "zh", languagePrefix: ":cn:" }, // 中国:中国語
      "de": { translateTo: "de", languagePrefix: ":de:" }, // ドイツ:ドイツ語
      "flag-de": { translateTo: "de", languagePrefix: ":de:" }, // ドイツ:ドイツ語
      "es": { translateTo: "es", languagePrefix: ":es:" }, // スペイン:スペイン語
      "flag-es": { translateTo: "es", languagePrefix: ":es:" }, // スペイン:スペイン語
      "fr": { translateTo: "fr", languagePrefix: ":fr:" }, // フランス:フランス語
      "flag-fr": { translateTo: "fr", languagePrefix: ":fr:" }, // フランス:フランス語
      "gb": { translateTo: "en", languagePrefix: ":gb:" }, // イギリス:英語
      "flag-gb": { translateTo: "en", languagePrefix: ":gb:" }, // イギリス:英語
      "it": { translateTo: "it", languagePrefix: ":it:" }, // イタリア:イタリア語
      "flag-it": { translateTo: "it", languagePrefix: ":it:" }, // イタリア:イタリア語
      "jp": { translateTo: "ja", languagePrefix: ":jp:" }, // 日本:日本語
      "flag-jp": { translateTo: "ja", languagePrefix: ":jp:" }, // 日本:日本語
      "kr": { translateTo: "ko", languagePrefix: ":kr:" }, // 韓国:韓国語
      "flag-kr": { translateTo: "ko", languagePrefix: ":kr:" }, // 韓国:韓国語
      "ru": { translateTo: "ru", languagePrefix: ":ru:" }, // ロシア:ロシア語
      "flag-ru": { translateTo: "ru", languagePrefix: ":ru:" }, // ロシア:ロシア語
      "us": { translateTo: "en", languagePrefix: ":us:" }, // ロシア:ロシア語
      "flag-us": { translateTo: "en", languagePrefix: ":us:" }, // ロシア:ロシア語
      "flag-mm": { translateTo: "my", languagePrefix: ":flag-mm:" }, // ミャンマー:ミャンマー語
    }
    
    function doPost(e) {
      try {
        var json = JSON.parse(e.postData.getDataAsString());
        if (json.type == "url_verification") {
          return ContentService.createTextOutput(json.challenge);
        }
        // https://api.slack.com/events/reaction_added
        // scope: "reactions:read"
        if (json.type == "event_callback" && json.event.type == "reaction_added") {
          return ContentService.createTextOutput(onReactionAdded(json.event));
        }
      } catch (ex) {
        console.log(ex);
      }
    }
    
    // https://api.slack.com/methods/chat.postMessage
    // scope: "chat:write:user" or "chat:write:bot"
    
    function postThreadMessage(channel, ts, text) {
      if (channel && ts && text) {
        var headers = {
          "Content-Type": "application/json",
          "Authorization": "Bearer " + TOKEN
        };
        var payload = {
          "token": TOKEN,
          "channel": channel,
          "thread_ts": ts,
          "text": text
        }
        var options = {
          "headers": headers,
          "method": "post",
          "payload": JSON.stringify(payload)
        };
        try {
          var response = UrlFetchApp.fetch("https://slack.com/api/chat.postMessage", options);
          console.log(JSON.parse(response.getContentText()));
        } catch (ex) {
          console.log(ex);
        }
      }
    }
    
    // https://api.slack.com/methods/conversations.replies
    // "channels:history" or "groups:history"
    
    function getMessages(channel, ts) {
      var headers = {
        "Authorization": "Bearer " + TOKEN
      };
      var options = {
        "headers": headers
      };
    
      var response = UrlFetchApp.fetch("https://slack.com/api/conversations.replies?channel=" + channel + "&ts=" + ts, options);
      var json = JSON.parse(response.getContentText());
      return json.messages;
    }
    
    function isTranslated(messages, lang) {
      for (var i in messages) {
        var message = messages[i];
        if (message.text.substring(0, lang.length) == lang) {
          return true;
        }
      }
      return false;
    }
    
    function onReactionAdded(json) {
      var channel = json.item.channel;
      var type = json.item.type;
      var ts = json.item.ts;
      var reaction = json.reaction;
      if (type == "message") {
        var messages = getMessages(channel, ts);
        if (messages) {
          if (messages[0].thread_ts) {
            ts = messages[0].thread_ts;
          }
          var message = messages[0].text;
          if (flag_map[reaction] && !isTranslated(messages, flag_map[reaction].languagePrefix)) {
            var translatedMessage = flag_map[reaction].languagePrefix + " " + LanguageApp.translate(message, "", flag_map[reaction].translateTo);
            postThreadMessage(channel, ts, translatedMessage);
          }
        }
      }
      return "OK";
    }
    
    원래 Qita의 글에서 포스트 메시지의 방법이 변경되었습니다(API 호출이 바뀌었습니까?)또는 대상으로부터 언어를 얻거나 원래 보도된 말의 답장 번역이 이상해지기 때문에 답장에 대한 반응도 변경된다.

    bot 제작 절차


    다음 순서에 따라 제작한다.
  • Google Apps Script로 새 항목 만들기
  • 위 소스 코드 붙여넣기 및 저장
  • [디버깅]→[새 디버깅]
  • 열기
  • URL을 미리 복사하려면 [웹 응용]을 선택하고 [액세스 가능한 사용자]에서 [모두]를 선택합니다.
  • Slack 응용 프로그램의 관리 화면을 엽니다(Slack 데스크톱 응용 프로그램의 경우 작업공간→기타 관리 항목→관리 응용 프로그램)
  • 오른쪽 위에 빌드
  • 열기
  • "Create New Apply"실행
  • Event Subscriptions를 열고 Enable Events를 On
  • 으로 설정합니다.
  • 4로 복사한 URL을 Request URL에 붙여넣기
  • "Subscribe to bot events"에서 "Add Bot User Event"에 "reaction added"를 추가하고 "Save Changes"를 클릭하여 저장
  • '오옥스 & Permissions'를 열고'오옥스 Tokens for Your Workspace'의'Bot User OAuth Token'
  • 을 복제한다.
  • GAS로 돌아가는 코드 편집기는 이전 편집기에서 스크립트 속성에 11개의 "xoxb-~"를 "TOEN"으로 설정합니다.( 참조: Google Apps Script의 속성 설정 방법이 가장 좋습니다. )
  • 11의 슬랙 어플리케이션 화면으로 돌아가서 "Scopes"에서 "Addan OAuth Scape"에서 다음 3개를 추가합니다.
    ・channeels:history
    ・chat:write
    ・reactions:read
  • 조금 위로 올라가'Reinstall to Workspace'에서 작업공간으로 추가 적용
  • 그럼 되겠네.
    그런 다음 Slack 애플리케이션 이름과 아이콘 등을 적절하게 변경하십시오.
    시행착오를 비교한 부분도 있어 기술 누락이 있을 수 있다.
    만약 순조롭지 못한 곳이 있으면 평론에서 저에게 알려주세요.

    못한 일


    안전 고려


    안전성이 미묘하다.
    누구나 할 수 있는 상태가 됐다.
    'Verification Token'이러면 대응하는 게 좋을 것 같아요.

    3초 규칙의 대응


    슬랙이 3초 안에 API 호출에 응답하는 규칙이 있지만 지원되지 않습니다.
    기존 기사에서는 같은 메시지가 전송되지 않도록 검사를 통해 대응했다.
    사실 이쪽 기사를 사용해야 하고 번역 처리는 별도로 해야 한다.
    https://zenn.dev/katzumi/articles/58354fb4d05038
    그렇긴 하지만 지금이라도 그렇게 늦은 것은 아니기 때문에 문제가 발생하지 않았다.

    끝맺다


    슬랙으로 번역된 Bot을 만드는 방법입니다.

    좋은 웹페이지 즐겨찾기