Skip to content

Instantly share code, notes, and snippets.

@paigeadelethompson
Last active March 28, 2025 06:07
Show Gist options
  • Save paigeadelethompson/251bce60fba23e5ba4d41aaff88e2c78 to your computer and use it in GitHub Desktop.
Save paigeadelethompson/251bce60fba23e5ba4d41aaff88e2c78 to your computer and use it in GitHub Desktop.
'_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
'_/ Portions generated by MASH - Microsoft Agent Scripting Helper, version 7.5
'_/ by BellCraft Technologies, http://www.bellcraft.com/mash
'_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
' * Agent Object
Dim AgentControl
' * Character Objects
Dim Peedy
' * Variables
Dim UsedChars
Dim PeedyID
Dim PeedyACS
Dim PeedyLoaded
Dim HideReq
Dim Req
Dim ScriptComplete
' * Initialize
UsedChars = "Peedy"
' * Peedy
PeedyID = "Peedy"
PeedyACS = "peedy.acs"
PeedyLoaded = False
ScriptComplete = False
Call Main
Function IsAgentInstalled()
' Purpose: Returns True if Agent 2.0 is installed, else False
On Error Resume Next
If ScriptEngineMajorVersion < 2 Then
IsAgentInstalled = False
Else
Set AgentControl = WScript.CreateObject("Agent.Control.2", "AgentControl_")
IsAgentInstalled = (Not AgentControl Is Nothing)
End If
End Function
Sub Main()
On Error Resume Next
' * INSERT ANY NON-AGENT RELATED SCRIPTING HERE
If Not IsAgentInstalled() Then
Exit Sub
End If
AgentControl.Connected = True
PeedyLoaded = LoadLocalChar(PeedyID, PeedyACS)
If Not PeedyLoaded Then
PeedyLoaded = LoadLocalChar(PeedyID, "")
End If
If PeedyLoaded Then
Call SetCharObj
Call AgentIntro
Else
Call LoadError
End If
End Sub
Function LoadLocalChar(ByVal CharID, ByVal CharACS)
' Purpose: Attempts to load the specified character
' Returns: True if successful, False if not
On Error Resume Next
If CharACS = "" Then
AgentControl.Characters.Load CharID, CharACS
Else
AgentControl.Characters.Load CharID, CharACS
End If
If Err = 0 Then
LoadLocalChar = True
Exit Function
End If
LoadLocalChar = False
End Function
Sub SetCharObj()
' Purpose: Sets the character reference and TTS Language ID
On Error Resume Next
Set Peedy = AgentControl.Characters(PeedyID)
Peedy.LanguageID = &H409
End Sub
Sub AgentControl_RequestComplete(ByVal RequestObject)
' Purpose: Take action on completion or failure of requests
On Error Resume Next
If RequestObject <> EndReq Then
Else
If Not Peedy.Visible Then
' Trigger the Script to Close
ScriptComplete = True
Else
' It is up to the user to close the script, by right-clicking
' the character and selecting 'Exit'
End If
End If
If RequestObject <> HideReq Then
Else
AgentControl.Characters.Unload PeedyID
ScriptComplete = True
End If
End Sub
Sub LoadError()
Dim strMsg
strMsg = "Error Loading Character: " & PeedyID
strMsg = strMsg & Chr(13) & Chr(13) & "This Microsoft Agent Script requires the character(s):"
strMsg = strMsg & Chr(13) & UsedChars
MsgBox strMsg, 48
End Sub
Sub AgentControl_Click(ByVal CharacterID, ByVal Button, ByVal Shift, ByVal X, ByVal Y)
End Sub
Sub AgentControl_DblClick(ByVal CharacterID, ByVal Button, ByVal Shift, ByVal X, ByVal Y)
' Purpose: Stop and Hide all characters on double-click
On Error Resume Next
Peedy.StopAll
If Not PeedyID.HasOtherClients Then
If Peedy.Visible Then
Set HideReq = Peedy.Hide()
Else
AgentControl.Characters.Unload PeedyID
ScriptComplete = True
End If
End If
End Sub
Sub InitAgentCommands()
' Purpose: Initialize the Commands menu
Peedy.Commands.RemoveAll
Peedy.Commands.Caption = "MASH Menu"
Peedy.Commands.Add "ACO", "Advanced Character Options", "Advanced Character Options"
Peedy.Commands.Add "Exit", "Exit", "Exit"
End Sub
Sub AgentControl_Command(ByVal UserInput)
' Purpose: Determine Command that was selected either by menu or voice
' and run the applicable Command Script
On Error Resume Next
Dim BadConfidence
BadConfidence = 10
If (UserInput.Confidence <= -40) Then
' Bad Recognition
Exit Sub
ElseIf (UserInput.Alt1Name <> "") And Abs(Abs(UserInput.Alt1Confidence) - Abs(UserInput.Confidence)) < BadConfidence Then
' Bad Confidence - too close to another command
Exit Sub
ElseIf (UserInput.Alt2Name <> "") And Abs(Abs(UserInput.Alt2Confidence) - Abs(UserInput.Confidence)) < BadConfidence Then
' Bad Confidence - too close to another command
Exit Sub
Else ' High Confidence
' *** BEGIN MASH USER COMMANDS ***
Select Case UserInput.Name
Case "ACO"
AgentControl.PropertySheet.Visible = True
End Select
' *** END MASH USER COMMANDS ***
If UserInput.Name = "Exit" Then
Set HideReq = Peedy.Hide()
End If
End If
End Sub
Sub AgentControl_Bookmark(ByVal BookmarkID)
On Error Resume Next
End Sub
Sub AgentIntro()
On Error Resume Next
Call InitAgentCommands
' *** BEGIN MASH USER SCRIPT ***
Peedy.TTSModeID = "{CA141FD0-AC7F-11D1-97A3-006008273001}"
Peedy.Show
' Loop forever reading from stdin
Dim stdinText
Dim alertCounter
alertCounter = 0
Do
' Read a line from stdin if available, otherwise set to empty
stdinText = ""
' Try to read from stdin
If Not WScript.StdIn.AtEndOfStream Then
stdinText = WScript.StdIn.ReadLine()
End If
' If we got text, speak it
If stdinText <> "" Then
Peedy.Speak "\Vol=19785\\Pit=50\\Spd=163\" & stdinText
' Reset alert counter when speaking
alertCounter = 0
Else
' If no text, sleep briefly
WScript.Sleep 256
' Increment alert counter
alertCounter = alertCounter + 1
' Play alert every ~5 seconds (20 iterations of 256ms sleep)
If alertCounter >= 20 Then
Peedy.Play "\Vol=19785\\Pit=50\\Spd=163\" & "Alert"
alertCounter = 0
End If
End If
Loop
' *** END MASH USER SCRIPT ***
End Sub
'_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
use strict;
use warnings;
use Irssi;
use IO::Handle;
use IPC::Open2;
use vars qw($VERSION %IRSSI);
$VERSION = '0.1';
%IRSSI = (
authors => 'NetcraveOS',
name => 'msagent',
description => 'Pipes messages to Microsoft Agent via Wine',
license => 'Public Domain',
);
my $PIPE;
my $PID;
my $AGENT_PATH = "wscript.exe";
my $SCRIPT_PATH = "$ENV{HOME}/irssi_agent.vbs";
# Add a flag to prevent recursion
my $IN_PIPE_WRITE = 0;
# Initialize the agent pipe
sub init_agent {
my $script_path = Irssi::settings_get_str('msagent_script_path') || $SCRIPT_PATH;
my $agent_path = Irssi::settings_get_str('msagent_wine_path') || $AGENT_PATH;
# Close existing pipe if open
if (defined $PIPE) {
close($PIPE);
kill('TERM', $PID) if defined $PID && $PID > 0;
}
# Build command for wine
my $cmd = "wine \"$agent_path\" \"$script_path\"";
Irssi::print("Starting agent with command: $cmd");
# Launch the process directly using open
$PIPE = undef;
$PID = undef;
# Try different pipe creation methods
eval {
# Method 1: Simple pipe open
open($PIPE, "|$cmd 2>/dev/null") or die "Failed to open pipe: $!";
};
if ($@) {
Irssi::print("Failed with simple pipe, error: $@");
eval {
# Method 2: Use system with explicit redirection
my $cmd = "wine \"$agent_path\" \"$script_path\" > /dev/null 2>&1";
$PIPE = undef;
open($PIPE, "|sh -c '$cmd'") or die "Failed shell pipe: $!";
};
}
# Check if we successfully created a pipe
if (!defined $PIPE) {
Irssi::print("Failed to open pipe to MS Agent using multiple methods");
return;
}
# Make pipe autoflush
$PIPE->autoflush(1);
# Send a test message
print $PIPE "MS Agent initialized\n";
Irssi::print("MS Agent initialized with script: $script_path");
# Add a timeout to periodically check if the pipe is still open
Irssi::timeout_add(15000, 'check_pipe_alive', undef);
}
# Check if the pipe is still alive and reopen if needed
sub check_pipe_alive {
return 1 unless defined $PIPE;
# Try a harmless write to see if pipe is still open
my $status = print $PIPE "";
if (!$status) {
Irssi::print("MS Agent pipe appears to be closed, reopening...");
init_agent();
}
return 1; # Keep the timeout active
}
# Strip IRC color codes and special characters
sub strip_irc_formatting {
my ($text) = @_;
return $text unless defined $text;
# Strip color codes: \x03 followed by 1-2 digits, optionally followed by "," and 1-2 more digits
$text =~ s/\x03\d{1,2}(?:,\d{1,2})?//g;
# Strip other formatting codes
$text =~ s/[\x02\x0F\x16\x1D\x1F]//g; # Bold, reset, reverse, italic, underline
# Strip all control characters
$text =~ s/[\x00-\x1F]//g;
# Only keep alphanumeric characters, spaces, and basic punctuation
$text =~ s/[^a-zA-Z0-9 .,!?:;'"-]//g;
return $text;
}
# Process irssi status messages - DISABLED to prevent hangs
# sub sig_printtext {
# my ($dest, $text, $stripped) = @_;
#
# # Skip if we're already in a pipe write to prevent recursion
# return if $IN_PIPE_WRITE;
#
# # Skip messages that we're already handling with other signals
# return if ($dest->{level} & (MSGLEVEL_PUBLIC | MSGLEVEL_MSGS |
# MSGLEVEL_JOINS | MSGLEVEL_PARTS |
# MSGLEVEL_QUITS | MSGLEVEL_NICKS |
# MSGLEVEL_TOPICS | MSGLEVEL_KICKS |
# MSGLEVEL_MODES));
#
# # Skip our own debug messages to avoid recursion
# return if $stripped =~ /^MS Agent|^Failed to open pipe|^Trying to reopen/;
#
# # Format based on message type
# my $formatted;
# if ($dest->{level} & MSGLEVEL_NOTICES) {
# $formatted = "NOTICE: $stripped";
# } elsif ($dest->{level} & MSGLEVEL_ACTIONS) {
# $formatted = "* $stripped";
# } elsif ($dest->{level} & MSGLEVEL_CRAP) {
# $formatted = "INFO: $stripped";
# } else {
# # Default format for other message types
# $formatted = "IRSSI: $stripped";
# }
#
# safe_pipe_write($formatted);
# }
# Write safely to the pipe, handling errors
sub safe_pipe_write {
my ($text) = @_;
# Guard against recursion
return if $IN_PIPE_WRITE;
$IN_PIPE_WRITE = 1;
eval {
# Normal pipe write logic
return unless defined $text && $text ne "";
# Filter out null chars and strip IRC formatting
$text = strip_irc_formatting($text);
# Skip if the text is now empty after stripping
return if $text eq "";
# Check if pipe exists, try to reopen if not
if (!defined $PIPE) {
Irssi::print("Pipe not open, trying to initialize...");
init_agent();
if (!defined $PIPE) {
Irssi::print("Failed to initialize pipe, message dropped");
return;
}
}
# Try to write to the pipe
my $result = print $PIPE "$text\n";
if (!$result) {
die "Write failed";
}
};
if ($@) {
Irssi::print("MS Agent pipe error: $@");
# Only try to reopen once per call
if (defined $PIPE) {
Irssi::print("Trying to reopen the pipe...");
close($PIPE) if defined $PIPE;
undef $PIPE;
init_agent();
}
}
# Reset the recursion flag
$IN_PIPE_WRITE = 0;
}
# Process server message
sub process_server_event {
my ($server, $data, $nick, $address) = @_;
# Format the server message
my $formatted = "* $data";
# Send to the agent
safe_pipe_write($formatted);
}
# Process a message - send it to the agent
sub process_message {
my ($server, $msg, $nick, $address, $target) = @_;
# Format the message
my $formatted = "$nick: $msg";
# Send to the agent
safe_pipe_write($formatted);
}
# Process public messages
sub sig_public {
my ($server, $msg, $nick, $address, $target) = @_;
process_message($server, $msg, $nick, $address, $target);
}
# Process private messages
sub sig_private {
my ($server, $msg, $nick, $address) = @_;
process_message($server, $msg, $nick, $address, "private");
}
# Process join messages
sub sig_join {
my ($server, $channel, $nick, $address) = @_;
process_server_event($server, "$nick has joined $channel", $nick, $address);
}
# Process part messages
sub sig_part {
my ($server, $channel, $nick, $address, $reason) = @_;
my $message = "$nick has left $channel";
$message .= " ($reason)" if $reason;
process_server_event($server, $message, $nick, $address);
}
# Process quit messages
sub sig_quit {
my ($server, $nick, $address, $reason) = @_;
my $message = "$nick has quit";
$message .= " ($reason)" if $reason;
process_server_event($server, $message, $nick, $address);
}
# Process nick change messages
sub sig_nick {
my ($server, $new_nick, $old_nick, $address) = @_;
process_server_event($server, "$old_nick is now known as $new_nick", $old_nick, $address);
}
# Process topic change messages
sub sig_topic {
my ($server, $channel, $topic, $nick, $address) = @_;
process_server_event($server, "$nick changed the topic of $channel to: $topic", $nick, $address);
}
# Process kick messages
sub sig_kick {
my ($server, $channel, $nick, $kicker, $address, $reason) = @_;
my $message = "$kicker has kicked $nick from $channel";
$message .= " ($reason)" if $reason;
process_server_event($server, $message, $kicker, $address);
}
# Process mode change messages
sub sig_mode {
my ($server, $channel, $nick, $address, $mode) = @_;
process_server_event($server, "mode/$channel [$mode] by $nick", $nick, $address);
}
# Process server notice messages
sub sig_server_notice {
my ($server, $data, $nick, $address) = @_;
my $formatted = "Server Notice: $data";
safe_pipe_write($formatted);
}
# Process channel actions (like /me commands)
sub sig_action {
my ($server, $msg, $nick, $address, $target) = @_;
process_server_event($server, "$nick $msg", $nick, $address);
}
# Process DCC messages
sub sig_dcc_request {
my ($dcc, $sendaddr) = @_;
my $formatted = "DCC: " . $dcc->{nick} . " wants to send you " . $dcc->{arg};
safe_pipe_write($formatted);
}
# Process server connect/disconnect events
sub sig_server_connected {
my ($server) = @_;
my $formatted = "Connected to " . $server->{address} . " (" . $server->{chatnet} . ")";
safe_pipe_write($formatted);
}
sub sig_server_disconnected {
my ($server) = @_;
my $formatted = "Disconnected from " . $server->{address} . " (" . $server->{chatnet} . ")";
safe_pipe_write($formatted);
}
# Process wallops (admin broadcasts)
sub sig_wallops {
my ($server, $msg, $nick, $address) = @_;
my $formatted = "wall ops: $nick: $msg";
safe_pipe_write($formatted);
}
# Add message level constants
use constant {
MSGLEVEL_CRAP => 0x0001,
MSGLEVEL_MSGS => 0x0002,
MSGLEVEL_PUBLIC => 0x0004,
MSGLEVEL_NOTICES => 0x0008,
MSGLEVEL_SNOTES => 0x0010,
MSGLEVEL_ACTIONS => 0x0020,
MSGLEVEL_JOINS => 0x0040,
MSGLEVEL_PARTS => 0x0080,
MSGLEVEL_QUITS => 0x0100,
MSGLEVEL_KICKS => 0x0200,
MSGLEVEL_MODES => 0x0400,
MSGLEVEL_TOPICS => 0x0800,
MSGLEVEL_WALLOPS => 0x1000,
MSGLEVEL_NICKS => 0x2000,
MSGLEVEL_DCC => 0x4000,
MSGLEVEL_HILIGHT => 0x8000,
};
# Clean up when unloading
sub UNLOAD {
if (defined $PIPE) {
close($PIPE);
Irssi::print("MS Agent pipe closed");
}
}
# Register settings
Irssi::settings_add_str('msagent', 'msagent_script_path', $SCRIPT_PATH);
Irssi::settings_add_str('msagent', 'msagent_wine_path', $AGENT_PATH);
# Register commands
Irssi::command_bind('msagent_start', 'init_agent');
Irssi::command_bind('msagent_say', sub {
my ($data, $server, $witem) = @_;
safe_pipe_write($data) if $data;
});
# Register signal handlers
Irssi::signal_add('message public', 'sig_public');
Irssi::signal_add('message private', 'sig_private');
Irssi::signal_add('message join', 'sig_join');
Irssi::signal_add('message part', 'sig_part');
Irssi::signal_add('message quit', 'sig_quit');
Irssi::signal_add('message nick', 'sig_nick');
Irssi::signal_add('message topic', 'sig_topic');
Irssi::signal_add('message kick', 'sig_kick');
Irssi::signal_add('message irc mode', 'sig_mode');
Irssi::signal_add('message irc notice', 'sig_server_notice');
Irssi::signal_add('message irc action', 'sig_action');
# Irssi::signal_add('print text', 'sig_printtext'); # This is disabled to prevent hangs
Irssi::signal_add('dcc request', 'sig_dcc_request');
Irssi::signal_add('server connected', 'sig_server_connected');
Irssi::signal_add('server disconnected', 'sig_server_disconnected');
Irssi::signal_add('message irc wallops', 'sig_wallops');
# Initialize at load time
init_agent();
Irssi::print("MS Agent script loaded. Use /msagent_start to restart agent or /msagent_say to send custom text.");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment