/PoshBot.GChat.Backend

Google Chat backend for PoshBot leveraging a Google Sheet as a message queue with Apps Script as the bot endpoint managing the Sheet contents

Primary LanguagePowerShellApache License 2.0Apache-2.0

PoshBot.GChat.Backend

AppVeyor Build Status

Google Chat backend for PoshBot leveraging a Google Sheet as a message queue with Apps Script as the bot endpoint managing the Sheet contents

Prerequisites

To start using PoshBot with Google Chat, you'll need to have a few things set up/installed first:

  1. PSGSuite
    • Documentation
    • Miniumum required version: 2.13.0
    • Developer Console Project must have the following API's enabled:
      • Hangouts Chat API (enabled and configured)
      • Sheets API
  2. Google Apps Script Sheet MQ
  3. PoshBot
  4. PowerShell version 5 or greater
    • PoshBot is built almost entirely with PowerShell classes which were first introduced in PowerShell 5.

Setting up the GChat backend for PoshBot

Installing the PoshBot.GChat.Backend module

You can install the PoshBot.GChat.Backend module directly from the PowerShell Gallery. If you do not have PSGSuite and/or PoshBot installed already, this will also install them:

Install-Module PoshBot.GChat.Backend -Scope CurrentUser

Starting up PoshBot

Here's a sample script that I use to get PoshBot started. Some important Google Chat/PSGSuite specific configuration items to note are:

  1. BotAdmins: GChat bot admins need to be listed using their primary email address.
  2. ConfigName: If you only use one config with PSGSuite, you can exclude this from the BackendConfiguration and it will retrieve the correct config name during backend instantiation
  3. SheetId: This is 100% necessary! Without the SheetId, the backend will not know where to connect. The PSGSuite AdminEmail account will need edit access to this Sheet as well.
  4. PollingFrequency: This defaults to 1500ms. You can exclude this from the BackendConfiguration if that is fine. Do not set the PollingFrequency below 1 second otherwise you risk being rate limited by the default Sheets Read quota of 100 Reads per 100 seconds.
# Import necessary modules
Import-Module PoshBot
Import-Module PoshBot.GChat.Backend

# Store config path in variable
$configPath = 'E:\Scripts\PoshBot\GChat\GChatConfig.psd1'

# Create hashtable of parameters for New-PoshBotConfiguration
$botParams = @{
    # The friendly name of the bot instance
    Name                   = 'GChatBot'
    # The primary email address(es) of the admin(s) that can manage the bot
    BotAdmins              = @('admin@domain.com', 'coadmin@domain.com')
    # Universal command prefix for PoshBot.
    # If the message includes this at the start, PoshBot will try to parse the command and 
    # return an error if no matching command is found
    CommandPrefix          = '!'
    # PoshBot log level.
    LogLevel               = 'Verbose'
    # The path containing the configuration files for PoshBot
    ConfigurationDirectory = 'E:\Scripts\PoshBot\GChat'
    # The path where you would like the PoshBot logs to be created
    LogDirectory           = 'E:\Scripts\PoshBot\GChat'
    # The path containing your PoshBot plugins
    PluginDirectory        = 'E:\Scripts\PoshBot\Plugins'

    BackendConfiguration   = @{
        # This is the PSGSuite config name that you would like the GChat backend to run under.
        # This config needs to have access to the Sheet set up as the Message Queue
        ConfigName       = "mydomain"
        # This is the FileID of the Sheet set up as the Message Queue
        SheetId          = "1H7mJoKflklakoJKDSwo923lsdO5sK3mjg"
        # How frequently you'd like to poll the Sheet for new messages.
        # If this is greater than 1000, it's treated as milliseconds, otherwise it's treated as seconds
        PollingFrequency = 1500
        # The friendly name for the backend
        Name             = 'GChatBackend'
    }
}

# Create the bot backend
$backend = New-PoshBotGChatBackend -Configuration $botParams.BackendConfiguration

# Create the bot configuration
$myBotConfig = New-PoshBotConfiguration @botParams

# Save bot configuration
Save-PoshBotConfiguration -InputObject $myBotConfig -Path $configPath -Force

# Create the bot instance from the backend and configuration path
$bot = New-PoshBotInstance -Backend $backend -Path $configPath

# Start the bot
$bot | Start-PoshBot

Running PoshBot as a service

Once you start PoshBot, it will hold the session open. The easiest way to have PoshBot running without tying up a visible PowerShell console is to run your Start script as a service.

Here's a quick guide to installing PoshBot as a service using NSSM: https://poshbot.readthedocs.io/en/latest/guides/run-poshbot-as-a-service/

NOTE: PSGSuite configurations are typically tied to the user who created them. Make sure you update the service to run as that account. If you are planning on using a service account to run the service, please create a PSGSuite configuration in the context of that service account so it is able to decrypt the configuration while running as a service.

Pros and cons with using Google Chat with PoshBot versus Slack

Google Chat and Slack have a number of differences in regards to event types and message widgets. Use this to guide your own PoshBot plugin development to ensure that both the user experience and returned command results match expectations no matter what ChatOps client you're using!

API Communication

Google Chat uses various endpoints, but does not have a WebSocket connection type equivalent to Slack's RealTimeMessaging API that PoshBot's Slack implementation uses.

This means that Google only sends events to the Bot endpoint if...

  • the message was sent via DM to the bot directly or...
  • the message was sent in a room the bot is a member of and the bot is tagged, i.e. @PoshBot !help

Reactions

Google Chat currently does not support adding reactions to messages, nor does it emit events when reactions are added. Due to this caveat, PoshBot is unable to signal to the sender that it is currently processing the message (indicated by a gear in Slack), the message was processed successfully (green check mark), or any others (i.e. warnings).

