Google Chat Bot. Properties

Перед вами новий пост і знов піде мова про розробку ботів для Google Chat’ів. Сьогодні ми навчимося додавати статистику використання до нашого бота. Відразу невеликий спойлер — ми будемо вести статистику в боті, не виходячи за межі нашого коду та не використовуючи інші сервіси. Ми познайомимось з таким функціоналом як Properties. Спочатку я хочу розповісти … Continue reading Google Chat Bot. Properties

Янв 26, 2025 - 16:01
 0
Google Chat Bot. Properties

Перед вами новий пост і знов піде мова про розробку ботів для Google Chat’ів. Сьогодні ми навчимося додавати статистику використання до нашого бота.

Відразу невеликий спойлер — ми будемо вести статистику в боті, не виходячи за межі нашого коду та не використовуючи інші сервіси. Ми познайомимось з таким функціоналом як Properties.

Спочатку я хочу розповісти що я хочу отримати у якості результату. За моїм задумом, бот буде рахувати скільки та які команди використовувалася, а також віддавати цю статистику за спеціальним запитом до бота, крім цього, я хочу щоб користувачі могли теж отримувати свою статистику використання бота.

Тож ось вам перелік даних які будемо збирати, не питайте чому саме ці дані, бо у самурая немає цілі, є лише шлях:

  • основні події: повідомлення, кліки, додавання та вилучення зі спейсу
  • виклик slash-команд – які команди використовували та скільки разів
  • виклик функцій, які прив’язані до кнопок на картках

Збір та організація статистики

Перше питання, звідкіля брати потрібну інформацію? Вся інформація про події в нас доступна у об’єкті event який нам приходить до 4-х ключових функцій які в нас лежать у файлі Code.gs:

/**
 * Responds to a MESSAGE event in Google Chat.
 *
 * @param {Object} event the event object from Google Chat
 */
function onMessage(event) {
  // ...
}

/**
 * Responds to a CARD_CLICKED event in Google Chat.
 *
 * @param {Object} event the event object from Google Chat
 */
function onCardClick(event) {
  // ...
}

/**
 * Responds to an ADDED_TO_SPACE event in Google Chat.
 *
 * @param {Object} event the event object from Google Chat
 */
function onAddToSpace(event) {
  // ...
}

/**
 * Responds to a REMOVED_FROM_SPACE event in Google Chat.
 *
 * @param {Object} event the event object from Google Chat
 */
function onRemoveFromSpace(event) {
  // ...
}

Щоб зібрати дані для статистики нам треба лише вбудувати виклик функції, яка і буде збирати дані:

/**
 * Responds to a MESSAGE event in Google Chat.
 *
 * @param {Object} event the event object from Google Chat
 */
function onMessage(event) {
  collectStatisticData(event)
  // ...
}

/**
 * Responds to a CARD_CLICKED event in Google Chat.
 *
 * @param {Object} event the event object from Google Chat
 */
function onCardClick(event) {
  collectStatisticData(event)
  // ...
}

/**
 * Responds to an ADDED_TO_SPACE event in Google Chat.
 *
 * @param {Object} event the event object from Google Chat
 */
function onAddToSpace(event) {
  collectStatisticData(event)
  // ...
}

/**
 * Responds to a REMOVED_FROM_SPACE event in Google Chat.
 *
 * @param {Object} event the event object from Google Chat
 */
function onRemoveFromSpace(event) {
  collectStatisticData(event)
  // ...
}

А ось саму функцію треба ще написати, вона повинна аналізувати наступні дані:

/**
 * Collect event data and count statistics for functions and commands
 *
 * @param {Object} event the event object from Google Chat
 */
function collectStatisticData(event) {
  // MESSAGE or CARD_CLICKED or ADDED_TO_SPACE or REMOVED_FROM_SPACE
  event.type
  // if user used a slash command
  event.message.slashCommand.commandId
  // if user clicked something on the card and call function
  event.common.invokedFunction
}

Тож наче все зрозуміло, тепер нам би розібратися де зберігати цю інформацію, і нам в цьому допоможуть Properties

Робота з Properties

