package require http package require json # Configuration set ai(link) "http://127.0.0.1:5000/chat" ;# Flask API endpoint for Grok set ai(secured) "0" ;# HTTPS disabled (0 = HTTP, 1 = HTTPS) set ai(rate_limit) 5 ;# Seconds between user requests per channel (non-poker commands) set ai(max_msg_length) 400 ;# Max IRC message length set ai(max_request_age) 3600 ;# Clean request records older than 1 hour array set ai_last_request {} ;# Track last request time per user-channel array set poker_games {} ;# Track poker games per channel # Enable HTTPS if configured if {$ai(secured) == 1} { package require tls http::register https 443 [list ::tls::socket -autoservername true] } # Bind to public messages bind pubm - * ai:botnickTalk setudef flag grok proc ai:botnickTalk {nick uhost handle target args} { global botnick ai ai_last_request poker_games set message [join [lrange $args 0 end] " "] putlog "Received message from $nick in $target: $message" # Check for $botnick, $botnick:, $botnick,, grok, grok:, grok,, or exact $botnick/grok if {![string match -nocase "$botnick *" $message] && ![string match -nocase "$botnick:*" $message] && ![string match -nocase "$botnick,*" $message] && ![string equal -nocase $botnick $message] && ![string match -nocase "grok *" $message] && ![string match -nocase "grok:*" $message] && ![string match -nocase "grok,*" $message] && ![string equal -nocase "grok" $message]} { return } if {![channel get $target grok]} { putlog "Grok not enabled for channel $target" putquick "PRIVMSG $target :Grok is not enabled for this channel." return } # Extract the message content based on the addressing style if {[string match -nocase "$botnick *" $message]} { set message [string trim [string range $message [string length $botnick] end]] } elseif {[string match -nocase "$botnick:*" $message]} { set message [string trim [string range $message [expr {[string length $botnick] + 1}] end]] } elseif {[string match -nocase "$botnick,*" $message]} { set message [string trim [string range $message [expr {[string length $botnick] + 1}] end]] } elseif {[string equal -nocase $botnick $message]} { set message "" } elseif {[string match -nocase "grok *" $message]} { set message [string trim [string range $message 4 end]] } elseif {[string match -nocase "grok:*" $message]} { set message [string trim [string range $message 5 end]] } elseif {[string match -nocase "grok,*" $message]} { set message [string trim [string range $message 5 end]] } elseif {[string equal -nocase "grok" $message]} { set message "" } if {$message == ""} { putquick "PRIVMSG $target :Please provide a message!" return } if {[string equal -nocase "channels" $message]} { putlog "Processing channels command from $nick in $target" ai:list_channels $nick $target return } if {[string equal -nocase "reload" $message]} { putlog "Processing reload command from $nick in $target" ai:reload_scripts $nick $target $uhost return } if {[string equal -nocase "clearpoker" $message]} { global poker_games if {[info exists poker_games($target)]} { unset poker_games($target) putquick "PRIVMSG $target :Poker game data cleared." putlog "Poker game data cleared for $target by $nick" } else { putquick "PRIVMSG $target :No poker game data to clear." } return } if {[string equal -nocase "debug" $message]} { putlog "Processing debug command from $nick in $target" ai:debug_channel $nick $target return } if {[string match -nocase "poker *" $message]} { putlog "Processing poker command from $nick in $target: $message" poker:handle_command $nick $target [string range $message 6 end] return } set user_key "$nick:$target" set current_time [clock seconds] if {[info exists ai_last_request($user_key)] && [expr {$current_time - $ai_last_request($user_key)}] < $ai(rate_limit)} { putquick "PRIVMSG $target :Please wait a few seconds before asking again!" return } set ai_last_request($user_key) $current_time ai:clean_old_requests $current_time set encodedMessage [::http::formatQuery message $message nick $nick] set url "$ai(link)?$encodedMessage" putlog "Sending request to $url" set attempts 0 set max_attempts 3 while {$attempts < $max_attempts} { incr attempts if {[catch { ::http::geturl $url -timeout 30000 -command [list ai:callback $target $nick] -headers {Accept application/json} } error]} { putlog "HTTP error (attempt $attempts/$max_attempts): $error" if {$attempts < $max_attempts} { putlog "Retrying after [expr {1000 * (2 ** ($attempts - 1))}]ms" after [expr {1000 * (2 ** ($attempts - 1))}] continue } putquick "PRIVMSG $target :Sorry, I couldn't connect to Grok! (Error: $error, Attempt: $attempts/$max_attempts)" return } return } } proc ai:debug_channel {nick target} { global botnick set channel_list [channels] set user_list [chanlist $target] set user_count [llength [lsearch -all -not -inline $user_list $botnick]] set modes [getchanmode $target] set debug_msg "Debug for $target: Bot in channel: [expr {[lsearch -nocase $channel_list $target] >= 0}], Users: [join $user_list ", "], Count (excl. bot): $user_count, Modes: $modes, Grok enabled: [channel get $target grok]" putquick "PRIVMSG $target :$debug_msg" putlog "Debug requested by $nick in $target: $debug_msg" } proc ai:list_channels {nick target} { set channel_list [channels] if {[llength $channel_list] == 0} { putquick "PRIVMSG $target :I'm not in any channels." putlog "Channels command by $nick in $target: No channels" return } putquick "PRIVMSG $target :I'm currently in: [join $channel_list ", "]" putlog "Channels command by $nick in $target: Listed channels [join $channel_list ", "]" } proc ai:reload_scripts {nick target uhost} { global botnick if {![matchattr [nick2hand $nick $uhost] n]} { putquick "PRIVMSG $target :$nick, you don't have permission to reload scripts." putlog "Reload failed: $nick ($uhost) is not an owner in $target" return } if {[catch { rehash putquick "PRIVMSG $target :Scripts reloaded successfully by $nick." putlog "Scripts reloaded successfully by $nick in $target" } error]} { putquick "PRIVMSG $target :Failed to reload scripts: $error" putlog "Reload failed: Error reloading scripts for $nick in $target: $error" } } proc ai:clean_old_requests {current_time} { global ai ai_last_request foreach key [array names ai_last_request] { if {[expr {$current_time - $ai_last_request($key)}] > $ai(max_request_age)} { unset ai_last_request($key) } } } proc ai:callback {target nick token} { global ai set status [::http::status $token] set response [::http::data $token] set http_code [::http::ncode $token] ::http::cleanup $token if {$status != "ok" || $http_code != 200} { putlog "HTTP error: status=$status, code=$http_code, response=$response" putquick "PRIVMSG $target :Sorry, I couldn't connect to Grok! (Status: $status, Code: $http_code)" return } if {[catch {set json_data [::json::json2dict $response]} error]} { putlog "JSON parse error: $error, response=$response" putquick "PRIVMSG $target :Sorry, I couldn't parse the Grok response!" return } if {[dict exists $json_data reply]} { set output [dict get $json_data reply] ai:displayMessage $output $target $nick } else { set fallback [dict exists $json_data fallback] ? [dict get $json_data fallback] : "Sorry, no response from Grok!" putlog "JSON missing reply key: $response" putquick "PRIVMSG $target :$fallback" } } proc ai:displayMessage {output target nick} { global ai set output [string map {\\\" "\"" \\n "\n" \\t "\t"} $output] foreach line [split $output "\n"] { if {$line == ""} { continue } while {[string length $line] > $ai(max_msg_length)} { set chunk [string range $line 0 [expr {$ai(max_msg_length) - 1}]] set last_space [string last " " $chunk] if {$last_space > 0} { set chunk [string range $line 0 [expr {$last_space - 1}]] set line [string range $line $last_space end] } else { set line [string range $line $ai(max_msg_length) end] } putquick "PRIVMSG $target :$chunk" } if {$line != ""} { putquick "PRIVMSG $target :$line" } } } proc isbotconnected {} { global server if {[info exists server] && [string length $server] > 0} { return 1 } return 0 } proc poker:handle_command {nick target command} { global poker_games ai set command [string tolower [string trim $command]] putlog "Handling poker command: '$command' from $nick in $target" if {$command == "start"} { poker:start_game $nick $target } elseif {$command == "join"} { poker:join_game $nick $target } elseif {[string match "bet *" $command]} { set amount [lindex [split $command] 1] putlog "Parsed bet amount: '$amount' for $nick in $target" poker:bet $nick $target $amount } elseif {$command == "fold"} { poker:fold $nick $target } elseif {$command == "call"} { poker:call $nick $target } elseif {$command == "status"} { poker:status $nick $target } elseif {$command == "stop"} { poker:stop_game $nick $target } elseif {$command == "debug"} { poker:debug_state $nick $target } else { putquick "PRIVMSG $target :Unknown poker command. Try: start, join, bet , fold, call, status, stop, debug" putlog "Unknown command: $command" } } proc poker:start_game {nick target} { global poker_games if {[info exists poker_games($target)]} { putquick "PRIVMSG $target :A poker game is already running in this channel!" putlog "Game already exists in $target" return } set poker_games($target) [dict create \ players [list $nick] \ hands [dict create $nick [poker:deal_cards]] \ community_cards {} \ pot 0 \ current_bet 0 \ stage "preflop" \ active_players [list $nick] \ chips [dict create $nick 1000] ] putlog "Game started by $nick in $target. Players: [dict get $poker_games($target) players], Active: [dict get $poker_games($target) active_players]" putquick "PRIVMSG $target :Poker game started by $nick! Use 'Grok poker join' to join." poker:notify_player $nick $target poker:send_grok_comment $target "A new poker game has started! Let's see who has the best poker face." } proc poker:join_game {nick target} { global poker_games if {![info exists poker_games($target)]} { putquick "PRIVMSG $target :No poker game running. Start one with 'Grok poker start'." putlog "No game found in $target for $nick to join" return } set game $poker_games($target) if {[lsearch [dict get $game players] $nick] >= 0} { putquick "PRIVMSG $target :$nick, you're already in the game!" putlog "$nick already in game in $target" return } dict lappend game players $nick dict set game hands $nick [poker:deal_cards] dict lappend game active_players $nick dict set game chips $nick 1000 set poker_games($target) $game putlog "$nick joined game in $target. Players: [dict get $game players], Active: [dict get $game active_players]" putquick "PRIVMSG $target :$nick has joined the poker game!" poker:notify_player $nick $target poker:send_grok_comment $target "$nick joins the table. Time to shuffle up and deal!" } proc poker:deal_cards {} { set suits {♠ ♥ ♦ ♣} set ranks {2 3 4 5 6 7 8 9 10 J Q K A} set deck {} foreach suit $suits { foreach rank $ranks { lappend deck "$rank$suit" } } set card1 [lindex $deck [expr {int(rand() * [llength $deck])}]] set deck [lremove $deck $card1] set card2 [lindex $deck [expr {int(rand() * [llength $deck])}]] putlog "Dealt cards: $card1, $card2" return [list $card1 $card2] } proc poker:bet {nick target amount} { global poker_games putlog "Attempting bet by $nick in $target with amount: '$amount'" if {![info exists poker_games($target)]} { putquick "PRIVMSG $target :No poker game running. Start one with 'Grok poker start'." putlog "Bet failed: No game for $nick in $target" return } set game $poker_games($target) set active_players [dict get $game active_players] putlog "Current game state in $target: Players=[dict get $game players], Active=[dict get $game active_players], Stage=[dict get $game stage], Pot=[dict get $game pot], CurrentBet=[dict get $game current_bet]" if {[lsearch $active_players $nick] < 0} { putquick "PRIVMSG $target :$nick, you're not in the active game!" putlog "Bet failed: $nick not in active_players: $active_players" return } if {![string is integer -strict $amount] || $amount <= 0} { putquick "PRIVMSG $target :Please specify a valid bet amount!" putlog "Bet failed: Invalid amount '$amount' by $nick" return } set chips [dict get $game chips $nick] if {$amount > $chips} { putquick "PRIVMSG $target :$nick, you only have $chips chips!" putlog "Bet failed: $nick tried to bet $amount but has $chips" return } dict incr game pot $amount dict incr game chips $nick [expr {-1 * $amount}] dict set game current_bet $amount set poker_games($target) $game putlog "Bet successful: $nick bet $amount in $target. New pot: [dict get $game pot], Chips: [dict get $game chips $nick], Active: $active_players" putquick "PRIVMSG $target :$nick bets $amount chips. Pot is now [dict get $game pot]." poker:advance_game $target } proc poker:fold {nick target} { global poker_games if {![info exists poker_games($target)]} { putquick "PRIVMSG $target :No poker game running. Start one with 'Grok poker start'." putlog "No game for fold by $nick in $target" return } set game $poker_games($target) set active_players [dict get $game active_players] if {[lsearch $active_players $nick] < 0} { putquick "PRIVMSG $target :$nick, you're not in the active game!" putlog "$nick not in active_players: $active_players for fold in $target" return } dict set game active_players [lremove $active_players $nick] set poker_games($target) $game putlog "$nick folded in $target. Active: [dict get $game active_players]" putquick "PRIVMSG $target :$nick folds." poker:advance_game $target } proc poker:call {nick target} { global poker_games if {![info exists poker_games($target)]} { putquick "PRIVMSG $target :No poker game running. Start one with 'Grok poker start'." putlog "No game for call by $nick in $target" return } set game $poker_games($target) set active_players [dict get $game active_players] if {[lsearch $active_players $nick] < 0} { putquick "PRIVMSG $target :$nick, you're not in the active game!" putlog "$nick not in active_players: $active_players for call in $target" return } set current_bet [dict get $game current_bet] set chips [dict get $game chips $nick] if {$current_bet > $chips} { putquick "PRIVMSG $target :$nick, you don't have enough chips to call ($current_bet)!" putlog "$nick tried to call $current_bet but has $chips in $target" return } dict incr game pot $current_bet dict incr game chips $nick [expr {-1 * $current_bet}] set poker_games($target) $game putlog "$nick called $current_bet in $target. Pot: [dict get $game pot], Active: $active_players" putquick "PRIVMSG $target :$nick calls $current_bet chips. Pot is now [dict get $game pot]." poker:advance_game $target } proc poker:status {nick target} { global poker_games if {![info exists poker_games($target)]} { putquick "PRIVMSG $target :No poker game running. Start one with 'Grok poker start'." putlog "No game for status by $nick in $target" return } set game $poker_games($target) set players [dict get $game players] set community_cards [dict get $game community_cards] set pot [dict get $game pot] set stage [dict get $game stage] set active_players [dict get $game active_players] putquick "PRIVMSG $target :Poker game status: Stage: $stage, Pot: $pot, Players: [join $players ", "], Active: [join $active_players ", "], Community cards: [join $community_cards ", "]" foreach player $players { poker:notify_player $player $target } putlog "Status requested by $nick in $target. Players: $players, Active: $active_players, Stage: $stage" } proc poker:stop_game {nick target} { global poker_games if {![info exists poker_games($target)]} { putquick "PRIVMSG $target :No poker game running." putlog "No game to stop by $nick in $target" return } unset poker_games($target) putlog "Game stopped by $nick in $target" putquick "PRIVMSG $target :Poker game stopped by $nick." poker:send_grok_comment $target "Game over! Thanks for playing, everyone." } proc poker:debug_state {nick target} { global poker_games if {![info exists poker_games($target)]} { putquick "PRIVMSG $target :No poker game running to debug." putlog "No game to debug by $nick in $target" return } set game $poker_games($target) set debug_msg "Debug state for $target: Players=[dict get $game players], Active=[dict get $game active_players], Stage=[dict get $game stage], Pot=[dict get $game pot], CurrentBet=[dict get $game current_bet], CommunityCards=[dict get $game community_cards]" foreach player [dict get $game players] { append debug_msg ", $player Hand=[dict get $game hands $player], Chips=[dict get $game chips $player]" } putquick "PRIVMSG $target :$debug_msg" putlog "Debug state requested by $nick in $target: $debug_msg" } proc poker:notify_player {nick target} { global poker_games if {![info exists poker_games($target)]} { return } set game $poker_games($target) set hand [dict get $game hands $nick] set chips [dict get $game chips $nick] putquick "PRIVMSG $target :$nick's hand: [join $hand ", "]. Chips: $chips." putlog "Notified $target of $nick's hand: [join $hand ", "], Chips: $chips" } proc poker:advance_game {target} { global poker_games if {![info exists poker_games($target)]} { putlog "No game to advance in $target" return } set game $poker_games($target) set stage [dict get $game stage] set active_players [dict get $game active_players] putlog "Advancing game in $target. Stage: $stage, Active players: $active_players" if {[llength $active_players] <= 1} { set winner [lindex $active_players 0] set pot [dict get $game pot] dict incr game chips $winner $pot putquick "PRIVMSG $target :$winner wins the pot of $pot chips!" unset poker_games($target) poker:send_grok_comment $target "$winner takes it all! Want to play another round?" putlog "Game ended in $target. Winner: $winner, Pot: $pot" return } if {$stage == "preflop"} { set community_cards [poker:deal_community_cards 3] dict set game community_cards $community_cards dict set game stage "flop" set poker_games($target) $game putquick "PRIVMSG $target :Flop: [join $community_cards ", "]" putlog "Dealt flop in $target: [join $community_cards ", "]" } elseif {$stage == "flop"} { set community_cards [concat [dict get $game community_cards] [poker:deal_community_cards 1]] dict set game community_cards $community_cards dict set game stage "turn" set poker_games($target) $game putquick "PRIVMSG $target :Turn: [join $community_cards ", "]" putlog "Dealt turn in $target: [join $community_cards ", "]" } elseif {$stage == "turn"} { set community_cards [concat [dict get $game community_cards] [poker:deal_community_cards 1]] dict set game community_cards $community_cards dict set game stage "river" set poker_games($target) $game putquick "PRIVMSG $target :River: [join $community_cards ", "]" putlog "Dealt river in $target: [join $community_cards ", "]" } elseif {$stage == "river"} { set winner [lindex $active_players 0] ;# Simplified: first active player wins set pot [dict get $game pot] dict incr game chips $winner $pot putquick "PRIVMSG $target :$winner wins the pot of $pot chips!" unset poker_games($target) poker:send_grok_comment $target "What a game! $winner wins! Ready for another?" putlog "Game ended in $target. Winner: $winner, Pot: $pot" return } dict set game current_bet 0 set poker_games($target) $game poker:send_grok_comment $target "What's your move? Bet, call, or fold!" } proc poker:deal_community_cards {count} { set suits {♠ ♥ ♦ ♣} set ranks {2 3 4 5 6 7 8 9 10 J Q K A} set deck {} foreach suit $suits { foreach rank $ranks { lappend deck "$rank$suit" } } set cards {} for {set i 0} {$i < $count} {incr i} { set card [lindex $deck [expr {int(rand() * [llength $deck])}]] lappend cards $card set deck [lremove $deck $card] } putlog "Dealt community cards: [join $cards ", "]" return $cards } proc poker:send_grok_comment {target comment} { global ai set encodedMessage [::http::formatQuery message $comment nick "Grok"] set url "$ai(link)?$encodedMessage" putlog "Sending poker comment to $url" if {[catch { ::http::geturl $url -timeout 30000 -command [list ai:callback $target "Grok"] -headers {Accept application/json} } error]} { putlog "HTTP error for poker comment: $error" putquick "PRIVMSG $target :$comment" } } proc lremove {list item} { set idx [lsearch $list $item] if {$idx >= 0} { return [lreplace $list $idx $idx] } return $list } putlog "grok.tcl loaded"