Card Formatting

Google Chat does not support certain card widgets that Slack does and supports others Slack does not. There is logic in place in the GChat Backend to provide a best-effort translation, but results may vary.

To assist with developing PoshBot plugins compatible with multiple backends, the module PoshBot.GChat.Backend comes with a helper function New-PoshBotGChatCardResponse. This function includes the same parameters as New-PoshBotCardResponse, while also supporting pipeline input of Google Chat widgets the same as you would use with Send-GSChatMessage.

Here's an example from my Plex plugin (unreleased) that shows the current process information for Plex Media Server:

if ($procs = Get-Process "Plex Media Server" -ErrorAction SilentlyContinue) {
    foreach ($proc in $procs) {
        $Fields = @{
            ProcName  = $proc.Name
            PID       = $proc.Id
            StartTime = $proc.StartTime.ToString("yyyy-MM-dd HH:mm:ss")
        }
        $Fields.Keys | ForEach-Object {
            $title = $_
            Add-GSChatKeyValue -TopLabel $title -Content $Fields[$title] -Icon CONFIRMATION_NUMBER_ICON
        } | Add-GSChatCardSection -SectionHeader "Process Details" | Add-GSChatCard | New-PoshBotGChatCardResponse -Text "Plex is running!" -Fields $fields
    }
}
else {
    New-PoshBotTextResponse -Text "*Plex Media Server is not currently running!* Type ``plex start`` to start Plex"
}

When plex status is ran from Slack, it returns the following...

Slack command example

... and when ran from Google Chat...

Google Chat command example

Interactive Cards

Slack's implementation of interactive cards necessitates an API endpoint to send Card Clicked events. Because of this, the Slack backend and its use of the RTM API prevents interactive cards from being usable. Google Chat, however, sends every event over to your desired endpoint (Sheets MQ in the case of this backend implementation).

To help ease development around this, I've created a sample PoshBot plugin for anyone to fork and customize to their liking. Check it out here: PoshBot.GChat.EventHandler. You can also see the following for example Event Handlers with the minimum configuration:

function AddedToSpace {
    [PoshBot.BotCommand(
        HideFromHelp = $true,
        Command = $false,
        TriggerType = 'Event',
        MessageType = 'Message',
        MessageSubType = 'ChannelJoined'
    )]
    [CmdletBinding()]
    param(
        [parameter(Position = 0,ValueFromRemainingArguments = $true)]
        [string[]]$Arguments
    )
    $originalMessage = ConvertFrom-Json $Global:PoshBotContext.ParsedCommand.CommandString
    Import-Module PSGSuite -MinimumVersion "2.13.0"
    Import-Module PoshBot.GChat.Backend -MinimumVersion '0.2.2'
    $text = switch ($originalMessage.space.type) {
        DM {
            "Thanks for adding me to your DMs, <$($originalMessage.user.name)>!"
        }
        Room {
            "Thanks for adding me to the room, <$($originalMessage.user.name)>!"
        }
    }
    New-PoshBotTextResponse -Text $text
}
function RemovedFromSpace {
    [PoshBot.BotCommand(
        HideFromHelp = $true,
        Command = $false,
        TriggerType = 'Event',
        MessageType = 'Message',
        MessageSubType = 'ChannelLeft'
    )]
    [CmdletBinding()]
    param(
        [parameter(Position = 0,ValueFromRemainingArguments = $true)]
        [string[]]$Arguments
    )
    $originalMessage = ConvertFrom-Json $Global:PoshBotContext.ParsedCommand.CommandString
    Import-Module PSGSuite -MinimumVersion "2.13.0"
    Import-Module PoshBot.GChat.Backend -MinimumVersion '0.2.2'
    
    # Empty for now since the bot is unable to send a message to a space it was removed from.
    # Maybe add logging specific to the implementation?
}

function CardClicked {
    [PoshBot.BotCommand(
        HideFromHelp = $true,
        Command = $false,
        TriggerType = 'Event',
        MessageType = 'PresenceChange' # Hack for now since Google Chat doesn't support presence change and CardClicked is not currently available as a message type in PoshBot. This maps to CARD_CLICKED events sent from Google Chat only!
    )]
    [CmdletBinding()]
    param(
        [parameter(Position = 0,ValueFromRemainingArguments = $true)]
        [string[]]$Arguments
    )
    $originalMessage = ConvertFrom-Json $Global:PoshBotContext.ParsedCommand.CommandString
    $actionMethod = $originalMessage.action.actionMethodName
    $actionParameters = $originalMessage.action.parameters
    Import-Module PSGSuite -MinimumVersion "2.13.0"
    Import-Module PoshBot.GChat.Backend -MinimumVersion '0.2.2'
    switch ($actionMethod) {
        launchNuke {
            Add-GSChatKeyValue -TopLabel "Keys Decrypted" -Content $actionParameters.value | Add-GSChatCardSection | Add-GSChatImage -ImageUrl "https://media.giphy.com/media/iyKm1yNjeSebe/giphy.gif" -LinkImage | Add-GSChatCardSection -SectionHeader "BOOM" | Add-GSChatCard | New-PoshBotGChatCardResponse -Text "The nukes have been launched! Original text: $($originalMessage.message.text)"
        }
        unleashHounds {
            Add-GSChatImage -ImageUrl "https://media.giphy.com/media/TVCqfX7rLyMuY/giphy.gif" -LinkImage | Add-GSChatCardSection -SectionHeader "GRRRRRR" | Add-GSChatCard | New-PoshBotGChatCardResponse -Text "The hounds have been unleashed! Original text: $($originalMessage.message.text)"
        }
        default {
            New-PoshBotGChatCardResponse -Text "There is no action for the action method requested: [$actionMethod]"
        }
    }
}