SlackBot.psm1
|
#Invokes an instance of a bot Function Invoke-SlackBot { <# .SYNOPSIS Start a Slack Bot. .DESCRIPTION Starts a long running script which connects to the Slack API via websockets to listen to and respond to Slack messages. .PARAMETER Token Your token for connecting to the Slack API. .PARAMETER LogPath A path where activity logs should be written, default: Logs/SlackBot.log in the current user's profile folder. .PARAMETER PSSlackConfigPath Path to the configuration file for the PSSlack module. .EXAMPLE Invoke-SlackBot Starts the SlackBot which will look for Token.XML and PSSlackConfig.xml in the parent of the current directory for credentials to the Slack API and config for the PSSlack module. #> [cmdletbinding()] Param( [string] $Token = (Import-Clixml (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath 'Token.xml')), #So I don't accidentally put it on the internet [string] $LogPath = (Join-Path -Path (Join-Path -Path ([Environment]::GetFolderPath('UserProfile')) -ChildPath 'Logs') -ChildPath 'SlackBot.log'), [string] $PSSlackConfigPath = (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath 'PSSlackConfig.xml') ) Set-PSSlackConfig -Path $PSSlackConfigPath -Token $Token #Web API call starts the session and gets a websocket URL to use. $RTMSession = Invoke-RestMethod -Uri https://slack.com/api/rtm.start -Body @{token="$Token"} Write-Log "I am $($RTMSession.self.name)" -Path $LogPath Try{ Do{ $WS = New-Object System.Net.WebSockets.ClientWebSocket $CT = New-Object System.Threading.CancellationToken $Conn = $WS.ConnectAsync($RTMSession.URL, $CT) While (!$Conn.IsCompleted) { Start-Sleep -Milliseconds 100 } Write-Log "Connected to $($RTMSession.URL)" -Path $LogPath $Size = 1024 $Array = [byte[]] @(,0) * $Size $Recv = New-Object System.ArraySegment[byte] -ArgumentList @(,$Array) While ($WS.State -eq 'Open') { $RTM = "" Do { $Conn = $WS.ReceiveAsync($Recv, $CT) While (!$Conn.IsCompleted) { Start-Sleep -Milliseconds 100 } $Recv.Array[0..($Conn.Result.Count - 1)] | ForEach-Object { $RTM = $RTM + [char]$_ } } Until ($Conn.Result.Count -lt $Size) Write-Log "$RTM" -Path $LogPath If ($RTM){ $RTM = ($RTM | convertfrom-json) Switch ($RTM){ {($_.type -eq 'message') -and (!$_.reply_to)} { If ( ($_.text -Match "<@$($RTMSession.self.id)>") -or $_.channel.StartsWith('D') ){ #A message was sent to the bot # *** Responses go here, for example..*** $words = "$($_.text)".ToLower() while ($words -match ' '){ $words = $words -replace ' ',' ' } $words = $words -split ' ' Switch ($words){ {@("hey","hello","hi") -contains $_} { Send-SlackMsg -Text 'Hello!' -Channel $RTM.Channel } {@("bye","cya") -contains $_} { Send-SlackMsg -Text 'Goodbye!' -Channel $RTM.Channel } default { Write-Verbose "I have no response for $_" } } }Else{ Write-Log "Message ignored as it wasn't sent to @$($RTMSession.self.name) or in a DM channel" -Path $LogPath } } {$_.type -eq 'reconnect_url'} { $RTMSession.URL = $RTM.url } default { Write-Log "No action specified for $($RTM.type) event" -Path $LogPath } } } } } Until (!$Conn) }Finally{ If ($WS) { Write-Verbose "Closing websocket" $WS.Dispose() } } } Function Send-SlackMsg { <# .SYNOPSIS Sends simple message responses via the RTM API .DESCRIPTION Used for sending Slack Messages through the WebSocket that is established by Invoke-SlackBot. .PARAMETER Text The message text string to be sent. .PARAMETER Channel The name or ID of the channel to send the message to. .PARAMETER ID A unique ID for the message. The current datetime's tick is used by default. .PARAMETER Timeout The number of seconds before sending the message should timeout. .EXAMPLE Send-SlackMsg -Text 'Hello!' -Channel 12345 Sends the specified text to the specified channel. .EXAMPLE Send-SlackMsg -Text 'Goodbye!' -Channel 12345 -ID 1 -Timeout 10 Sends the specified text to the specified channel with specified ID and timeout settings. #> [cmdletbinding()] Param( [Parameter(Mandatory = $true)] [string]$Text, [Parameter(Mandatory = $true)] $Channel, $ID = (get-date).ticks, $Timeout = 30 ) If (!($WS -is [System.Net.WebSockets.ClientWebSocket])) { Write-Log -Level Error 'A WebSocket to Slack is not open via $WS.' -Path $LogPath Return } $Prop = @{'id' = $ID; 'type' = 'message'; 'text' = $Text; 'channel' = $Channel } $Msg = (New-Object -TypeName PSObject -Prop $Prop) | ConvertTo-Json $Array = @() $Encoding = [System.Text.Encoding]::UTF8 $Array = $Encoding.GetBytes($Msg) $Msg = New-Object System.ArraySegment[byte] -ArgumentList @(, $Array) $Conn = $WS.SendAsync($Msg, [System.Net.WebSockets.WebSocketMessageType]::Text, [System.Boolean]::TrueString, $CT) $ConnStart = Get-Date While (!$Conn.IsCompleted) { $TimeTaken = ((get-date) - $ConnStart).Seconds If ($TimeTaken -gt $Timeout) { Write-Log -Level Error "Message $ID took longer than $Timeout seconds and may not have been sent." -Path $LogPath Return } Start-Sleep -Milliseconds 100 } } Function ConvertFrom-UnixTime { <# .SYNOPSIS ConvertFrom-UnixTime converts Unix timestamps to a PowerShell datetime object. .PARAMETER UnixTime And integer representing the unix formatted time. .EXAMPLE 1489664257 | ConvertFrom-UnixTime .EXAMPLE ConvertFrom-UnixTime -UnixTime 1489664257 .LINK https://gallery.technet.microsoft.com/scriptcenter/Write-Log-PowerShell-999c32d0 #> Param( [Parameter(Mandatory=$true,ValueFromPipeline=$true)] [Int32]$UnixTime ) BEGIN { $StartDate = Get-Date -Date '01/01/1970' } PROCESS { $TimeSpan = New-Timespan -Seconds $UnixTime $StartDate + $TimeSpan } } Function Write-Log { <# .SYNOPSIS Write-Log writes a message to a specified log file with the current time stamp. .DESCRIPTION The Write-Log function is designed to add logging capability to other scripts. In addition to writing output and/or verbose you can write to a log file for later debugging. .PARAMETER Message Message is the content that you wish to add to the log file. .PARAMETER Path The path to the log file to which you would like to write. By default the function will create the path and file if it does not exist. .PARAMETER Level Specify the criticality of the log information being written to the log (i.e. Error, Warning, Informational) .PARAMETER NoClobber Use NoClobber if you do not wish to overwrite an existing file. .EXAMPLE Write-Log -Message 'Log message' Writes the message to PowerShellLog.log in the current user's profile folder. .EXAMPLE Write-Log -Message 'Restarting Server.' -Path c:\Logs\Scriptoutput.log Writes the content to the specified log file and creates the path and file specified. .EXAMPLE Write-Log -Message 'Folder does not exist.' -Path c:\Logs\Script.log -Level Error Writes the message to the specified log file as an error message, and writes the message to the error pipeline. .LINK https://gallery.technet.microsoft.com/scriptcenter/Write-Log-PowerShell-999c32d0 #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [ValidateNotNullOrEmpty()] [Alias("LogContent")] [string]$Message, [Alias('LogPath')] [string]$Path=(Join-Path -Path ([Environment]::GetFolderPath('UserProfile')) -ChildPath 'PowerShellLog.log'), [ValidateSet("Error","Warn","Info")] [string]$Level="Info", [switch]$NoClobber ) BEGIN { $VerbosePreference = 'Continue' } PROCESS { If ((Test-Path $Path) -AND $NoClobber) { Write-Error "Log file $Path already exists, and you specified NoClobber. Either delete the file or specify a different name." -ErrorAction Stop } ElseIf (!(Test-Path $Path)) { Write-Verbose "Creating $Path." New-Item $Path -Force -ItemType File | Out-Null } $FormattedDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss" Switch ($Level) { 'Error' { Write-Error $Message $LevelText = 'ERROR:' } 'Warn' { Write-Warning $Message $LevelText = 'WARNING:' } 'Info' { Write-Verbose $Message $LevelText = 'INFO:' } } "$FormattedDate $LevelText $Message" | Out-File -FilePath $Path -Append } END { } } |