Google Chat #Space: Create Message

Google Chat: Space, Create Message

Google Chat: Space, Create Message
Posts a message in a Google Chat Space. Bold decoration (with asterisks) and Inline-code decoration (with backticks) are also applied as they are. It is also possible to attach some Process Data in card format.
Configs
  • A1: Set INCOMING WEBHOOK Url (at Menu “Manage Webhook”) *#{EL}
  • B1: Set Body of Message#{EL}
  • B2: If to post Data, Set Field Names in each line (eg “q_corp”)#{EL}
  • B3: If to display footer link, Set ${var[applicationRoot]}#{EL}
  • C1: Select STRING DATA that stores Message Name (update)
  • C2: Select STRING DATA that stores Space Name (update)
Script (click to open)
// GraalJS Script (engine type: 2)

//////// START "main()" /////////////////////////////////////////////////////////////////

main();
function main(){ 

//// == Config Retrieving / 工程コンフィグの参照 ==
const strWebhookUrl        = configs.get      ( "StrConfA1" );    /// REQUIRED
  if( strWebhookUrl      === "" ){
    throw new Error( "\n AutomatedTask ConfigError:" +
                     " Config {A1: WebhookUrl} is empty \n" );
  }
const strBodyOfMessage     = configs.get      ( "StrConfB1" );    // NotRequired
const strFieldNames        = configs.get      ( "StrConfB2" );    // NotRequired
const strApplicationRoot   = configs.get      ( "StrConfB3" );    // NotRequired
const strPocketMessageName = configs.getObject( "SelectConfC1" ); // NotRequired
const strPocketSpaceName   = configs.getObject( "SelectConfC2" ); // NotRequired
  if( strBodyOfMessage  === "" && strFieldNames === "" ){
    throw new Error( "\n AutomatedTask ConfigError:" +
                     " Both Config {B1:Message} and {B2:FieldNames} are empty \n" );
  }

//// == Data Retrieving / ワークフローデータの参照 ==
// (Nothing. Retrieved via Expression Language in Config Retrieving)


//// == Calculating / 演算 ==
//// get property
const strProcessInstanceId    = processInstance.getProcessInstanceId() + "";
const strProcessInstanceTitle = processInstance.getProcessInstanceTitle() + "";
const strWorkflowAppName      = processInstance.getProcessModelInfoName() + "";
const strWorkflowAppId        = processInstance.getProcessModelInfoId() + "";

//// prepare for access to "Google Chat Bot"
/// Google Workspace for Developers > Google Chat for Developers
/// > Integrate any tool, service, or resource with Google Chat bots
/// > Send asychronous messages with incoming webhooks
/// https://developers.google.com/chat/how-tos/webhooks
/// 
/// NOT "Google Chat API"
/// "Get programmatic access to events, messages, spaces, and attachments"
/// Reference > REST Resources > spaces.messages > create
/// https://developers.google.com/chat/api/reference/rest/v1/spaces.messages/create

// Message
let request1Obj = {};

// Simple text message
// https://developers.google.com/chat/api/guides/message-formats/basic
    request1Obj.text = strBodyOfMessage;

// Card message
// https://developers.google.com/chat/api/guides/message-formats/cards
// https://developers.google.com/chat/api/guides/message-formats/cards#card_text_formatting

if( strFieldNames !== "" || strApplicationRoot !== "" ){
    request1Obj.cards = [];           // ___ ___ prepare Cards array ___ ___
    request1Obj.cards[0] = {};
    request1Obj.cards[0].sections = [];  // ___ prepare Sections array ___

    let objTitleSection = {};            // ### Title Section
        objTitleSection.widgets = [];        // ___ prepare Widgets array ___
        objTitleSection.widgets[0] = {};
        objTitleSection.widgets[0].textParagraph = {};
        objTitleSection.widgets[0].textParagraph.text = strApplicationRoot ?
          // clickable link to WorkflowApp Info
          `<font color="#57C400"><i>p${strProcessInstanceId}</i></font><br>` +
          `<font color="#009900"><b>` + escaper.escapeHtml(strProcessInstanceTitle) + `</b></font><br> ` +
          `<a href=${strApplicationRoot}OR/ProcessModel/view?processModelInfoId=${strWorkflowAppId}>` +
            escaper.escapeHtml(strWorkflowAppName) + `</a>` :
          // No link
          `<font color="#57C400"><i>p${strProcessInstanceId}</i></font><br>` +
          `<font color="#009900"><b>` + escaper.escapeHtml(strProcessInstanceTitle) +
          `</b></font><br> ` + escaper.escapeHtml(strWorkflowAppName);
    request1Obj.cards[0].sections.push( objTitleSection ); // push TitleSection to Cards[0].Sections ###

  if( strFieldNames !== "" ){
    const arrFieldNames = strFieldNames.split("\n");
    for( let i = 0; i < arrFieldNames.length; i++ ){
      if( arrFieldNames[i] === "" ){ continue; }
      let objDataSection = {};           // ### Data Section
          objDataSection.widgets = [];       // ___ prepare Widgets array ___
      let objKeyValueWidget = {};            // ### KeyValue Widget
          objKeyValueWidget.keyValue = {};
      let qPocket = engine.findDataDefinitionByVarName( arrFieldNames[i] );
      // refer Process Data
      if( qPocket === null ){
        engine.log( " AutomatedTask RuntimeWarning: " +
                    " {B2: FieldName} contains illegal name: " + arrFieldNames[i] );
        continue;
      }else if( qPocket.matchDataType( "STRING_TEXTFIELD" ) ){ 
          objKeyValueWidget.keyValue.topLabel = qPocket.getName();
          objKeyValueWidget.keyValue.content  = engine.findData(qPocket) ?
            escaper.escapeHtml(engine.findData(qPocket)) : `<i><font color="#aaaaaa">null</font></i>`;
          objKeyValueWidget.keyValue.contentMultiline = "false";
          // "topLabel" "bottomLabel" Subset_of_HTML_Tags not supported
      }else if( qPocket.matchDataType( "STRING_TEXTAREA" ) ){
          objKeyValueWidget.keyValue.topLabel = qPocket.getName();
          objKeyValueWidget.keyValue.content  = engine.findData(qPocket) ?
            escaper.escapeHtml(engine.findData(qPocket)) : `<i><font color="#aaaaaa">null</font></i>`;
          objKeyValueWidget.keyValue.contentMultiline = "true";
      }else if( qPocket.matchDataType( "DECIMAL" ) ){
          objKeyValueWidget.keyValue.topLabel = qPocket.getName();
          objKeyValueWidget.keyValue.content  = engine.findData(qPocket) ?
            (engine.findData(qPocket) + "") : `<i><font color="#aaaaaa">null</font></i>`;
          objKeyValueWidget.keyValue.contentMultiline = "false";
      }else if( qPocket.matchDataType( "SELECT" ) ){
          let qSelected = engine.findData(qPocket);
          let numOfSelected = qSelected.size() - 0;
          let strSelected = "";
          for( let j = 0; j < numOfSelected; j++ ){
            strSelected += escaper.escapeHtml(qSelected.get(j).getDisplay());
            if( j !== numOfSelected - 1 ){ strSelected += "<br>";}
          }
          objKeyValueWidget.keyValue.topLabel = qPocket.getName();
          objKeyValueWidget.keyValue.content  = engine.findData(qPocket) ?
            strSelected : `<i><font color="#aaaaaa">null</font></i>`;
          objKeyValueWidget.keyValue.contentMultiline = "true";
      }else if( qPocket.matchDataType( "DATE" ) ){
          objKeyValueWidget.keyValue.topLabel = qPocket.getName();
          objKeyValueWidget.keyValue.content  = engine.findData(qPocket) ?
            (engine.findData(qPocket).toString()) : `<i><font color="#aaaaaa">null</font></i>`;
          objKeyValueWidget.keyValue.contentMultiline = "false";
      }else if( qPocket.matchDataType( "DATETIME" ) ){
          objKeyValueWidget.keyValue.topLabel = qPocket.getName();
          objKeyValueWidget.keyValue.content  = engine.findData(qPocket) ?
            (engine.findData(qPocket).toString()) : `<i><font color="#aaaaaa">null</font></i>`;
          objKeyValueWidget.keyValue.contentMultiline = "false";
      }else if( qPocket.matchDataType( "FILE" ) ){
          let qFiles = engine.findData(qPocket);
          let numOfFiles = qFiles.size() - 0;
          let strFiles = "";
          for( let j = 0; j < numOfFiles; j++ ){
            strFiles += escaper.escapeHtml(qFiles.get(j).getName());
            if( j !== numOfFiles - 1 ){ strFiles += "<br>";}
          }
          objKeyValueWidget.keyValue.topLabel = qPocket.getName();
          objKeyValueWidget.keyValue.content  = engine.findData(qPocket) ?
            strFiles : `<i><font color="#aaaaaa">null</font></i>`;
          objKeyValueWidget.keyValue.contentMultiline = "true";
      }else if( qPocket.matchDataType( "QUSER" ) ){
          objKeyValueWidget.keyValue.topLabel = qPocket.getName();
          objKeyValueWidget.keyValue.content  = engine.findData(qPocket) ?
            escaper.escapeHtml(engine.findData(qPocket).getName()) : `<i><font color="#aaaaaa">null</font></i>`;
          objKeyValueWidget.keyValue.contentMultiline = "false";
      }else if( qPocket.matchDataType( "QGROUP" ) ){
          objKeyValueWidget.keyValue.topLabel = qPocket.getName();
          objKeyValueWidget.keyValue.content  = engine.findData(qPocket) ?
            escaper.escapeHtml(engine.findData(qPocket).getName()) : `<i><font color="#aaaaaa">null</font></i>`;
          objKeyValueWidget.keyValue.contentMultiline = "false";
      }else if( qPocket.matchDataType( "LIST" ) ){
          objKeyValueWidget.keyValue.topLabel = qPocket.getName();
          objKeyValueWidget.keyValue.content  = engine.findData(qPocket) ?
            (`<i><font color="#aaaaaa">` + engine.findData(qPocket).size() + ` rows</font></i>`) : `<i><font color="#aaaaaa">null</font></i>`;
          objKeyValueWidget.keyValue.contentMultiline = "false";
      }else if( qPocket.matchDataType( "DISCUSSION" ) ){
        engine.log( " AutomatedTask RuntimeWarning: " +
                    " {B2: FieldName} contains DISCUSSION type (skipped): " + arrFieldNames[i] );
        continue;
      }else{
        engine.log( " AutomatedTask RuntimeWarning: " +
                    " {B2: FieldName} contains unsupported type (skipped): " + arrFieldNames[i] );
        continue;
      }
      objDataSection.widgets.push( objKeyValueWidget );     // push Widget to DataSection ###
      request1Obj.cards[0].sections.push( objDataSection ); // push DataSection to Cards[0].Sections ###
    }
  }

  if( strApplicationRoot !== "" ){
    let objFooterSection = {};          // ### Footer Section
        objFooterSection.widgets = [];

    let objTextButtonWidget = {};         // ### Button Widget
        objTextButtonWidget.buttons = [];
        objTextButtonWidget.buttons[0] = {};
        objTextButtonWidget.buttons[0].textButton = {};
        objTextButtonWidget.buttons[0].textButton.text = "Desktop View";
        objTextButtonWidget.buttons[0].textButton.onClick = {};
        objTextButtonWidget.buttons[0].textButton.onClick.openLink = {};
        objTextButtonWidget.buttons[0].textButton.onClick.openLink.url =
          `${strApplicationRoot}PE/Workitem/list?processInstanceId=${strProcessInstanceId}`;
        objTextButtonWidget.buttons[1] = {};
        objTextButtonWidget.buttons[1].textButton = {};
        objTextButtonWidget.buttons[1].textButton.text = "Mobile View";
        objTextButtonWidget.buttons[1].textButton.onClick = {};
        objTextButtonWidget.buttons[1].textButton.onClick.openLink = {};
        objTextButtonWidget.buttons[1].textButton.onClick.openLink.url =
          `${strApplicationRoot}SP/PE/ProcessInstance/view?processInstanceId=${strProcessInstanceId}`;
    objFooterSection.widgets.push( objTextButtonWidget );   // push Widget to FooterSection ###

    request1Obj.cards[0].sections.push( objFooterSection ); // push FooterSection to Cards[0].Sections ###
  }
}
// engine.log( JSON.stringify( request1Obj ) ); // for debug

/// access to "Google Chat Bot"
let request1Uri = strWebhookUrl;
let request1    = httpClient.begin(); // HttpRequestWrapper
    request1    = request1.body( JSON.stringify( request1Obj ), "application/json" );
// request1, try
const response1     = request1.post( request1Uri ); // HttpResponseWrapper
const arrRequest1Uri = request1Uri.split('?'); // "key" will not be loged
engine.log( " AutomatedTask ApiRequest1 Start: " + arrRequest1Uri[0] );
const response1Code = response1.getStatusCode() + "";
const response1Body = response1.getResponseAsString() + "";
engine.log( " AutomatedTask ApiResponse Status: " + response1Code );
if( response1Code !== "200"){
  throw new Error( "\n AutomatedTask UnexpectedResponseError: " +
                    response1Code + "\n" + response1Body + "\n" );
}
// response1, parse
/* 
engine.log( response1Body ); // for debug
{
  "name": "spaces/AAAA4xxxxx4/messages/EdtRhyyyyy4.EdtRhyyyyy4",
  "sender": {
    "name": "users/114029999953014888889",
    "displayName": "TestWebhook",
    "avatarUrl": "",
    "email": "",
    "domainId": "",
    "type": "BOT",
    "isAnonymous": false
  },
  "text": "test\n*bold text*\n`backquotes`",
  "cards": [],
  "previewText": "",
  "annotations": [],
  "thread": {
    "name": "spaces/AAAA4xxxxx4/threads/EdtRhyyyyy4"
  },
  "space": {
    "name": "spaces/AAAA4xxxxx4",
    "type": "ROOM",
    "singleUserBotDm": false,
    "threaded": true,
    "displayName": "TestSpace",
    "legacyGroupChat": false
  },
  "fallbackText": "",
  "argumentText": "test\n*bold text*\n`backquotes`",
  "attachment": [],
  "createTime": "2022-03-08T07:33:33.109455Z",
  "lastUpdateTime": "2022-03-08T07:33:33.109455Z"
}
*/
const response1Obj   = JSON.parse( response1Body );
const strMessageName = response1Obj.name;
const strSpaceName   = response1Obj.space.name;


//// == Data Updating / ワークフローデータへの代入 ==
if( strPocketMessageName !== null ){
  engine.setData( strPocketMessageName, strMessageName );
  // Example: spaces/AAAAAAAAAAA/messages/BBBBBBBBBBB.BBBBBBBBBBB
}
if( strPocketSpaceName !== null ){
  engine.setData( strPocketSpaceName, strSpaceName );
  // Example: spaces/AAAAAAAAAAA
}


} //////// END "main()" /////////////////////////////////////////////////////////////////


