Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions Out-IrcBot.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<#
.Synopsis
Reads text from the pipeline, and sends it to an IRC channel
via a named pipe -- to --> Run-IrcBot.ps1
.DESCRIPTION
Long description
.EXAMPLE
PS C:\> 'Hello World' | .\Out-IrcBot.ps1
.EXAMPLE
PS C:\> Get-Content test.txt | .\Out-IrcBot.ps1
#>
[CmdletBinding()]
[Alias()]
Param
(
# The text to send to IRC
[Parameter(Mandatory=$true,
ValueFromPipeline=$true)]
[String]$Text
)

Process
{
# Create and connect to a pipe
# (This end is asynchronous and message based because the other end needs to be, and this matches it)
# Pipe is 'InOut' direction because if you just specify a one-way pipe, you can't
# make it 'Message' type, unless you manually specify the permissions. This is easier.
Write-Host -ForegroundColor Cyan "Writing: $Text"

$pipe = New-Object System.IO.Pipes.NamedPipeClientStream('.', #computer
'ircbot_pipe', #pipe name
[System.IO.Pipes.PipeDirection]::InOut,
[System.IO.Pipes.PipeOptions]::Asynchronous)


$pipe.Connect(10000) #10,000 milisecond connection timeout
$pipe.ReadMode = [System.IO.Pipes.PipeTransmissionMode]::Message # can't specify in ctor

# Convert the text to a byte array and send it
$Message = [System.Text.Encoding]::UTF8.GetBytes($Text)
$pipe.write($Message, 0, $Message.Length)

$pipe.Dispose()
}
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,20 @@ You can also use the command-line option to use another script or pass arguments
.\Run-IrcBot.ps1 awesomebot ircserver channel superbot
.\Run-IrcBot.ps1 awesomebot ircserver channel { .\superbot.ps1 $Message $Bot -DoAwesomeStuff }
```
### Sending text from PowerShell to IRC

Use this to make the bot 'speak' in IRC, triggered by a local PowerShell script.
e.g. once the bot is running, open a new PowerShell window and:

```PowerShell
echo 'Hello World!' | .\Out-IrcBot.ps1
```
The bot will say 'Hello World' into the first IRC channel it's joined to. You could use this to notify an IRC channel of the results of a command:

```PowerShell
Get-Content test.txt | .\Out-IrcBot.ps1
```
This works by Run-IrcBot.ps1 keeping a named pipe open, and Out-IrcBot.ps1 reading from the pipeline and writing to the named pipe.

## Specification

Expand Down
109 changes: 109 additions & 0 deletions Run-IrcBot.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,105 @@ function Run-Bot ($line, $bot, [switch]$fatal)
$bot.CurrentError = $null
}

function Handle-InputPipeStateMachine ($bot)
{
# Keeps a named pipe open on the local computer.
# Other PowerShell cmdlets can write text to it using | Out-IrcBot.ps1
# and this reads it and sends it on to IRC through the $bot

# Is Asynchronous to make it non-blocking. Lifecycle is:
# 1. Create a named pipe.
# . Initialize variables for one message
# 3. Async wait for connections
# 4. Check if a connection happened, if not stay here at 4.
# 5. Start an async Read().
# 6. if read finished, add text to array. If not, stay here at 6.
# 7. Check if Pipe Message is completed. If not, goto 5.
# . All text read, pipe message complete. Write text to IRC. Goto 2.

# Init
if ($null -eq $Script:InputPipeState) {
$Script:InputPipeState = 1
}

# State machine
switch ($Script:InputPipeState)
{
1 {
$Script:InputPipe = new-object System.IO.Pipes.NamedPipeServerStream('ircbot_pipe',
[System.IO.Pipes.PipeDirection]::InOut,
1, #? idk what this is for, just copypasted it
[System.IO.Pipes.PipeTransmissionMode]::Message,
[System.IO.Pipes.PipeOptions]::Asynchronous)

$Script:InputPipeMessageBuffer = New-Object byte[] 1024 #1Kb read buffer
$Script:InputPipeMessageBuilder = New-Object System.Text.StringBuilder

$Script:InputPipeState = 3
}

3 {
$Script:InputPipeConnectionWait = $Script:InputPipe.WaitForConnectionAsync() # wait for client

$Script:InputPipeState = 4
}

4 {
if ($Script:InputPipeConnectionWait.IsCompleted) { # client connected
$Script:InputPipeState = 5
}
}

5 {
$Script:InputPipeReadWait = $Script:InputPipe.ReadAsync($Script:InputPipeMessageBuffer, #begin reading from pipe into buffer
0, #store at buffer byte 0
$Script:InputPipeMessageBuffer.Length) #max num bytes to read
$Script:InputPipeState = 6
}

6 {
if ($Script:InputPipeReadWait.IsCompleted) { # background read finished
$NumBytesRead = $Script:InputPipeReadWait.Result
$MessageText = [System.Text.Encoding]::UTF8.GetString($Script:InputPipeMessageBuffer, 0, $NumBytesRead)
$null = $Script:InputPipeMessageBuilder.Append($MessageText)

$Script:InputPipeState = 7
}
}

7 {
if (-not $Script:InputPipe.IsMessageComplete)
{
$Script:InputPipeState = 5 # read again
}
else
{
$line = $Script:InputPipeMessageBuilder.ToString()
$target = @($bot.Channels)[0]
$line = "PRIVMSG $target :$line"

if ($bot.Writer) {
$bot.Writer.WriteLine($line)
$bot.Writer.Flush()
}

$Script:InputPipe.Close()
$Script:InputPipe.Dispose()
$Script:InputPipeState = 1 # restart
}
}

}

}

function Handle-InputPipe ($bot) {
# As the main loop has a delay in it, this runs quickly through the entire state machine each call
for ($i=0; $i -lt 7; $i++) {
Handle-InputPipeStateMachine $bot
}
}

function Main
{
try
Expand Down Expand Up @@ -782,6 +881,8 @@ function Main

while ($bot.Running)
{
Handle-InputPipe $bot

if ($active)
{
sleep -Milliseconds $bot.InteractiveDelay
Expand Down Expand Up @@ -843,13 +944,21 @@ function Main
}
finally
{

if ($bot.Connection)
{
$bot.Connection.Close()
$bot.Connection.Dispose()

Write-BotHost "Disconnected [$([DateTime]::Now.ToString())]`n"
}

if ($Script:InputPipe)
{
$Script:InputPipe.Close()
$Script:InputPipe.Dispose()
}

}
}

Expand Down