Source: reply_keyboard_markup.js

/**
 * @module reply_keyboard_markup
 */
'use strict';

import { ValidationError } from 'vvlad1973-error-definitions';
import { KeyboardButtonPollTypes } from './enums.js';

/**
 * @typedef {Object} module:reply_keyboard_markup.WebAppInfo - Describes a Web App
 * @property {string} url - An HTTPS URL of a Web App to be opened with additional data
 *     as specified in Initializing Web Apps
 */


//TODO: To implement methods addRequestUserButton()  
//TODO: To implement methods insertRequestUserButton()  
//TODO: To implement methods addRequestChatButton()  
//TODO: To implement methods insertRequestChatButton()  

/**
 * @typedef {Object} module:reply_keyboard_markup.KeyboardButton - This object
 *     represents one button of the reply keyboard. For simple text buttons,
 *     String can be used instead of this object to specify the button text.
 *     The optional fields web_app, request_user, request_chat, request_contact,
 *     request_location, and request_poll are mutually exclusive
 * @property {string} text -Text of the button. If none of the optional fields
 *     are used, it will be sent as a message when the button is pressed
 * @property {module:reply_keyboard_markup.KeyboardButtonRequestUser} [request_user] -
 *     If specified, pressing the button will open a list of suitable users.
 *     Tapping on any user will send their identifier to the bot in a “user_shared”
 *     service message. Available in private chats only
 * @property {module:reply_keyboard_markup.KeyboardButtonRequestChat} [request_chat] -
 *     If specified, pressing the button will open a list of suitable chats.
 *     Tapping on a chat will send its identifier to the bot in a “chat_shared”
 *     service message. Available in private chats only
 * @property {boolean} [request_contact] - If True, the user's phone number
 *     will be sent as a contact when the button is pressed. Available in
 *     private chats only
 * @property {boolean} [request_location] - If True, the user's current
 *     location will be sent when the button is pressed. Available in private
 *     chats only
 * @property {module:reply_keyboard_markup.KeyboardButtonPollType} [request_poll] -
 *     If specified, the user will be asked to create a poll and send it to the
 *     bot when the button is pressed. Available in private chats only
 * @property {module:reply_keyboard_markup.WebAppInfo} [web_app] - If specified,
 *     the described Web App will be launched when the button is pressed. The
 *     Web App will be able to send a “web_app_data” service message. Available
 *     in private chats only
 */

/**
 * @class Implements structure for operations with Telegram bot's commands
 *
 * @description This object represents a custom keyboard with reply options
 */
export class ReplyKeyboardMarkup {
  /**
   * @property {Array.<module:reply_keyboard_markup.KeyboardButton[]>} - Array
   *     of button rows, each represented by an Array of KeyboardButton objects
   * @property {boolean} [is_persistent] - Requests clients to always show the
   *     keyboard when the regular keyboard is hidden. Defaults to false, in
   *     which case the custom keyboard can be hidden and opened with a
   *     keyboard icon
   *  @property {boolean} [resize_keyboard] - Requests clients to resize the
   *     keyboard vertically for optimal fit (e.g., make the keyboard smaller
   *     if there are just two rows of buttons). Defaults to false, in which
   *     case the custom keyboard is always of the same height as the app's
   *     standard keyboard
   *  @property {boolean} [one_time_keyboard] - Requests clients to hide the
   *     keyboard as soon as it's been used. The keyboard will still be
   *     available, but clients will automatically display the usual
   *     letter-keyboard in the chat - the user can press a special button in
   *     the input field to see the custom keyboard again. Defaults to false
   *  @property {string} [input_field_placeholder] - The placeholder to be
   *     shown in the input field when the keyboard is active; 1-64 characters
   *  @property {boolean} [selective] - Use this parameter if you want to show
   *     the keyboard to specific users only. Targets: 1) users that are @mentioned
   *     in the text of the Message object; 2) if the bot's message
   *     is a reply (has reply_to_message_id), sender of the original message
   *     Example: A user requests to change the bot's language, bot replies to
   *     the request with a keyboard to select the new language. Other users in
   *     the group don't see the keyboard
   */
  keyboard = [];
  is_persistent;
  resize_keyboard;
  one_time_keyboard;
  input_field_placeholder;
  selective;

  /**
   * @returns {module:reply_keyboard_markup.ReplyKeyboardMarkup} Instance of the ReplyKeyboardMarkup class
   */
  /**
   * Append one or more rows to the keyboard
   *
   * @param {number} [number] - Number of rows to append. If not specified,
   *     wlll append one row
   */
  appendRows(number = 1) {
    for (let i = 0; i < number; i++) {
      this.keyboard.push([]);
    }
  }

