- 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 <amount>, 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"
Recent Pastes