From: b Date: Sat, 30 Dec 2023 22:58:40 +0000 (+0000) Subject: reworked Coincidence chat X-Git-Tag: v1.1.0~18 X-Git-Url: http://bicyclesonthemoon.info/git-projects/?a=commitdiff_plain;h=5be2e212b0653a83315d90ed51b28e02f95a6fd4;p=ott%2Fbsta reworked Coincidence chat --- diff --git a/2words.1.pl b/2words.1.pl index ce6d445..453f4d5 100644 --- a/2words.1.pl +++ b/2words.1.pl @@ -114,12 +114,13 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; if ($ENV{'REQUEST_METHOD'} =~ /^(HEAD|GET|POST)$/) { $method = $1; } -else{ +else { exit fail_method($ENV{'REQUEST_METHOD'}, 'GET, POST, HEAD'); } %http = read_header_env(\%ENV); %cgi = url_query_decode($ENV{'QUERY_STRING'}); + if ($method eq 'POST') { if ($http{'content-type'} eq 'application/x-www-form-urlencoded') { my %cgi_post = url_query_decode( ); @@ -130,6 +131,7 @@ if ($method eq 'POST') { exit fail_content_type($method, $http{'content-type'}); } } + $IP = get_remote_addr(); $page = get_id(\%cgi); if ($cgi{'words'} ne '') { diff --git a/bsta_lib.1.pm b/bsta_lib.1.pm index db60e49..c16899c 100644 --- a/bsta_lib.1.pm +++ b/bsta_lib.1.pm @@ -36,7 +36,7 @@ use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); our @ISA = qw(Exporter); our @EXPORT = (); our @EXPORT_OK = ( - 'STATE', 'TEXT_MODE', 'INTF_STATE', + 'STATE', 'TEXT_MODE', 'INTF_STATE', 'CHAT_STATE', 'CHAT_ACTION', 'failpage', 'fail_method', 'fail_content_type', 'fail_open_file', 'fail_attachment', 'fail_500', 'redirect', @@ -121,6 +121,19 @@ use constant TEXT_MODE => { 'bb' => 1, 'info' => 2, }; +use constant CHAT_STATE => { + 'disconnected' => 0, + 'ready' => 1, + 'active' => 2, +}; +use constant CHAT_ACTION => { + 'none' => 0, + 'join' => 1, + 'leave' => 2, + 'nopost' => 3, + 'file' => 4, +}; + use constant tags_bbcode => { 'ht' => '', @@ -345,7 +358,10 @@ sub get_remote_addr { # functions to get ID/number etc. sub get_id { - (my $cgi, my $cgi_name) = @_; + (my $cgi, my $default, my $cgi_name) = @_; + if ($default eq '') { + $default = 0; + } if ($cgi_name eq '') { $cgi_name = 'i'; } @@ -357,14 +373,14 @@ sub get_id { return int($1); } else { - return 0; + return int($default); } } # function to obtain frame number sub get_frame { - (my $cgi) = @_; - return get_id($cgi, 'f'); + (my $cgi, my $default) = @_; + return get_id($cgi, $default, 'f'); } # function to obtain password diff --git a/chat.1.pl b/chat.1.pl index 5449aad..f7297f9 100644 --- a/chat.1.pl +++ b/chat.1.pl @@ -21,27 +21,49 @@ # along with this program. If not, see . use strict; -#use warnings; +use utf8; ###PERL_LIB: use lib /botm/lib/bsta -use bsta_lib qw(failpage gethttpheader getcgi entityencode readdatafile writedatafile urlencode); +# use Encode::Locale ('decode_argv'); +use Encode ('encode', 'decode'); + +###PERL_LIB: use lib /botm/lib/bsta +use botm_common ( + 'read_data_file', 'write_data_file', + 'read_header_env', + 'url_query_decode', 'url_query_encode', + 'merge_url', + 'html_entity_encode_dec', +); +use bsta_lib ( + 'CHAT_STATE', 'CHAT_ACTION', + 'fail_method', 'fail_content_type', + 'get_remote_addr', 'get_id', 'get_password', + 'print_html_start', 'print_html_end', + 'print_html_head_start', 'print_html_head_end', + 'print_html_body_start', 'print_html_body_end', + 'merge_settings' +); use File::Copy; ###PERL_CGI_PATH: CGI_PATH = /bsta/ ###PERL_CGI_COIN_PATH: CGI_COIN_PATH = /bsta/coin -###PERL_CGI_CSS_PATH: CGI_CSS_PATH = /bsta/bsta.css -###PERL_CGI_LOGO_PATH: CGI_LOGO_PATH = /bsta/botmlogo.png ###PERL_DATA_CHAT_PATH: DATA_CHAT_PATH = /botm/data/bsta/chat ###PERL_DATA_COIN_PATH: DATA_COIN_PATH = /botm/data/bsta/coincidence +###PERL_DATA_SETTINGS_PATH: DATA_SETTINGS_PATH = /botm/data/bsta/settings -###PERL_WEBSITE: WEBSITE = 1190.bicyclesonthemoon.info ###PERL_WEBSITE_NAME: WEBSITE_NAME = Bicycles on the Moon -###PERL_FAVICON_PATH: FAVICON_PATH = /img/favicon.png + +binmode STDIN, ':encoding(UTF-8)'; +binmode STDOUT, ':encoding(UTF-8)'; +binmode STDERR, ':encoding(UTF-8)'; +# decode_argv(); my %http; my %cgi; my %coin; my %chat; +my %settings; my $time = time(); srand ($time-$$); @@ -49,148 +71,121 @@ srand ($time-$$); my $method; my $IP; my $page; -my $words; -my $username; -my $action; +my $words = ''; +my $username = ''; +my $action = CHAT_ACTION->{'none'}; my $password; -my $chatfile; +my $fh; my $state; -my $passwordOK; -my @chatlines; -my $chatstate; +my $password_ok; +my @chat_lines; +my $chat_state; my $message; -my $chatid; -my $lastid; +my $chat_id; +my $last_id; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; ###PERL_SET_PATH: $ENV{'PATH'} = /usr/local/bin:/usr/bin:/bin; if ($ENV{'REQUEST_METHOD'} =~ /^(HEAD|GET|POST)$/) { - $method=$1; + $method = $1; } -else{ - exit failpage("Status: 405 Method Not Allowed\nAllow: GET, POST, HEAD\n","405 Method Not Allowed","The interface does not support the $ENV{'REQUEST_METHOD'} method.",$method); +else { + exit fail_method($ENV{'REQUEST_METHOD'}, 'GET, POST, HEAD'); } -%http = gethttpheader (\%ENV); -%cgi = getcgi($ENV{'QUERY_STRING'}); +%http = read_header_env(\%ENV); +%cgi = url_query_decode($ENV{'QUERY_STRING'}); if ($method eq 'POST') { if ($http{'content-type'} eq 'application/x-www-form-urlencoded') { - my %cgipost=getcgi( ); - foreach my $ind (keys %cgipost) { - $cgi{$ind}=$cgipost{$ind}; - } + my %cgi_post = url_query_decode( ); + %cgi = merge_settings(\%cgi, \%cgi_post); } # multipart not supported else{ - exit failpage("Status: 415 Unsupported Media Type\n","415 Unsupported Media Type","Unsupported Content-type: $http{'content-type'}."); + exit fail_content_type($method, $http{'content-type'}); } } -if ($ENV{'PATH_INFO'} =~ /^\/(.+)$/) { - $page=int($1); -} -else { - $page=-1; -} +$IP = get_remote_addr(); +$page = get_id(\%cgi, -1); +$password = get_password(\%cgi); -if ($ENV{'REMOTE_ADDR'} =~ /^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$/) { - $IP=$1; -} -else { - $IP='0.0.0.0'; -} +%coin = read_data_file(DATA_COIN_PATH()); +%settings = read_data_file(DATA_SETTINGS_PATH()); + +$password_ok = ($password eq $settings{'password'}); if ($cgi{'words'} ne '') { - $words=$cgi{'words'}; -} -if ($cgi{'username'} ne '') { - $username=$cgi{'username'}; -} -if ($cgi{'join'} ne '') { - $action=1; -} -elsif ($cgi{'leave'} ne '') { - $action=2; -} -elsif ($cgi{'nopost'} ne '') { - $action=3; -} -elsif ($cgi{'file'} ne '') { - $action=4; + $words = $cgi{'words'}; } -else { - $action=0; -} -if ($cgi{'p'} ne '') { - $password=$cgi{'p'}; -} - -%coin=readdatafile(DATA_COIN_PATH); - -if($password eq $coin{'password'}){ - $passwordOK = 1; +if ($password_ok && ($cgi{'username'} ne '')) { + $username = $cgi{'username'}; } -else{ - $passwordOK = 0; - $username = ''; +foreach my $action_id ('join', 'leave', 'nopost', 'file') { + if ($cgi{$action_id} ne '') { + $action = CHAT_ACTION->{$action_id}; + last; + } } -if($page < 0) { - if (open ($chatfile,"+<",DATA_CHAT_PATH)){ - if (flock($chatfile,2)) { - %chat=readdatafile($chatfile); +# ongoing chat +if ($page < 0) { + if (open ($fh, "+<", encode('locale_fs', DATA_CHAT_PATH()))){ + if (flock($fh, 2)) { + %chat = read_data_file($fh); - $chatstate=int($chat{'state'}); - $chatid=int($chat{'id'}); - $lastid=$chatid; + $chat_state = int($chat{'state'}); + $chat_id = int($chat{'id'}); + $last_id = $chat_id; - if($action==0 && $cgi{'words'} ne '') { - if($chatstate < 1 && !$passwordOK) { + if (($action == CHAT_ACTION->{'none'}) && ($words ne '')) { + if (($chat_state < CHAT_STATE->{'ready'}) && !$password_ok) { $message = 'Not connected.'; } else { - if ($cgi{'words'} !~ /[\r\n]/) { - if($username =~ /^[A-Za-z]*$/) { - $chat{'content'}=$chat{'content'}.$username.': '.$cgi{'words'}."\n"; - if($chatstate < 2) { - $chat{'state'} = 2; - $chatstate = 2; + if ($words !~ /[\r\n]/) { + if ($username =~ /^[A-Za-z]*$/) { + $chat{'content'} .= $username.': '.$words."\n"; + if ($chat_state < CHAT_STATE->{'active'}) { + $chat_state = CHAT_STATE->{'active'}; + $chat{'state'} = $chat_state; } - writedatafile($chatfile,%chat); + write_data_file($fh, '', '', \%chat); } else { - $message='Invalid username.'; + $message = 'Invalid username.'; } } else { - $message='Invalid text.'; + $message = 'Invalid text.'; } } } - elsif ($action==1) { - if($chatstate > 0 && !$passwordOK) { + + elsif ($action == CHAT_ACTION->{'join'}) { + if (($chat_state > CHAT_STATE->{'disconnected'}) && !$password_ok) { $message = 'Already connected.'; } else { - if($username =~ /^[A-Za-z]*$/) { - if ($passwordOK || $cgi{'words'} eq $coin{'server'}) { - $chat{'content'}=$chat{'content'}.'join@'.$username.': '.$cgi{'words'}."\n"; - if($chatstate < 1) { - $chat{'state'} = 1; - $chatstate = 1; + if ($username =~ /^[A-Za-z]*$/) { + if ($password_ok || $words eq $coin{'server'}) { + $chat{'content'} .= 'join@'.$username.': '.$words."\n"; + if ($chat_state < CHAT_STATE->{'ready'}) { + $chat_state = CHAT_STATE->{'ready'}; + $chat{'state'} = $chat_state; } - writedatafile($chatfile,%chat); + write_data_file($fh, '', '', \%chat); } - elsif ($cgi{'words'} eq '') { - $message='Server ID missing.'; + elsif ($words eq '') { + $message = 'Server ID missing.'; } - elsif ($cgi{'words'} !~ /^[0-9]+$/) { - $message='Invalid server ID.'; + elsif ($words !~ /^[0-9]+$/) { + $message = 'Invalid server ID.'; } else { - $message='No active Coincidence server with this ID.'; + $message = 'No active Coincidence server with this ID.'; } } else { @@ -198,30 +193,29 @@ if($page < 0) { } } } - elsif ($action==2) { - if($chatstate < 1 && !$passwordOK) { + + elsif ($action == CHAT_ACTION->{'leave'}) { + if (($chat_state < CHAT_STATE->{'ready'}) && !$password_ok) { $message = 'Already disconnected.'; } else { - if($username =~ /^[A-Za-z]*$/) { - $chat{'content'}=$chat{'content'}.'leave@'.$username.': '.$cgi{'words'}."\n"; - if($username ne '') { - writedatafile($chatfile,%chat); - } - elsif ($chatstate > 1) { - writedatafile(DATA_CHAT_PATH.$chatid,%chat); - my %newchat; - $newchat{'id'}=$chatid+1; - $newchat{'state'}=0; - $newchat{'content'}=''; - writedatafile($chatfile,%newchat); + if ($username =~ /^[A-Za-z]*$/) { + $chat{'content'} .= 'leave@'.$username.': '.$words."\n"; + if ($username ne '') { + write_data_file($fh, '', '', \%chat); } else { - my %newchat; - $newchat{'id'}=$chatid; - $newchat{'state'}=0; - $newchat{'content'}=''; - writedatafile($chatfile,%newchat); + my %new_chat; + if ($chat_state > 1) { + write_data_file(DATA_CHAT_PATH.$chat_id, '', '', \%chat); + $new_chat{'id'} = $chat_id+1; + } + else { + $new_chat{'id'} = $chat_id; + } + $new_chat{'state'} = CHAT_STATE->{'disconnected'}; + $new_chat{'content'} = ''; + write_data_file($fh, '', '', \%new_chat); } } else { @@ -229,165 +223,201 @@ if($page < 0) { } } } - elsif($action==4 && $cgi{'file'} ne '' && $cgi{'words'} ne '' && $passwordOK) { - if ($cgi{'words'} !~ /[\r\n]/) { - if($username =~ /^[A-Za-z]*$/) { - $chat{'content'}=$chat{'content'}.'file@'.$username.': '.$cgi{'words'}."\n"; - if($chatstate < 2) { - $chat{'state'} = 2; - $chatstate = 2; + + elsif ( + ($action == CHAT_ACTION->{'file'}) && + ($cgi{'file'} ne '') && + ($words ne '') && + $password_ok + ) { + if ($words !~ /[\r\n]/) { + if ($username =~ /^[A-Za-z]*$/) { + $chat{'content'} .= 'file@'.$username.': '.$words."\n"; + if ($chat_state < CHAT_STATE->{'active'}) { + $chat_state = CHAT_STATE->{'active'}; + $chat{'state'} = $chat_state; } - writedatafile($chatfile,%chat); + write_data_file($fh, '', '', \%chat); } else { - $message='Invalid username.'; + $message = 'Invalid username.'; } } else { - $message='Invalid text.'; + $message = 'Invalid text.'; } } - - @chatlines = split(/\r?\n/,$chat{'content'}); + @chat_lines = split(/\r?\n/, $chat{'content'}); } else{ - $chatstate=0; - $message='Can\'t lock data file!'; + $chat_state = CHAT_STATE->{'disconnected'}; + $message = 'Can\'t lock data file!'; } - - close($chatfile); + close($fh); } else { - $chatstate=0; + $chat_state = CHAT_STATE->{'disconnected'}; $message='Can\'t open data file!'; } } +# old chat archive else { - # %chat=readdatafile(DATA_CHAT_PATH); - # $chatid=int($chat{'id'})-$page; - %chat=readdatafile(DATA_CHAT_PATH); - $lastid=int($chat{'id'}); - %chat=readdatafile(DATA_CHAT_PATH.$page); - $chatid=int($chat{'id'}); - $chatstate=int($chat{'state'}); - @chatlines = split(/\r?\n/,$chat{'content'}); + $chat_id = $page; + %chat = read_data_file(DATA_CHAT_PATH()); + $last_id = int($chat{'id'}); + if ($chat_id < $last_id) { + %chat = read_data_file(DATA_CHAT_PATH.$page); + $chat_state = int($chat{'state'}); + @chat_lines = split(/\r?\n/, $chat{'content'}); + } } - -# print "Content-type: text/plain\n\n"; -# print DATA_CHAT_PATH."\n"; -# print 'state: '.$chat{'state'}."\n"; -# print 'id: '.$chat{'id'}."\n\n"; -# print $chat{'content'}; - print "Content-type: text/html\n\n"; if($method eq 'HEAD') { exit; } -print ''."\n"; -print ''."\n"; -print 'Coincidence • '.WEBSITE_NAME.''."\n"; -print ''."\n"; -print ''."\n"; -print ''."\n"; -print ''."\n"; -print ''."\n"; -print '
'."\n"; - -print '
'."\n"; - -print '
'."\n"; -print '