  /**
   * Insert one or more rows to the keyboard
   *
   * @param {number} [index] - Number of position to insert rows (0-based).
   *     If not specified, rows will insert at the first position
   * @param {number} [number] - Number of rows to append. If not specified,
   *     wlll insert one row
   */
  insertRows(index = 0, number = 1) {
    for (let i = 0; i < number; i++) {
      this.keyboard.splice(index, 0, []);
    }
  }

  /**
   * Delete a row from the keyboard
   *
   * @param {number} [index] - Index of row to delete (0-based). If not
   *     specified, will delete the last row
   */
  deleteRow(index = -1) {
    if (index == -1) {
      this.keyboard.splice(this.keyboard.length - 1);
    } else if (index < this.keyboard.length) {
      this.keyboard.splice(index, 1);
    } else throw new ValidationError('Keyboard row does not exist');
  }

  /**
   * Remove a button from a specific row
   *
   * @param {number} [row] - Number of row of button to delete (0-based). 
   *     If not specified, will delete a button from the last row
   * @param {number} [column] - Number of column of button to delete
   *     (0-based). If not specified, will delete the last button from the
   *     row
   */
  deleteButton(row = -1, column = -1) {

    if (row === -1) row = this.keyboard.length - 1;

    if (column === -1)
      column = this.keyboard[row].length - 1;

    if (row < this.keyboard.length) {
      if (column < this.keyboard[row].length)
        this.keyboard[row].splice(column, 1)
      else
        throw new ValidationError('The button does not exist');
    } else
      throw new ValidationError('Keyboard row does not exist');
  };

  /**
   * Insert a text button to reply keyboard
   *
   * @param {string} text - Text label for the button
   * @param {number} [row] - Row number to insert button (0-based). If
   *     not specified, the button will insert to the first row
   * @param {number} [column] - Number of column of button to insert
   *     (0-based). If not specified, the button will insert to the first
   *     position of the row
   */
  insertTextButton(text, row = 0, column = 0) {
    if (this.keyboard.length === 0)
      this.keyboard.push([]);

    if (row == -1) row = this.keyboard.length - 1;

    if (row < this.keyboard.length) {
      this.keyboard[row].splice(column, 0, { text: text });
    } else throw new ValidationError('Keyboard row does not exist');
  }

  /**
   * Append a text button to reply keyboard
   *
   * @param {string} text - Text label for the button
   * @param {number} [row] - Row number to append button (0-based). If
   *     not specified, the button will append to the last row
   */
  appendTextButton(text, row = -1) {
    if (this.keyboard.length === 0)
      this.keyboard.push([]);

    if (row == -1) row = this.keyboard.length - 1;

    if (row < this.keyboard.length) this.keyboard[row].push({ text: text });
    else throw new ValidationError('Keyboard row does not exist');
  }

  /**
   * Insert a request contact button to reply keyboard
   *
   * @param {string} text - Text label for the button
   * @param {number} [row] - Row number to insert button (0-based). If
   *     not specified, the button will insert to the first row
   * @param {number} [column] - Number of column of button to insert
   *     (0-based). If not specified, the button will insert to the first
   *     position of the row
   */
  insertRequestContactButton(text, row = 0, column = 0) {
    if (this.keyboard.length === 0)
      this.keyboard.push([]);

    if (row == -1) row = this.keyboard.length - 1;

    if (row < this.keyboard.length) {
      this.keyboard[row].splice(column, 0, {
        text: text,
        request_contact: true,
      });
    } else throw new ValidationError('Keyboard row does not exist');
  }

  /**
   * Append a request contact button to reply keyboard
   *
   * @param {string} text - Text label for the button
   * @param {number} [row] - Row number to append button (0-based). If
   *     not specified, the button will append to the last row
   */
  appendRequestContactButton(text, row = -1) {
    if (this.keyboard.length === 0)
      this.keyboard.push([]);

    if (row == -1) row = this.keyboard.length - 1;

    if (row < this.keyboard.length)
      this.keyboard[row].push({ text: text, request_contact: true });
    else throw new ValidationError('Keyboard row does not exist');
  }

