1 ###RUN_PERL: #!/usr/bin/perl
4 # chat.pl is generated from chat.1.pl.
6 # The coincidence interface
8 # Copyright (C) 2016, 2017, 2023 Balthasar SzczepaĆski
10 # This program is free software: you can redistribute it and/or modify
11 # it under the terms of the GNU Affero General Public License as
12 # published by the Free Software Foundation, either version 3 of the
13 # License, or (at your option) any later version.
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU Affero General Public License for more details.
20 # You should have received a copy of the GNU Affero General Public License
21 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 ###PERL_LIB: use lib /botm/lib/bsta
26 use bsta_lib qw(failpage gethttpheader getcgi entityencode readdatafile writedatafile urlencode);
29 ###PERL_CGI_PATH: CGI_PATH = /bsta/
30 ###PERL_CGI_COIN_PATH: CGI_COIN_PATH = /bsta/coin
31 ###PERL_CGI_CSS_PATH: CGI_CSS_PATH = /bsta/bsta.css
32 ###PERL_CGI_LOGO_PATH: CGI_LOGO_PATH = /bsta/botmlogo.png
34 ###PERL_DATA_CHAT_PATH: DATA_CHAT_PATH = /botm/data/bsta/chat
35 ###PERL_DATA_COIN_PATH: DATA_COIN_PATH = /botm/data/bsta/coincidence
37 ###PERL_WEBSITE: WEBSITE = 1190.bicyclesonthemoon.info
38 ###PERL_WEBSITE_NAME: WEBSITE_NAME = Bicycles on the Moon
39 ###PERL_FAVICON_PATH: FAVICON_PATH = /img/favicon.png
65 delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
66 ###PERL_SET_PATH: $ENV{'PATH'} = /usr/local/bin:/usr/bin:/bin;
68 if ($ENV{'REQUEST_METHOD'} =~ /^(HEAD|GET|POST)$/) {
72 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);
75 %http = gethttpheader (\%ENV);
76 %cgi = getcgi($ENV{'QUERY_STRING'});
78 if ($method eq 'POST') {
79 if ($http{'content-type'} eq 'application/x-www-form-urlencoded') {
80 my %cgipost=getcgi( <STDIN> );
81 foreach my $ind (keys %cgipost) {
82 $cgi{$ind}=$cgipost{$ind};
85 # multipart not supported
87 exit failpage("Status: 415 Unsupported Media Type\n","415 Unsupported Media Type","Unsupported Content-type: $http{'content-type'}.");
91 if ($ENV{'PATH_INFO'} =~ /^\/(.+)$/) {
98 if ($ENV{'REMOTE_ADDR'} =~ /^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$/) {
105 if ($cgi{'words'} ne '') {
106 $words=$cgi{'words'};
108 if ($cgi{'username'} ne '') {
109 $username=$cgi{'username'};
111 if ($cgi{'join'} ne '') {
114 elsif ($cgi{'leave'} ne '') {
117 elsif ($cgi{'nopost'} ne '') {
120 elsif ($cgi{'file'} ne '') {
126 if ($cgi{'p'} ne '') {
130 %coin=readdatafile(DATA_COIN_PATH);
132 if($password eq $coin{'password'}){
141 if (open ($chatfile,"+<",DATA_CHAT_PATH)){
142 if (flock($chatfile,2)) {
143 %chat=readdatafile($chatfile);
145 $chatstate=int($chat{'state'});
146 $chatid=int($chat{'id'});
149 if($action==0 && $cgi{'words'} ne '') {
150 if($chatstate < 1 && !$passwordOK) {
151 $message = 'Not connected.';
154 if ($cgi{'words'} !~ /[\r\n]/) {
155 if($username =~ /^[A-Za-z]*$/) {
156 $chat{'content'}=$chat{'content'}.$username.': '.$cgi{'words'}."\n";
161 writedatafile($chatfile,%chat);
164 $message='Invalid username.';
168 $message='Invalid text.';
173 if($chatstate > 0 && !$passwordOK) {
174 $message = 'Already connected.';
177 if($username =~ /^[A-Za-z]*$/) {
178 if ($passwordOK || $cgi{'words'} eq $coin{'server'}) {
179 $chat{'content'}=$chat{'content'}.'join@'.$username.': '.$cgi{'words'}."\n";
184 writedatafile($chatfile,%chat);
186 elsif ($cgi{'words'} eq '') {
187 $message='Server ID missing.';
189 elsif ($cgi{'words'} !~ /^[0-9]+$/) {
190 $message='Invalid server ID.';
193 $message='No active Coincidence server with this ID.';
197 $message = 'Invalid username.';
202 if($chatstate < 1 && !$passwordOK) {
203 $message = 'Already disconnected.';
206 if($username =~ /^[A-Za-z]*$/) {
207 $chat{'content'}=$chat{'content'}.'leave@'.$username.': '.$cgi{'words'}."\n";
208 if($username ne '') {
209 writedatafile($chatfile,%chat);
211 elsif ($chatstate > 1) {
212 writedatafile(DATA_CHAT_PATH.$chatid,%chat);
214 $newchat{'id'}=$chatid+1;
216 $newchat{'content'}='';
217 writedatafile($chatfile,%newchat);
221 $newchat{'id'}=$chatid;
223 $newchat{'content'}='';
224 writedatafile($chatfile,%newchat);
228 $message = 'Invalid username.';
232 elsif($action==4 && $cgi{'file'} ne '' && $cgi{'words'} ne '' && $passwordOK) {
233 if ($cgi{'words'} !~ /[\r\n]/) {
234 if($username =~ /^[A-Za-z]*$/) {
235 $chat{'content'}=$chat{'content'}.'file@'.$username.': '.$cgi{'words'}."\n";
240 writedatafile($chatfile,%chat);
243 $message='Invalid username.';
247 $message='Invalid text.';
251 @chatlines = split(/\r?\n/,$chat{'content'});
255 $message='Can\'t lock data file!';
263 $message='Can\'t open data file!';
267 # %chat=readdatafile(DATA_CHAT_PATH);
268 # $chatid=int($chat{'id'})-$page;
269 %chat=readdatafile(DATA_CHAT_PATH);
270 $lastid=int($chat{'id'});
271 %chat=readdatafile(DATA_CHAT_PATH.$page);
272 $chatid=int($chat{'id'});
273 $chatstate=int($chat{'state'});
274 @chatlines = split(/\r?\n/,$chat{'content'});
278 # print "Content-type: text/plain\n\n";
279 # print DATA_CHAT_PATH."\n";
280 # print 'state: '.$chat{'state'}."\n";
281 # print 'id: '.$chat{'id'}."\n\n";
282 # print $chat{'content'};
284 print "Content-type: text/html\n\n";
285 if($method eq 'HEAD') {
289 print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">'."\n";
290 print '<html lang="en"><head>'."\n";
291 print '<title>Coincidence • '.WEBSITE_NAME.'</title>'."\n";
292 print '<meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
293 print '<link rel="icon" type="image/png" href="'.FAVICON_PATH.'">'."\n";
294 print '<link rel="stylesheet" href="'.CGI_CSS_PATH.'">'."\n";
295 print '</head><body>'."\n";
296 print '<a href="/"><img id="botmlogo" src="'.CGI_LOGO_PATH.'" alt="'.WEBSITE.'"></a>'."\n";
297 print '<div id="all">'."\n";
299 print '<div id="inst" class="ins">'."\n";
301 print '<div id="title">'."\n";
302 print '<H1 id="titletext">Coincidence</H1>'."\n";
305 print '<div id="storypuzzle">'."\n";
307 print 'Before: '.$chatid."\n";
309 elsif ($chatstate>0){
310 print 'Connected to server <span class="br">'.entityencode($coin{'server'}).'</span> as user <span class="ni">'.entityencode(($username ne '')?$username:$coin{'name'}).'</span> (<span class="ni">'.entityencode(abbrname(($username ne '')?$username:$coin{'name'})).'</span>), public key <span class="br">'.entityencode($coin{'key'}).'</span>.'."\n";
313 print 'Not connected.';
316 print '<div id="command">'."\n";
317 if ($message ne '') {
318 print '<span class="br">'.entityencode($message).'</span>'."\n";
321 print '<form method="post" action="'.CGI_COIN_PATH.'">'."\n";
323 print '<input class="intxc" type="text" name="words">'."\n";
324 print '<input class="inbt" type="submit" value="Send">'."\n";
326 print '<input class="intx" type="text" name="username" value="'.entityencode($username).'">'."\n";
327 print '<input class="inbt" type="submit" name="nopost" value="Refresh">'."\n";
328 print '<input class="inbt" type="submit" name="join" value="Connect">'."\n";
329 print '<input class="inbt" type="submit" name="leave" value="Disconnect">'."\n";
330 print '<input class="inbt" type="submit" name="file" value="Send file">'."\n";
331 print '<input type="hidden" name="p" value="'.entityencode($coin{'password'}).'">'."\n";
333 elsif ($chatstate>0) {
334 print '<input class="intxc" type="text" name="words">'."\n";
335 print '<input class="inbt" type="submit" value="Send">'."\n";
337 print '<input class="inbt" type="submit" name="nopost" value="Refresh">'."\n";
338 print '<input class="inbt" type="submit" name="leave" value="Disconnect">'."\n";
341 print '<input class="intx" type="text" name="words">'."\n";
342 print '<input class="inbt" type="submit" name="join" value="Connect">'."\n";
344 print '</form>'."\n";
348 print '</div><div id="insb" class="ins">'."\n";
350 print '<div id="chat">'."\n";
352 for (my $i = @chatlines-1; $i>=0; --$i) {
353 print chatline($chatlines[$i])."<br>\n";
357 for (my $i = 0; $i<@chatlines; ++$i) {
358 print chatline($chatlines[$i])."<br>\n";
363 print '<div id="underlinks">'."\n";
364 print '<a href="'.CGI_PATH.'">BSTA</a> | <a href="'.CGI_COIN_PATH.($passwordOK?('?p='.urlencode($coin{'password'})):'').'">Once again</a>';
366 print ' | <a href="'.CGI_COIN_PATH.'/'.($chatid-1).($passwordOK?('?p='.urlencode($coin{'password'})):'').'">Before</a>';
368 if ($chatid < $lastid) {
369 print ' | <a href="'.CGI_COIN_PATH.'/'.(($chatid < $lastid-1)?($chatid +1):'').($passwordOK?('?p='.urlencode($coin{'password'})):'').'">Unbefore</a>';
372 print ' | <a href="'.CGI_COIN_PATH.'/0'.($passwordOK?('?p='.urlencode($coin{'password'})):'').'">Initially</a>';
374 print ' | (This interface is only a demo, a proof of concept. It is very limited. No autorefresh, no private chat, etc. For full functionality use the actual Coincidence client.)'."\n";
380 print '<a href="/" class="cz">'.WEBSITE.'</a>'."\n";
381 print '</body></html>'."\n";
385 # print DATA_CHAT_PATH."\n";
386 # print 'state: '.$chat{'state'}."\n";
387 # print 'id: '.$chat{'id'}."\n\n";
388 # print $chat{'content'};
394 if($name !~ /^[A-Za-z]+$/) {
398 $abbr = uc(substr($name,0,1));
399 $name = substr($name,1);
400 while($name =~ m/([A-Z])/g) {
412 if ($line =~ /^([a-z]*@)?([A-Za-z]*): (.*)$/) {
418 if ($action eq 'join@') {
419 return entityencode(($name ne '')?$name:$coin{'name'}).' ('.entityencode(abbrname(($name ne '')?$name:$coin{'name'})).') joined the public chat on server '.entityencode($coin{'server'}).'.';
421 elsif ($action eq 'leave@') {
422 return entityencode(($name ne '')?$name:$coin{'name'}).' ('.entityencode(abbrname(($name ne '')?$name:$coin{'name'})).') left the public chat on server '.entityencode($coin{'server'}).'.';
424 elsif ($action eq 'file@') {
425 return entityencode(($name ne '')?$name:$coin{'name'}).' ('.entityencode(abbrname(($name ne '')?$name:$coin{'name'})).') sent the file '.entityencode($text).'.';
432 return '<span class="'.(($name ne '')?'br':'ni').'">'.entityencode(abbrname(($name ne '')?$name:$coin{'name'})).': '.entityencode($text).'</span>';