Apps Script мають можливість використовувати 3 типи сховищ для службових даних:

  • ScriptProperties — сховище даних вашого додатку, воно потрібно щоб зберігати загальні налаштування скрипта на кшталт ключів доступу, кредів, тощо
  • UserProperties — сховище даних поточного користувача, воно прив’язано до вашого додатку, його використовують щоб зберігати налаштування користувача відносно вашого додатку, наприклад — вибір метричної системи або якоїсь іншої
  • DocumentProperties — це сховище пов’язане з відкритим документом, це не наш випадок, але і не розповісти про цього я не міг

При використанні слід пам’ятити про обмеження при використанні Properties Service — це 9KB на значення, та 500KB загалом на сховище. Також слід врахувати, що є обмеження на кількість read/write операцій — 50 000 на день для gmail акаунтів там 500 000 для Google Workspace.

Для реалізації мого задуму нам знадобляться наступні методи:

  • PropertiesService.getScriptProperties() — сховище даних вашого додатку, тут будемо зберігати загальну статистику використання
  • PropertiesService.getUserProperties() — сховище даних поточного користувача, тут будемо зберігати статистику користувача

Це key-value сховища, для роботи з якими є декілька методів, серед яких нам наразі потрібні лише getProperty(key) та setProperty(key, value).

Давайте додамо використання сховища ScriptProperties до нашої функції collectStatisticData(event):

/**
 * Collect event data and count statistics for functions and commands
 * 
 * @param {Object} event the event object from Google Chat
 */
function collectStatisticData(event) {
  const scriptProperties = PropertiesService.getScriptProperties();

  let eventCounter = scriptProperties.getProperty(event.type) || 0;

  scriptProperties.setProperty(event.type, parseInt(eventCounter) + 1);

  // Increment slash command counter if applicable
  if (event.message && event.message.slashCommand) {
    let slashCommandCounter = scriptProperties.getProperty('SLASH_' + event.message.slashCommand.commandId) || 0
    scriptProperties.setProperty('SLASH_' + event.message.slashCommand.commandId, parseInt(slashCommandCounter) + 1)
  }

  // Increment invoked function counter if applicable
  if (event.common && event.common.invokedFunction) {
    let functionCounter = scriptProperties.getProperty('FNC_' + event.common.invokedFunction) || 0
    scriptProperties.setProperty('FNC_' + event.common.invokedFunction, parseInt(functionCounter) + 1)
  }
}

Виглядає досить «дивно», та ми ще повернемося до цього функціоналу, давайте подивимось де ми можемо подивитися на дані які ми зберігаємо. Для цього треба перейти до вкладки Project Settings, і проскроліть до розділу Script Properties:

А тепер трошки оптимізуємо наявний спосіб збереження, я пропоную зберігати дані у JSON форматі:

{
  events: {
    MESSAGE: 0,
    CARD_CLICKED: 0,
    ADDED_TO_SPACE: 0,
    REMOVED_FROM_SPACE: 0,
  },
  commands: {},
  functions: {},
}

Тепер внесемо зміни до функції збору статистики:

/**
 * Collect event data and count statistics for functions and commands
 * 
 * @param {Object} event the event object from Google Chat
 */
function collectStatisticData(event) {
  const scriptProperties = PropertiesService.getScriptProperties();

  let statistics = scriptProperties.getProperty('STATS');

  if (statistics) {
    statistics = JSON.parse(statistics)
  } else {
    statistics = {
      events: {
        MESSAGE: 0,
        CARD_CLICKED: 0,
        ADDED_TO_SPACE: 0,
        REMOVED_FROM_SPACE: 0,
      },
      commands: {},
      functions: {},
    }
  }

  statistics[event.type]++

  // Increment slash command counter if applicable
  if (event.message && event.message.slashCommand) {
    const { commandId } = event.message.slashCommand
    statistics.commands[commandId] = (statistics.commands[commandId] || 0) + 1
  }

  // Increment invoked function counter if applicable
  if (event.common && event.common.invokedFunction) {
    const functionName = event.common.invokedFunction
    statistics.functions[functionName] = (statistics.functions[functionName] || 0) + 1
  }

  scriptProperties.setProperty('STATS', JSON.stringify(statistics))
}

Зверніть увагу в цьому прикладі, що дані які ми отримуємо зі сховища то є текст, і нам ще треба його розпарсити. Звісно перед зберіганням треба JSON знов перетворити на текст. І це ще досить непогано, бо у попередній версії цієї функції нам треба була кожного разу парсити текст, щоб працювати з цифрами