  /**
   * Insert a request location button to reply keyboard
   *
   * @param {string} text - Text label for the button
   * @param {number} [row] - Row number to insert button (0-based). If
   *     not specified, the button will insert to the first row
   * @param {number} [column] - Number of column of button to insert
   *     (0-based). If not specified, the button will insert to the first
   *     position of the row
   */
  insertRequestLocationButton(text, row = 0, column = 0) {
    if (this.keyboard.length === 0)
      this.keyboard.push([]);

    if (row == -1) row = this.keyboard.length - 1;

    if (row < this.keyboard.length) {
      this.keyboard[row].splice(column, 0, {
        text: text,
        request_location: true,
      });
    } else throw new ValidationError('Keyboard row does not exist');
  }

  /**
   * Append a request location button to inline keyboard
   *
   * @param {string} text - Text label for the button
   * @param {number} [row] - Row number to append button (0-based). If
   *     not specified, the button will append to the last row
   */
  appendRequestLocationButton(text, row = -1) {
    if (this.keyboard.length === 0)
      this.keyboard.push([]);

    if (row == -1) row = this.keyboard.length - 1;

    if (row < this.keyboard.length)
      this.keyboard[row].push({ text: text, request_location: true });
    else throw new ValidationError('Keyboard row does not exist');
  }

  /**
   * Insert a Web App button to reply keyboard
   *
   * @param {string} text - Text label for the button
   * @param {string} url - An HTTPS URL of a Web App to be opened with
   *     additional data as specified in Initializing Web Apps
   * @param {number} [row] - Row number to insert button (0-based). If
   *     not specified, the button will insert to the first row
   * @param {number} [column] - Number of column of button to insert
   *     (0-based). If not specified, the button will insert to the first
   *     position of the row
   */
  insertWebAppButton(text, url, row = 0, column = 0) {
    if (this.keyboard.length === 0)
      this.keyboard.push([]);

    if (row == -1) row = this.keyboard.length - 1;

    if (row < this.keyboard.length) {
      this.keyboard[row].splice(column, 0, {
        text: text,
        web_app: {
          url: url,
        },
      });
    } else throw new ValidationError('Keyboard row does not exist');
  }

  /**
   * Append a Web App button to reply keyboard
   *
   * @param {string} text - Text label for the button
   * @param {string} url - An HTTPS URL of a Web App to be opened with
   *     additional data as specified in Initializing Web Apps
   * @param {number} [row] - Row number to append button (0-based). If
   *     not specified, the button will append to the last row
   */
  appendWebAppButton(text, url, row = -1) {
    if (this.keyboard.length === 0)
      this.keyboard.push([]);

    if (row == -1) row = this.keyboard.length - 1;

    if (row < this.keyboard.length)
      this.keyboard[row].push({
        text: text,
        web_app: {
          url: url,
        },
      });
    else throw new ValidationError('Keyboard row does not exist');
  }

  /**
   * Insert a request poll button to reply keyboard
   *
   * @param {string} text - Text label for the button
   * @param {module:enums.KeyboardButtonPollTypes} [pollType] - If specified, 
   *     the user will be asked to create a poll and send it to the bot when
   *     the button is pressed. Available in private chats only
   * @param {number} [row] - Row number to insert button (0-based). If
   *     not specified, the button will insert to the first row
   * @param {number} [column] - Number of column of button to insert
   *     (0-based). If not specified, the button will insert to the first
   *     position of the row
   */
  insertRequestPollButton(text, pollType, row = 0, column = 0) {
    if (this.keyboard.length === 0)
      this.keyboard.push([]);

    if (row == -1) row = this.keyboard.length - 1;

    if (row < this.keyboard.length) {
      this.keyboard[row].splice(column, 0, {
        text: text,
        request_poll: {
          type: pollType,
        },
      });
    } else throw new ValidationError('Keyboard row does not exist');
  }

  /**
   * Append a request poll button to reply keyboard
   *
   * @param {string} text - Text label for the button
   * @param {module:enums.KeyboardButtonPollTypes} [pollType] - If specified, 
   *     the user will be asked to create a poll and send it to the bot when
   *     the button is pressed. Available in private chats only
   * @param {number} [row] - Row number to append button (0-based). If
   *     not specified, the button will append to the last row
   */
  appendRequestPollButton(text, pollType = KeyboardButtonPollTypes.ANY, row = -1) {
    if (this.keyboard.length === 0)
      this.keyboard.push([]);

    if (row == -1) row = this.keyboard.length - 1;

    if (row < this.keyboard.length)
      this.keyboard[row].push({
        text: text,
        request_poll: {
          type: pollType,
        },
      });
    else throw new ValidationError('Keyboard row does not exist');
  }
}

export default ReplyKeyboardMarkup;