Coincidence

'."\n"; -print '
'."\n"; - -print '
'."\n"; +if ($username eq '') { + $username = $coin{'name'}; +} + +my $base_url = CGI_PATH(); +my $coin_url = CGI_COIN_PATH(); +my $form_url = $coin_url; +my $oldest_url = merge_url( + {'path' => $coin_url}, + {'path' => 0} +); +my $older_url = merge_url( + {'path' => $coin_url}, + {'path' => $chat_id -1} +); +my $newer_url = ($chat_id < ($last_id -1)) ? + merge_url( + {'path' => $coin_url}, + {'path' => $chat_id +1} + ) : $coin_url; + +if ($password_ok) { + my $password_query = url_query_encode({'p', $settings{'password'}}); + $coin_url = merge_url($coin_url , {'query' => $password_query, 'append_query' => 1}); + $oldest_url = merge_url($oldest_url, {'query' => $password_query, 'append_query' => 1}); + $older_url = merge_url($older_url , {'query' => $password_query, 'append_query' => 1}); + $newer_url = merge_url($newer_url , {'query' => $password_query, 'append_query' => 1}); +} + +my $abbr = abbr_name($username); +my $_website_name = html_entity_encode_dec(WEBSITE_NAME() , 1); +my $_server = html_entity_encode_dec($coin {'server'} , 1); +my $_key = html_entity_encode_dec($coin {'key'} , 1); +my $_password = html_entity_encode_dec($settings{'password'}, 1); +my $_cgi_username = html_entity_encode_dec($cgi {'username'}, 1); +my $_username = html_entity_encode_dec($username , 1); +my $_abbr = html_entity_encode_dec($abbr , 1); +my $_message = html_entity_encode_dec($message , 1); +my $_base_url = html_entity_encode_dec($base_url , 1); +my $_coin_url = html_entity_encode_dec($coin_url , 1); +my $_form_url = html_entity_encode_dec($form_url , 1); +my $_oldest_url = html_entity_encode_dec($oldest_url, 1); +my $_older_url = html_entity_encode_dec($older_url , 1); +my $_newer_url = html_entity_encode_dec($newer_url , 1); + +print_html_start(\*STDOUT); +print_html_head_start(\*STDOUT); + +print ' Coincidence • '.$_website_name.''."\n"; + +print_html_head_end(\*STDOUT); +print_html_body_start(\*STDOUT); + +print '
'."\n"; + +print '
'."\n"; +print '