/*
Notes:
- Integrates between the Workflow App and the Google Chat Space.
    - When the process reaches this automated task, a message is automatically posted to Google Chat.
    - Also be used as a means of sharing business data with "non-users" of the workflow platform.
    - The automated task posts an arbitrary string of text (message body).
    - It is also possible to attach a Card with the business data stored in the process.
- Can be used for various timeline chatting.
    - Automatically report "Order information" to SPACE for management team.
    - Automatically report "Business progress" to SPACE for communication with customers.
- The Incoming Webhook function of Google Chat is used.
    - The application administrator only needs to set the "Webhook URL" for the automatic process.
    - "Webhook URL" is obtained from the "Manage Webhook" menu of the relevant "Space".
    - e.g: `https://chat.googleapis.com/v1/spaces/AAAA4xxxxx4/messages?key=AIzaSxxxxxhCZtEyyyyyMm-WEzzzzzPzqKqqsHI&token=UMlLOxxxxxw2JgtyyyyyWLqMizzzzzSiPnBxxxxxENI%3D`
APPENDIX
- This automated task communicates via the "Incoming Webhook" of Bot. (Asynchronous Messages)
    - No user authentication or user authorization (OAuth2 settings, etc.) configuration is required.
    - https://developers.google.com/chat/how-tos/webhooks
    - Communication is done by a different mechanism from "Google Chat API".
        - https://developers.google.com/chat/api/reference/rest/v1/spaces.messages/create
- Google Chat "Spaces" were previously called "Rooms" (2021-09)
    - This automated task assumes a "Space" created in your Google Workspace account.
    - Unlike Group Messages, Spaces can add or remove users at any time.
    - Unlike Group Messages, Spaces also allow you to talk to people outside of your organization.
        - Invited external users can join from "Browse Spaces".
        - https://support.google.com/chat/answer/7653963
- Message body
    - Posted body messages are sent in plain text.
    - However, decorations such as Bold (with asterisks) and Code (with backquotes) are also possible.
    - https://developers.google.com/chat/api/guides/message-formats/basic
- Process Data
    - If the app manager specifies a "field name" for the automatic process, the case data will also be posted.
    - A simple Card is additionally displayed at the bottom of the "Message Body".
    - A Process Title is automatically added to the header of the Card.
    - This is useful for sharing information with people who do not have an account on the workflow platform (non-users).

Notes-ja:
- ワークフローアプリとGoogleチャット Space のデータ連携を実現します。
    - 案件がこの自動工程に到達した際、Googleチャットに対して自動的に投稿します。
    - ワークフロー基盤の「非ユーザ」と業務データを共有する手段としても利用できます。
    - 自動工程は、任意の文字列(メッセージ本文)を投稿します。
    - 案件内に格納されている案件データをCard添付することも可能です。
- 様々タイムライン・コミュニケーションに利用できます。
    - マネージメントチーム用Spaceに「受注情報」を自動報告する
    - 顧客とのコミュニケーション用のSpaceで「業務進捗」を自動報告する
- GoogleチャットのWebhook機能(着信Webhook)が利用されます。
    - アプリ管理者は、自動工程に "Webhook URL" をセットするだけで利用できます。
    - "Webhook URL" は、当該「スペース」の「Webhook を管理」メニューから取得します。
    - 例: `https://chat.googleapis.com/v1/spaces/AAAA4xxxxx4/messages?key=AIzaSxxxxxhCZtEyyyyyMm-WEzzzzzPzqKqqsHI&token=UMlLOxxxxxw2JgtyyyyyWLqMizzzzzSiPnBxxxxxENI%3D`
APPENDIX-ja
- この自動工程では、Bot の「着信Webhook」を介して通信されます。(非同期メッセージ)
    - ユーザ認証やユーザ認可(OAuth2設定等)の設定は必要ありません。
    - https://developers.google.com/chat/how-tos/webhooks
    - "Google Chat API" とは異なる仕組みで通信されます。
        - https://developers.google.com/chat/api/reference/rest/v1/spaces.messages/create
- Googleチャットの「スペース」は、以前は「チャットルーム」と呼称されていました(~2021-09)
    - この自動工程は、Google Workspace アカウントで作成された「スペース」を前提としています。
    - 「スペース」は、「グループ メッセージ」と異なり、いつでもユーザ追加削除が可能です。
    - 「スペース」は、「グループ メッセージ」と異なり、社外ユーザとの会話も可能です。
        - 招待された社外ユーザは「スペースをブラウジング」から参加します。
        - https://support.google.com/chat/answer/7653963
- メッセージ本文
    - 投稿される本文メッセージはプレーンテキストで送信されます。
    - ただし、太字(アスタリスク囲み)やCode(バッククオート囲み)といった装飾も可能です。
    - https://developers.google.com/chat/api/guides/message-formats/basic
- 案件データ
    - アプリ管理者が、自動工程に "フィールド名" を指定した場合、案件データも投稿されます。
    - シンプルな Card が「メッセージ本文」の下部に追加表示されます。
    - Card のヘッダー部には、案件タイトルが自動的に追加されます。
    - ワークフロー基盤にアカウントを持たない人(非ユーザ)との情報共有に便利です。
*/

