1 ###RUN_PERL: #!/usr/bin/perl
4 # 2words is generated from 2words.1.pl.
6 # The wordgame 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 # use Encode::Locale ('decode_argv');
26 use Encode ('encode', 'decode');
28 ###PERL_LIB: use lib /botm/lib/bsta
30 'read_data_file', 'write_data_file'
33 'STATE', 'INTF_STATE',
34 'fail_method', 'fail_content_type',
52 ###PERL_CGI_PATH: CGI_PATH = /bsta/
53 ###PERL_CGI_2WORDS_PATH: CGI_2WORDS_PATH = /bsta/2words
54 ###PERL_CGI_CSS_PATH: CGI_CSS_PATH = /bsta/bsta.css
55 ###PERL_CGI_LOGO_PATH: CGI_LOGO_PATH = /bsta/botmlogo.png
56 ###PERL_CGI_VIEWER_PATH: CGI_VIEWER_PATH = /bsta/v
58 ###PERL_DATA_PATH: DATA_PATH = /botm/data/bsta/
59 ###PERL_DATA_COIN_PATH: DATA_COIN_PATH = /botm/data/bsta/coincidence
60 ###PERL_DATA_DEFAULT_PATH: DATA_DEFAULT_PATH = /botm/data/bsta/default
61 ###PERL_DATA_LIST_PATH: DATA_LIST_PATH = /botm/data/bsta/list
62 ###PERL_DATA_SETTINGS_PATH: DATA_SETTINGS_PATH = /botm/data/bsta/settings
63 ###PERL_DATA_STATE_PATH: DATA_STATE_PATH = /botm/data/bsta/state
64 ###PERL_DATA_STORY_PATH: DATA_STORY_PATH = /botm/data/bsta/story
66 ###PERL_WWW_PATH: WWW_PATH = /botm/www/1190/bsta/
67 ###PERL_WWW_INDEX_PATH: WWW_INDEX_PATH = /botm/www/1190/bsta/index.htm
69 ###PERL_WEBSITE: WEBSITE = 1190.bicyclesonthemoon.info
70 ###PERL_WEBSITE_NAME: WEBSITE_NAME = Bicycles on the Moon
71 ###PERL_FAVICON_PATH: FAVICON_PATH = /img/favicon.png
73 ###PERL_COIN_DATE: COIN_DATE = 13-Nov-2016 22:15
74 ###PERL_INTF_DATE: INTF_DATE = 28-Sep-2016 20:34
76 ###PERL_STORY_LENGTH: STORY_LENGTH = 16
77 ###PERL_PAGE_LENGTH: PAGE_LENGTH = 16
78 ###PERL_FIRSTPAGE_LENGTH: FIRSTPAGE_LENGTH = 4
80 binmode STDIN, ':encoding(UTF-8)';
81 binmode STDOUT, ':encoding(UTF-8)';
82 binmode STDERR, ':encoding(UTF-8)';
120 delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
121 ###PERL_SET_PATH: $ENV{'PATH'} = /usr/local/bin:/usr/bin:/bin;
123 if ($ENV{'REQUEST_METHOD'} =~ /^(HEAD|GET|POST)$/) {
127 exit fail_method($ENV{'REQUEST_METHOD'}, 'GET, POST, HEAD');
130 %http = read_header_env(\%ENV);
131 %cgi = url_query_decode($ENV{'QUERY_STRING'});
132 if ($method eq 'POST') {
133 if ($http{'content-type'} eq 'application/x-www-form-urlencoded') {
134 my %cgi_post=url_query_decode( <STDIN> );
135 foreach my $ind (keys %cgi_post) {
136 $cgi{$ind} = $cgi_post{$ind};
139 # multipart not supported
141 exit fail_content_type($http{'content-type'}, $method);
144 $IP = get_remote_addr();
145 if ($ENV{'PATH_INFO'} =~ /^\/(.+)$/) {
151 if ($cgi{'words'} ne '') {
152 $words=$cgi{'words'};
155 %settings = read_data_file(DATA_SETTINGS_PATH());
156 %state = read_data_file(DATA_STATE_PATH());
157 $ong_state = int($state{'state'});
158 $cmd_clear = settings{'password'}.' clear';
159 $cmd_clear_all = settings{'password'}.' clearall';
162 if (open ($story_file,"+<:encoding(UTF-8)",DATA_STORY_PATH())){
164 if (flock($story_file,2)) {
167 %story = $read_data_file($story_file);
169 if ($story{'lastip'} =~ /^.+$/) {
176 $last_letter = lc($story{'letter'});
177 $story_id = int($story{'id'});
178 $intf_pass = int($story{'pass'});
179 $intf_state = int($story{'state'});
180 $intf_mode = $intf_state;
181 $intf_pause = $intf_state & 0x01;
186 if ($IP ne $last_IP) {
194 ($words eq $cmd_clear) ||
195 ($words eq $cmd_clear_all) ||
199 ($words eq $cmd_clear_all) ||
204 $story{'content'} = '';
205 $story{'lastip' } = '0.0.0.0';
206 $story{'letter' } = '';
208 $story{'state' } = INTF_STATE->{'X'};
210 if($ong_state == STATE->{'inactive'}) {
211 writeindex(WWW_INDEX_PATH,0,0,0); # TO REPLACE
213 write_data_file($story_file, '', '', \%story);
218 $message = "It's not your turn.";
220 # TODO: consider allowing non-ASCII letters in words.
221 # (not very important in English language)
222 elsif ($words =~ /^([!"\(\),\.:;\?][ \t]*)?([A-Za-z][A-Za-z'\-]*[A-Za-z']?)([!"\(\),\.:;\? \t][ \t]*)([A-Za-z][A-Za-z'\-]*[A-Za-z']?)([!"\(\),\.:;\?]?[ \t]*)$/) {
223 $first_letter = lc(substr($2, 0, 1));
224 $second_letter = lc(substr($4, 0, 1));
226 ($first_letter ne $last_letter) &&
229 $message = 'The first word must start with '.uc($last_letter).'.';
231 elsif ($first_letter eq $second_letter) {
232 $message = 'The second word can\'t also start with '.uc($first_letter).'.';
235 $story{'content'} = $story{'content'} . $words."\n";
237 $story{'lastip'} = $IP;
238 $story{'letter'} = $second_letter;
240 if ($cgi{'next'} ne '') {
241 if (split(/\r?\n/,$story{'content'}) >= (STORY_LENGTH-1)) {
242 $story_path = DATA_STORY_PATH.$story_id;
243 write_data_file($story_path, '', '', \%story);
244 $new_story{'id' } = $story_id + 1;
245 $new_story{'letter' } = '';
246 $new_story{'lastip' } = $IP;
247 $new_story{'content'} = '';
248 $new_story{'pass' } = 0;
249 $new_story{'state' } = INTF_STATE->{'X'};
250 $intf_state = INTF_STATE->{'X'};
252 $intf_mode = INTF_STATE->{'X'};
254 if($ong_state == STATE->{'inactive'}) {
255 writeindex(WWW_INDEX_PATH,0,0,0); # TO REPLACE
257 write_data_file($story_file, '', '', \%new_story);
260 $message = 'To early to finish this wordgame.';
261 write_data_file($story_file, '', '', \%story);
265 if ($intf_pass == 1) {
268 if($ong_state == STATE->{'inactive'}) {
269 writeindex(WWW_INDEX_PATH,2,0,0); # TO REPLACE
272 elsif(lc($2).' '.lc($4) eq $settings{'unlock'}) {
273 if ($intf_pass != 0) {
274 $message = 'The password has already been used in this story.';
276 elsif ($ong_state != STATE->{'inactive'}) {
280 my %frame_data = read_data_file(DATA_PATH.0);
281 my %default = read_data_file(DATA_DEFAULT_PATH());
285 $frame_data{'ongtime'} = $time;
286 $goto_list{'title-0'} = $frame_data{'title'};
287 $goto_list{'ongtime-0'} = $frame_data{'ongtime'};
288 writedatafile(DATA_PATH.0,%frame_data);
289 writedatafile(DATA_LIST_PATH,%goto_list);
291 foreach my $ind (keys %default) {
292 unless(defined($frame_data{$ind})){
293 $frame_data{$ind}=$default{$ind};
297 $inpath = DATA_PATH.sprintf($settings{'frame'},0,$frame_data{'ext'});
298 $outpath = WWW_PATH.sprintf($settings{'frame'},0,$frame_data{'ext'});
300 if(copy ($inpath, $outpath)) {
305 $story{'pass'} = '1';
306 $story{'state'} = '0';
307 writeindex(WWW_INDEX_PATH,1,0,0);
311 writedatafile($story_file,%story);
315 $message = 'Please, two words, not more, not less (some punctuation is allowed).';
319 elsif (($cgi{'s'} ne '') && ($intf_pass==1) && ($ong_state == 0)) {
320 $intf_state = int($cgi{'s'});
321 if($intf_state > 63 || $intf_state <0) {
324 $intf_mode = $intf_state;
325 $intf_pause = $intf_state & 1;
329 $story{'state'} = $intf_state;
330 writeindex(WWW_INDEX_PATH,1,$intf_mode,$intf_pause);
331 writedatafile($story_file,%story);
333 @storylines = split(/\r?\n/,$story{'content'});
334 if(@storylines & 1) {
341 print "Content-type: text/html\n\n";
342 if($method eq 'HEAD') {
346 print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">'."\n";
347 print '<html lang="en"><head>'."\n";
348 print '<title>Two words • '.WEBSITE_NAME.'</title>'."\n";
349 print '<meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
350 print '<link rel="icon" type="image/png" href="'.FAVICON_PATH.'">'."\n";
351 print '<link rel="stylesheet" href="'.CGI_CSS_PATH.'">'."\n";
352 print '</head><body>'."\n";
353 print '<a href="/"><img id="botmlogo" src="'.CGI_LOGO_PATH.'" alt="'.WEBSITE.'"></a>'."\n";
354 print '<div id="all">'."\n";
356 print '<div id="inst" class="ins">'."\n";
358 print '<div id="title">'."\n";
359 print '<H1 id="titletext">Two words</H1>'."\n";
363 print '<div id="storypuzzle">'."\n";
364 for (my $i = 0; $i < @storylines; ++$i){
365 print '<span class="'.($turn?'ni':'br').'">'.entityencode($storylines[$i]).'</span>'."\n";
370 print '<div id="command">'."\n";
371 if ($message ne '') {
372 print '<span class="br">'.entityencode($message).'</span>'."\n";
376 print '<form method="post" action="'.CGI_2WORDS_PATH.'">'."\n";
377 if ($message eq '') {
378 if ($story{"content"} eq '') {
379 print 'Two words, please:<br>'."\n";
382 print 'Please continue, two words:<br>'."\n";
385 print '<input class="intx" type="text" name="words">'."\n";
386 print '<input class="inbt" type="submit" value="enter">'."\n";
387 if(@storylines >= (STORY_LENGTH-1)) {
388 print '<input class="inbt" type="submit" name="next" value="enter and then start a new one">'."\n";
390 print '</form>'."\n";
393 if ($message eq '') {
394 print 'Wait for it.'."\n";
399 elsif ($message ne '') {
400 print '<div id="command">'."\n";
401 print '<span class="br">'.entityencode($message).'</span>'."\n";
405 if(($intf_pass == 1) && ($ong_state == 0)) {
406 print '</div><div id="framespace">'."\n";
407 print '<table id="intftable" cellspacing="0" cellpadding="0"><tr class="intf">'."\n";
409 print '<td colspan="6" class="intf"><img src="'.CGI_PATH.'intf-00';
410 if ($intf_mode == 4) {
413 elsif ($intf_mode == 8) {
416 elsif ($intf_state == 16) {
419 print'.gif" alt="" class="intf"></td>'."\n";
421 print '</tr><tr class="intf">'."\n";
422 print '<td class="intf"><img src="'.CGI_PATH.'intf-20.gif" alt="o" class="intf"></td>'."\n";
423 print '<td class="intf"><a href="'.CGI_PATH.'2words?s='.($intf_pause?17:16).'"><img src="'.CGI_PATH.'intf-10'.(($intf_mode==16)?'_':'').'.gif" class="intf" alt=">"></td>'."\n";
424 print '<td class="intf"><a href="'.CGI_PATH.'2words?s='.($intf_pause?9:8).'"><img src="'.CGI_PATH.'intf-08'.(($intf_mode==8)?'_':'').'.gif" class="intf" alt="<<"></td>'."\n";
425 print '<td class="intf"><a href="'.CGI_PATH.'2words?s='.($intf_pause?5:4).'"><img src="'.CGI_PATH.'intf-04'.(($intf_mode==4)?'_':'').'.gif" class="intf" alt=">>"></td>'."\n";
426 print '<td class="intf"><a href="'.CGI_PATH.'2words?s='.($intf_pause?0:0).'"><img src="'.CGI_PATH.'intf-02.gif" class="intf" alt="^"></td>'."\n";
427 print '<td class="intf"><a href="'.CGI_PATH.'2words?s='.($intf_pause?$intf_mode:$intf_mode+1).'"><img src="'.CGI_PATH.'intf-01'.($intf_pause?'_':'').'.gif" class="intf" alt="||"></td>'."\n";
428 print '</tr></table>'."\n";
431 print '</div><div id="insb" class="ins">'."\n";
432 print '<div id="undertext">'."\n";
433 for (my $i = $story_id-1-(($page!=0)?((($page-1)*PAGE_LENGTH)+FIRSTPAGE_LENGTH):0); $i > ($story_id-1-($page*PAGE_LENGTH)- FIRSTPAGE_LENGTH) && $i >= 0; --$i) {
434 $story_path = DATA_STORY_PATH.$i;
435 %new_story = readdatafile($story_path);
436 print '<p class="'.(($i&1)?'br':'ni').'" id="s'.$i.'">'.entityencode($new_story{'content'}).'</p>'."\n";
440 print '<div id="underlinks">'."\n";
441 print '<a href="'.CGI_PATH.'">BSTA</a> | <a href="'.CGI_2WORDS_PATH.'">Once again</a>';
442 if(($story_id - ($page*PAGE_LENGTH)) - FIRSTPAGE_LENGTH > 0) {
443 print ' | <a href="'.CGI_2WORDS_PATH.'/'.($page+1).'">Before</a>';
446 print ' | <a href="'.CGI_2WORDS_PATH.'/'.($page-1).'">Unbefore</a>';
448 if(($story_id - ($page*PAGE_LENGTH)) - FIRSTPAGE_LENGTH > 0) {
449 print ' | <a href="'.CGI_2WORDS_PATH.'/'.(int(($story_id - FIRSTPAGE_LENGTH - 1) / PAGE_LENGTH) + 1).'">Initially</a>';
452 print ' | (Entering words here is irreversible. Your actions might be remembered forever. So please be reasonable.)';
459 print '<a href="/" class="cz">'.WEBSITE.'</a>'."\n";
460 print '</body></html>'."\n";
463 (my $indexpath, my $pass, my $mode, my $pause) = @_;
467 if(ref($indexpath)) {
468 $indexfile=$indexpath;
469 unless (seek($indexfile, 0, 0)) {
474 unless (open ($indexfile, ">", $indexpath)) {
480 $indexof =~ s/\/$//g;
482 my %coin = readdatafile(DATA_COIN_PATH);
485 print $indexfile '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">'."\n";
486 print $indexfile '<html>'."\n";
487 print $indexfile ' <head>'."\n";
488 print $indexfile ' <title>Index of '.$indexof.'</title>'."\n";
489 print $indexfile ' </head>'."\n";
490 print $indexfile ' <body>'."\n";
491 print $indexfile '<h1>Index of '.$indexof.'</h1>'."\n";
492 print $indexfile '<table><tr><th><img src="/icons/blank.gif" alt="[ICO]"></th><th><a href="?C=N;O=D">Name</a></th><th><a href="?C=M;O=A">Last modified</a></th><th><a href="?C=S;O=A">Size</a></th><th><a href="?C=D;O=A">Description</a></th></tr><tr><th colspan="5"><hr></th></tr>'."\n";
493 print $indexfile '<tr><td valign="top"><img src="/icons/back.gif" alt="[DIR]"></td><td><a href="/">Parent Directory</a></td><td> </td><td align="right"> - </td><td> </td></tr>'."\n";
494 print $indexfile '<tr><td valign="top"><img src="/icons/folder.gif" alt="[DIR]"></td><td><a href="2words/">2words/</a></td><td align="right">'.INTF_DATE.' </td><td align="right"> - </td><td> </td></tr>'."\n";
495 print $indexfile '<tr><td valign="top"><img src="/icons/folder.gif" alt="[DIR]"></td><td><a href="coin/">coin/</a></td><td align="right">'.COIN_DATE.' </td><td align="right"> - </td><td> Coincidence </td></tr>'."\n";
496 print $indexfile '<tr><th colspan="5"><hr></th></tr>'."\n";
497 print $indexfile '</table>'."\n";
498 print $indexfile '<address>Apache/2.2.22 (Debian) Server at '.WEBSITE.' Port 80</address>'."\n";
499 print $indexfile '</body></html>'."\n";
506 %framedata = readdatafile(DATA_PATH.0);
507 %nextframedata = readdatafile(DATA_PATH.1);
508 %default=readdatafile(DATA_DEFAULT_PATH);
510 # if($mode == 16 && $pause) {
511 # $framedata{'ongtime'} = $time;
512 # writedatafile(DATA_PATH.0,%framedata);
515 foreach my $ind (keys %default) {
516 unless(defined($framedata{$ind})){
517 $framedata{$ind}=$default{$ind};
519 unless(defined($nextframedata{$ind})){
520 $nextframedata{$ind}=$default{$ind};
525 print $indexfile '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">'."\n";
526 print $indexfile '<html lang="en"><head>'."\n";
527 print $indexfile '<title>';
528 if($mode == 8 || $mode == 4) {
529 print $indexfile 'Index of';
532 print $indexfile entityencode($settings{'story'});
534 print $indexfile ' • '.WEBSITE_NAME;
538 print $indexfile 'Index of '.$indexof;
540 print $indexfile '</title>'."\n";
541 print $indexfile '<meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
542 print $indexfile '<link rel="icon" type="image/png" href="'.FAVICON_PATH.'">'."\n";
543 print $indexfile '<link rel="stylesheet" href="'.CGI_CSS_PATH.'">'."\n";
544 print $indexfile '</head><body>'."\n";
545 print $indexfile '<a href="/"><img id="botmlogo" src="'.CGI_LOGO_PATH.'" alt="'.WEBSITE.'"></a>'."\n";
546 print $indexfile '<div id="all">'."\n";
548 print $indexfile '<div id="inst" class="ins">'."\n";
550 print $indexfile '<div id="title">'."\n";
551 print $indexfile '<H1 id="titletext">';
552 if($mode == 8 || $mode == 4) {
553 print $indexfile 'Index of';
556 print $indexfile entityencode($settings{'story'});
559 print $indexfile 'Index of '.$indexof;
561 print $indexfile '</H1>'."\n";
562 print $indexfile '</div>'."\n";
564 print $indexfile '</div><div id="framespace">'."\n";
565 print $indexfile '<img src="'.CGI_PATH;
567 print $indexfile 'intf-ll.gif'
570 print $indexfile 'intf-pp.gif'
574 print $indexfile sprintf($settings{'frame'},0,$framedata{'ext'}).'" title="'.entityencode($framedata{'title'});
577 print $indexfile 'intf-tr.gif'
581 print $indexfile 'intf-kw.gif'
583 print $indexfile '" alt="0" id="frame">'."\n";
585 print $indexfile '</div><div id="insb" class="ins">'."\n";
586 print $indexfile '<div id="undertext">'."\n";
587 if($mode == 8 || $mode == 4) {
588 print $indexfile '<img src="/icons/back.gif" alt="[DIR]"> <a href="/">Parent Directory</a><br><img src="/icons/folder.gif" alt="[DIR]"> <a href="#">yyyyb/</a>'."\n";
590 elsif ($mode == 16) {
592 print $indexfile bb2ht($framedata{'content'})."\n";
595 print $indexfile "...\n";
599 print $indexfile '<img src="/icons/back.gif" alt="[DIR]"> <a href="/">Parent Directory</a><br>'."\n";
600 print $indexfile '<img src="/icons/folder.gif" alt="[DIR]"> <a href="2words/">2words/</a> '.INTF_DATE.' - <br>'."\n";
601 print $indexfile '<img src="/icons/folder.gif" alt="[DIR]"> <a href="coin/">coin/</a> '.COIN_DATE.' - '.entityencode($coin{'server'})."\n";
603 print $indexfile '</div>'."\n";
605 print $indexfile '<div id="command">'."\n";
607 print $indexfile '[<span class="br">EE</span>:<span class="br">EE</span>:<span class="br">EE</span>]'."\n";
610 print $indexfile '[<span class="ni">EE</span>:<span class="ni">EE</span>:<span class="ni">EE</span>]'."\n";
612 elsif ($mode == 16) {
614 print $indexfile '[<span class="br">00</span>:<span class="br">00</span>:<span class="br">00</span>]<br>'."\n";
615 print $indexfile '><a href="'.CGI_VIEWER_PATH.'/1">'.entityencode($nextframedata{'title'}).'</a>'."\n";
618 print $indexfile '[<span class="ni">--</span>:<span class="ni">--</span>:<span class="ni">--</span>]<br>'."\n";
619 print $indexfile '>...<span class="inp">_</span>'."\n";
624 print $indexfile '</div>'."\n";
626 print $indexfile '</div>'."\n";
628 print $indexfile '</div>'."\n";
629 print $indexfile '<a href="/" class="cz">'.WEBSITE.'</a>'."\n";
631 print $indexfile '</body></html>'."\n";
634 unless (ref($indexpath)) {
638 truncate ($indexfile , tell($indexfile));