Last active
March 28, 2025 06:07
-
-
Save paigeadelethompson/251bce60fba23e5ba4d41aaff88e2c78 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ | |
'_/ 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 | |
'_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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