Download

2021-03-11 (C) Questetra, Inc. (MIT License)
https://support.questetra.com/addons/google-chat-space-create-message-2022/
The Add-on import feature is available with Professional edition.
Freely modifiable JavaScript (ECMAScript) code. No warranty of any kind.

Notes

  • Integrates between the Workflow App and the Google Chat Space.
    • When the process reaches this automated task, a message is automatically posted to Google Chat.
    • Also can be used as a means of sharing business data with non-users of the workflow platform.
    • The automated task posts an arbitrary string of text (message body).
    • It is also possible to attach a Card with the business data stored in the process.
  • Can be used for a variety of timeline communications.
    • Automatical reporting of order information to a Space for the management team.
    • Automatical reporting of business progress to a Space for communication with customers.
  • The Incoming Webhook function of Google Chat is used.
    • The application administrator only needs to set the Webhook URL for the automatic process.
    • The Webhook URL is obtained from the “Manage Webhook” menu of the relevant Space.
    • e.g: https://chat.googleapis.com/v1/spaces/AAAA4xxxxx4/messages?key=AIzaSxxxxxhCZtEyyyyyMm-WEzzzzzPzqKqqsHI&token=UMlLOxxxxxw2JgtyyyyyWLqMizzzzzSiPnBxxxxxENI%3D
  • If the phone number has been posted, you can click to call from your smartphone Gmail app.

Capture

Posts a message in Google Chat "Space". Bold decoration (by asterisks) and Inline-code decoration (by backticks) are also applied as they are. Also possible to attach some Process Data in "card format".

Appendix

  • This automated task communicates via the bot’s Incoming Webhook (asynchronous messages).
  • Google Chat “Spaces” were previously called “Rooms” (2021-09)
    • This automated task assumes a Space created in your Google Workspace account.
    • Unlike Group Messages, Spaces can add or remove users at any time.
    • Unlike Group Messages, Spaces also allow you to talk to people outside of your organization.
  • Message body
  • Process Data
    • If the app manager specifies a field name for the automatic process, the case data will also be posted.
    • A simple Card is additionally displayed at the bottom of the message body.
    • A Process Title is automatically added to the header of the Card.
    • This is useful for sharing information with people who do not have an account on the workflow platform (non-users).

See also

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Scroll to Top

Discover more from Questetra Support

Subscribe now to keep reading and get access to the full archive.

Continue reading