Coincidence

'."\n"; +print '
'."\n"; + +print '
'."\n"; if ($page >= 0) { - print 'Before: '.$chatid."\n"; + print ' Before: '.$chat_id."\n"; } -elsif ($chatstate>0){ - print 'Connected to server '.entityencode($coin{'server'}).' as user '.entityencode(($username ne '')?$username:$coin{'name'}).' ('.entityencode(abbrname(($username ne '')?$username:$coin{'name'})).'), public key '.entityencode($coin{'key'}).'.'."\n"; +elsif ($chat_state > CHAT_STATE->{'disconnected'}) { + print ' Connected to server '.$_server.' as user '.$_username.' ('.$_abbr.'), public key '.$_key.'.'."\n"; } else{ - print 'Not connected.'; + print ' Not connected.'; } -print '
'."\n"; -print '
'."\n"; +print '
'."\n"; + +print '
'."\n"; if ($message ne '') { - print ''.entityencode($message).''."\n"; + print ' '.$_message.''."\n"; } if ($page < 0) { - print '
'."\n"; - if ($passwordOK) { - print ''."\n"; - print ''."\n"; - print "|\n"; - print ''."\n"; - print ''."\n"; - print ''."\n"; - print ''."\n"; - print ''."\n"; - print ''."\n"; + print ' '."\n"; + if ($password_ok) { + print ' '."\n"; + print ' '."\n"; + print " |\n"; + print ' '."\n"; + print ' '."\n"; + print ' '."\n"; + print ' '."\n"; + print ' '."\n"; + print ' '."\n"; } - elsif ($chatstate>0) { - print ''."\n"; - print ''."\n"; - print "|\n"; - print ''."\n"; - print ''."\n"; + elsif ($chat_state > CHAT_STATE->{'disconnected'}) { + print ' '."\n"; + print ' '."\n"; + print " |\n"; + print ' '."\n"; + print ' '."\n"; } else { - print ''."\n"; - print ''."\n"; + print ' '."\n"; + print ' '."\n"; } - print '
'."\n"; + print ' '."\n"; } -print '
'."\n"; +print '
'."\n"; -print '
'."\n"; +print '
'."\n"; +print '
'."\n"; -print '
'."\n"; +print '
'."\n"; if ($page < 0) { - for (my $i = @chatlines-1; $i>=0; --$i) { - print chatline($chatlines[$i])."
\n"; + for (my $i = @chat_lines-1; $i>=0; --$i) { + print ' '.chat_line($chat_lines[$i])."
\n"; } } else { - for (my $i = 0; $i<@chatlines; ++$i) { - print chatline($chatlines[$i])."
\n"; + for (my $i = 0; $i<@chat_lines; ++$i) { + print ' '.chat_line($chat_lines[$i])."
\n"; } } -print '
'."\n"; +print '
'."\n"; -print ''."\n"; -print '
'."\n"; -print '
'."\n"; -print ''.WEBSITE.''."\n"; -print ''."\n"; +print '
'."\n"; +print_html_body_end(\*STDOUT); +print_html_end(\*STDOUT); -# print DATA_CHAT_PATH."\n"; -# print 'state: '.$chat{'state'}."\n"; -# print 'id: '.$chat{'id'}."\n\n"; -# print $chat{'content'}; - -sub abbrname { +sub abbr_name { (my $name) = @_; my $abbr; @@ -403,33 +433,44 @@ sub abbrname { return $abbr; } -sub chatline { +sub chat_line { (my $line) = @_; - my $action; - my $name; - my $text; if ($line =~ /^([a-z]*@)?([A-Za-z]*): (.*)$/) { - $action=$1; - $name=$2; - $text=$3; + my $action = $1; + my $name = $2; + my $text = $3; + my $color; + if ($name eq '') { + $name = $coin{'name'}; + $color = 'ni'; + } + else { + $color = 'br'; + } + $abbr = abbr_name($name); + + my $_name = html_entity_encode_dec($name , 1); + my $_abbr = html_entity_encode_dec($abbr , 1); + my $_text = html_entity_encode_dec($text , 1); + my $_server = html_entity_encode_dec($coin{'server'}, 1); - if($action ne ''){ + if($action ne '') { if ($action eq 'join@') { - return entityencode(($name ne '')?$name:$coin{'name'}).' ('.entityencode(abbrname(($name ne '')?$name:$coin{'name'})).') joined the public chat on server '.entityencode($coin{'server'}).'.'; + return "$_name ($_abbr) joined the public chat on server $_server."; } elsif ($action eq 'leave@') { - return entityencode(($name ne '')?$name:$coin{'name'}).' ('.entityencode(abbrname(($name ne '')?$name:$coin{'name'})).') left the public chat on server '.entityencode($coin{'server'}).'.'; + return "$_name ($_abbr) left the public chat on server $_server."; } elsif ($action eq 'file@') { - return entityencode(($name ne '')?$name:$coin{'name'}).' ('.entityencode(abbrname(($name ne '')?$name:$coin{'name'})).') sent the file '.entityencode($text).'.'; + return "$_name ($_abbr) sent the file $_text."; } else { return 'E:E:E'; } } else { - return ''.entityencode(abbrname(($name ne '')?$name:$coin{'name'})).': '.entityencode($text).''; + return "$_abbr: $_text"; } } else { diff --git a/reset.1.pl b/reset.1.pl index 7c8fada..7d1a534 100644 --- a/reset.1.pl +++ b/reset.1.pl @@ -29,7 +29,7 @@ use botm_common ( 'read_data_file', 'write_data_file', ); use bsta_lib ( - 'STATE', 'INTF_STATE', + 'STATE', 'INTF_STATE', 'CHAT_STATE', 'write_index' ); @@ -83,7 +83,7 @@ unless ( ); %chat = ( 'id' => 0, - 'state' => 0, # TODO chat state enum? + 'state' => CHAT_STATE->{'disconnected'}, 'content' => '' );