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 ###PERL_LIB: use lib /botm/lib/bsta
26 use bsta_lib qw(failpage gethttpheader getcgi entityencode readdatafile writedatafile urlencode bb2ht);
29 ###PERL_CGI_PATH: CGI_PATH = /bsta/
30 ###PERL_CGI_2WORDS_PATH: CGI_2WORDS_PATH = /bsta/2words
31 ###PERL_CGI_CSS_PATH: CGI_CSS_PATH = /bsta/bsta.css
32 ###PERL_CGI_LOGO_PATH: CGI_LOGO_PATH = /bsta/botmlogo.png
33 ###PERL_CGI_VIEWER_PATH: CGI_VIEWER_PATH = /bsta/v
35 ###PERL_DATA_PATH: DATA_PATH = /botm/data/bsta/
36 ###PERL_DATA_COIN_PATH: DATA_COIN_PATH = /botm/data/bsta/coincidence
37 ###PERL_DATA_DEFAULT_PATH: DATA_DEFAULT_PATH = /botm/data/bsta/default
38 ###PERL_DATA_LIST_PATH: DATA_LIST_PATH = /botm/data/bsta/list
39 ###PERL_DATA_SETTINGS_PATH: DATA_SETTINGS_PATH = /botm/data/bsta/settings
40 ###PERL_DATA_STATE_PATH: DATA_STATE_PATH = /botm/data/bsta/state
41 ###PERL_DATA_STORY_PATH: DATA_STORY_PATH = /botm/data/bsta/story
43 ###PERL_WWW_PATH: WWW_PATH = /botm/www/1190/bsta/
44 ###PERL_WWW_INDEX_PATH: WWW_INDEX_PATH = /botm/www/1190/bsta/index.htm
46 ###PERL_WEBSITE: WEBSITE = 1190.bicyclesonthemoon.info
47 ###PERL_WEBSITE_NAME: WEBSITE_NAME = Bicycles on the Moon
48 ###PERL_FAVICON_PATH: FAVICON_PATH = /img/favicon.png
50 ###PERL_COIN_DATE: COIN_DATE = 13-Nov-2016 22:15
51 ###PERL_INTF_DATE: INTF_DATE = 28-Sep-2016 20:34
53 ###PERL_STORY_LENGTH: STORY_LENGTH = 16
54 ###PERL_PAGE_LENGTH: PAGE_LENGTH = 16
55 ###PERL_FIRSTPAGE_LENGTH: FIRSTPAGE_LENGTH = 4
91 delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
92 ###PERL_SET_PATH: $ENV{'PATH'} = /usr/local/bin:/usr/bin:/bin;
94 if ($ENV{'REQUEST_METHOD'} =~ /^(HEAD|GET|POST)$/) {
98 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);
101 %http = gethttpheader (\%ENV);
102 %cgi = getcgi($ENV{'QUERY_STRING'});
104 if ($method eq 'POST') {
105 if ($http{'content-type'} eq 'application/x-www-form-urlencoded') {
106 my %cgipost=getcgi( <STDIN> );
107 foreach my $ind (keys %cgipost) {
108 $cgi{$ind}=$cgipost{$ind};
111 # multipart not supported
113 exit failpage("Status: 415 Unsupported Media Type\n","415 Unsupported Media Type","Unsupported Content-type: $http{'content-type'}.");
117 if ($ENV{'PATH_INFO'} =~ /^\/(.+)$/) {
124 if ($ENV{'HTTP_X_FORWARDED_FOR'} =~ /^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$/) {
127 elsif ($ENV{'REMOTE_ADDR'} =~ /^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$/) {
134 if ($cgi{'words'} ne '') {
135 $words=$cgi{'words'};
138 %settings=readdatafile(DATA_SETTINGS_PATH);
139 %state=readdatafile(DATA_STATE_PATH);
140 $ongstate=int($state{'state'});
143 if (open ($storyfile,"+<",DATA_STORY_PATH)){
145 if (flock($storyfile,2)) {
148 %story=readdatafile($storyfile);
150 if ($story{'lastip'} =~ /^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$/) {
157 $storyid = int($story{'id'});
158 $intfpass = int($story{'pass'});
159 $intfstate = int($story{'state'});
160 $intfmode = $intfstate;
161 $intfpause = $intfstate & 1;
166 if ($IP ne $lastip) {
173 if($words =~ /^bstaaaclear(all)?$/ || $intfstate < 0) {
174 if($words eq 'bstaaaclearall' || $intfstate < -1) {
177 $story{'content'}='';
178 $story{'lastip'}='0.0.0.0';
184 writeindex(WWW_INDEX_PATH,0,0,0);
186 writedatafile($storyfile,%story);
191 $message = "It's not your turn.";
194 if ($words =~ /^([!"\(\),\.:;\?][ \t]*)?([A-Za-z][A-Za-z'\-]*[A-Za-z']?)([!"\(\),\.:;\? \t][ \t]*)([A-Za-z][A-Za-z'\-]*[A-Za-z']?)([!"\(\),\.:;\?]?[ \t]*)$/) {
196 $firstletter = lc(substr($2,0,1));
197 $secondletter = lc(substr($4,0,1));
198 if (($firstletter ne $story{'letter'})&&($story{'letter'} ne '')) {
199 $message = 'The first word must start with '.uc($story{'letter'}).'.';
201 elsif ($firstletter eq $secondletter) {
202 $message = 'The second word can\'t also start with '.uc($firstletter).'.';
205 $story{'content'} = $story{'content'} . $words."\n";
207 $story{'lastip'} = $IP;
208 $story{'letter'} = $secondletter;
210 if ($cgi{'next'} ne '') {
211 if (split(/\r?\n/,$story{'content'}) >= (STORY_LENGTH-1)) {
212 $storypath = DATA_STORY_PATH.$storyid;
213 writedatafile($storypath,%story);
214 $newstory{'id'} = $storyid + 1;
215 $newstory{'letter'}='';
216 $newstory{'lastip'}=$IP;
217 $newstory{'content'}='';
218 $newstory{'pass'}='0';
219 $newstory{'state'}='0';
225 writeindex(WWW_INDEX_PATH,0,0,0);
227 writedatafile($storyfile,%newstory);
230 $message = 'To early to finish this wordgame.';
231 writedatafile($storyfile,%story);
237 $story{'pass'} = '2';
239 writeindex(WWW_INDEX_PATH,2,0,0);
242 elsif(lc($2).' '.lc($4) eq $settings{'unlock'}) {
243 if ($intfpass == 0) {
245 my %framedata = readdatafile(DATA_PATH.0);
246 my %default = readdatafile(DATA_DEFAULT_PATH);
250 $framedata{'ongtime'} = $time;
251 $gotolist{'title-0'} = $framedata{'title'};
252 $gotolist{'ongtime-0'} = $framedata{'ongtime'};
253 writedatafile(DATA_PATH.0,%framedata);
254 writedatafile(DATA_LIST_PATH,%gotolist);
256 foreach my $ind (keys %default) {
257 unless(defined($framedata{$ind})){
258 $framedata{$ind}=$default{$ind};
262 $inpath = DATA_PATH.sprintf($settings{'frame'},0,$framedata{'ext'});
263 $outpath = WWW_PATH.sprintf($settings{'frame'},0,$framedata{'ext'});
265 if(copy ($inpath, $outpath)) {
270 $story{'pass'} = '1';
271 $story{'state'} = '0';
272 writeindex(WWW_INDEX_PATH,1,0,0);
280 $message = 'The password has already been used in this story.';
283 writedatafile($storyfile,%story);
288 $message = 'Please, two words, not more, not less (some punctuation is allowed).';
292 elsif (($cgi{'s'} ne '') && ($intfpass==1) && ($ongstate == 0)) {
293 $intfstate = int($cgi{'s'});
294 if($intfstate > 63 || $intfstate <0) {
297 $intfmode = $intfstate;
298 $intfpause = $intfstate & 1;
302 $story{'state'} = $intfstate;
303 writeindex(WWW_INDEX_PATH,1,$intfmode,$intfpause);
304 writedatafile($storyfile,%story);
306 @storylines = split(/\r?\n/,$story{'content'});
307 if(@storylines & 1) {
314 print "Content-type: text/html\n\n";
315 if($method eq 'HEAD') {
319 print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">'."\n";
320 print '<html lang="en"><head>'."\n";
321 print '<title>Two words • '.WEBSITE_NAME.'</title>'."\n";
322 print '<meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
323 print '<link rel="icon" type="image/png" href="'.FAVICON_PATH.'">'."\n";
324 print '<link rel="stylesheet" href="'.CGI_CSS_PATH.'">'."\n";
325 print '</head><body>'."\n";
326 print '<a href="/"><img id="botmlogo" src="'.CGI_LOGO_PATH.'" alt="'.WEBSITE.'"></a>'."\n";
327 print '<div id="all">'."\n";
329 print '<div id="inst" class="ins">'."\n";
331 print '<div id="title">'."\n";
332 print '<H1 id="titletext">Two words</H1>'."\n";
336 print '<div id="storypuzzle">'."\n";
337 for (my $i = 0; $i < @storylines; ++$i){
338 print '<span class="'.($turn?'ni':'br').'">'.entityencode($storylines[$i]).'</span>'."\n";
343 print '<div id="command">'."\n";
344 if ($message ne '') {
345 print '<span class="br">'.entityencode($message).'</span>'."\n";
349 print '<form method="post" action="'.CGI_2WORDS_PATH.'">'."\n";
350 if ($message eq '') {
351 if ($story{"content"} eq '') {
352 print 'Two words, please:<br>'."\n";
355 print 'Please continue, two words:<br>'."\n";
358 print '<input class="intx" type="text" name="words">'."\n";
359 print '<input class="inbt" type="submit" value="enter">'."\n";
360 if(@storylines >= (STORY_LENGTH-1)) {
361 print '<input class="inbt" type="submit" name="next" value="enter and then start a new one">'."\n";
363 print '</form>'."\n";
366 if ($message eq '') {
367 print 'Wait for it.'."\n";
372 elsif ($message ne '') {
373 print '<div id="command">'."\n";
374 print '<span class="br">'.entityencode($message).'</span>'."\n";
378 if(($intfpass == 1) && ($ongstate == 0)) {
379 print '</div><div id="framespace">'."\n";
380 print '<table id="intftable" cellspacing="0" cellpadding="0"><tr class="intf">'."\n";
382 print '<td colspan="6" class="intf"><img src="'.CGI_PATH.'intf-00';
383 if ($intfmode == 4) {
386 elsif ($intfmode == 8) {
389 elsif ($intfstate == 16) {
392 print'.gif" alt="" class="intf"></td>'."\n";
394 print '</tr><tr class="intf">'."\n";
395 print '<td class="intf"><img src="'.CGI_PATH.'intf-20.gif" alt="o" class="intf"></td>'."\n";
396 print '<td class="intf"><a href="'.CGI_PATH.'2words?s='.($intfpause?17:16).'"><img src="'.CGI_PATH.'intf-10'.(($intfmode==16)?'_':'').'.gif" class="intf" alt=">"></td>'."\n";
397 print '<td class="intf"><a href="'.CGI_PATH.'2words?s='.($intfpause?9:8).'"><img src="'.CGI_PATH.'intf-08'.(($intfmode==8)?'_':'').'.gif" class="intf" alt="<<"></td>'."\n";
398 print '<td class="intf"><a href="'.CGI_PATH.'2words?s='.($intfpause?5:4).'"><img src="'.CGI_PATH.'intf-04'.(($intfmode==4)?'_':'').'.gif" class="intf" alt=">>"></td>'."\n";
399 print '<td class="intf"><a href="'.CGI_PATH.'2words?s='.($intfpause?0:0).'"><img src="'.CGI_PATH.'intf-02.gif" class="intf" alt="^"></td>'."\n";
400 print '<td class="intf"><a href="'.CGI_PATH.'2words?s='.($intfpause?$intfmode:$intfmode+1).'"><img src="'.CGI_PATH.'intf-01'.($intfpause?'_':'').'.gif" class="intf" alt="||"></td>'."\n";
401 print '</tr></table>'."\n";
404 print '</div><div id="insb" class="ins">'."\n";
405 print '<div id="undertext">'."\n";
406 for (my $i = $storyid-1-(($page!=0)?((($page-1)*PAGE_LENGTH)+FIRSTPAGE_LENGTH):0); $i > ($storyid-1-($page*PAGE_LENGTH)- FIRSTPAGE_LENGTH) && $i >= 0; --$i) {
407 $storypath = DATA_STORY_PATH.$i;
408 %newstory = readdatafile($storypath);
409 print '<p class="'.(($i&1)?'br':'ni').'" id="s'.$i.'">'.entityencode($newstory{'content'}).'</p>'."\n";
413 print '<div id="underlinks">'."\n";
414 print '<a href="'.CGI_PATH.'">BSTA</a> | <a href="'.CGI_2WORDS_PATH.'">Once again</a>';
415 if(($storyid - ($page*PAGE_LENGTH)) - FIRSTPAGE_LENGTH > 0) {
416 print ' | <a href="'.CGI_2WORDS_PATH.'/'.($page+1).'">Before</a>';
419 print ' | <a href="'.CGI_2WORDS_PATH.'/'.($page-1).'">Unbefore</a>';
421 if(($storyid - ($page*PAGE_LENGTH)) - FIRSTPAGE_LENGTH > 0) {
422 print ' | <a href="'.CGI_2WORDS_PATH.'/'.(int(($storyid - FIRSTPAGE_LENGTH - 1) / PAGE_LENGTH) + 1).'">Initially</a>';
425 print ' | (Entering words here is irreversible. Your actions might be remembered forever. So please be reasonable.)';
432 print '<a href="/" class="cz">'.WEBSITE.'</a>'."\n";
433 print '</body></html>'."\n";
436 (my $indexpath, my $pass, my $mode, my $pause) = @_;
440 if(ref($indexpath)) {
441 $indexfile=$indexpath;
442 unless (seek($indexfile, 0, 0)) {
447 unless (open ($indexfile, ">", $indexpath)) {
453 $indexof =~ s/\/$//g;
455 my %coin = readdatafile(DATA_COIN_PATH);
458 print $indexfile '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">'."\n";
459 print $indexfile '<html>'."\n";
460 print $indexfile ' <head>'."\n";
461 print $indexfile ' <title>Index of '.$indexof.'</title>'."\n";
462 print $indexfile ' </head>'."\n";
463 print $indexfile ' <body>'."\n";
464 print $indexfile '<h1>Index of '.$indexof.'</h1>'."\n";
465 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";
466 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";
467 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";
468 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";
469 print $indexfile '<tr><th colspan="5"><hr></th></tr>'."\n";
470 print $indexfile '</table>'."\n";
471 print $indexfile '<address>Apache/2.2.22 (Debian) Server at '.WEBSITE.' Port 80</address>'."\n";
472 print $indexfile '</body></html>'."\n";
479 %framedata = readdatafile(DATA_PATH.0);
480 %nextframedata = readdatafile(DATA_PATH.1);
481 %default=readdatafile(DATA_DEFAULT_PATH);
483 # if($mode == 16 && $pause) {
484 # $framedata{'ongtime'} = $time;
485 # writedatafile(DATA_PATH.0,%framedata);
488 foreach my $ind (keys %default) {
489 unless(defined($framedata{$ind})){
490 $framedata{$ind}=$default{$ind};
492 unless(defined($nextframedata{$ind})){
493 $nextframedata{$ind}=$default{$ind};
498 print $indexfile '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">'."\n";
499 print $indexfile '<html lang="en"><head>'."\n";
500 print $indexfile '<title>';
501 if($mode == 8 || $mode == 4) {
502 print $indexfile 'Index of';
505 print $indexfile entityencode($settings{'story'});
507 print $indexfile ' • '.WEBSITE_NAME;
511 print $indexfile 'Index of '.$indexof;
513 print $indexfile '</title>'."\n";
514 print $indexfile '<meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
515 print $indexfile '<link rel="icon" type="image/png" href="'.FAVICON_PATH.'">'."\n";
516 print $indexfile '<link rel="stylesheet" href="'.CGI_CSS_PATH.'">'."\n";
517 print $indexfile '</head><body>'."\n";
518 print $indexfile '<a href="/"><img id="botmlogo" src="'.CGI_LOGO_PATH.'" alt="'.WEBSITE.'"></a>'."\n";
519 print $indexfile '<div id="all">'."\n";
521 print $indexfile '<div id="inst" class="ins">'."\n";
523 print $indexfile '<div id="title">'."\n";
524 print $indexfile '<H1 id="titletext">';
525 if($mode == 8 || $mode == 4) {
526 print $indexfile 'Index of';
529 print $indexfile entityencode($settings{'story'});
532 print $indexfile 'Index of '.$indexof;
534 print $indexfile '</H1>'."\n";
535 print $indexfile '</div>'."\n";
537 print $indexfile '</div><div id="framespace">'."\n";
538 print $indexfile '<img src="'.CGI_PATH;
540 print $indexfile 'intf-ll.gif'
543 print $indexfile 'intf-pp.gif'
547 print $indexfile sprintf($settings{'frame'},0,$framedata{'ext'}).'" title="'.entityencode($framedata{'title'});
550 print $indexfile 'intf-tr.gif'
554 print $indexfile 'intf-kw.gif'
556 print $indexfile '" alt="0" id="frame">'."\n";
558 print $indexfile '</div><div id="insb" class="ins">'."\n";
559 print $indexfile '<div id="undertext">'."\n";
560 if($mode == 8 || $mode == 4) {
561 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";
563 elsif ($mode == 16) {
565 print $indexfile bb2ht($framedata{'content'})."\n";
568 print $indexfile "...\n";
572 print $indexfile '<img src="/icons/back.gif" alt="[DIR]"> <a href="/">Parent Directory</a><br>'."\n";
573 print $indexfile '<img src="/icons/folder.gif" alt="[DIR]"> <a href="2words/">2words/</a> '.INTF_DATE.' - <br>'."\n";
574 print $indexfile '<img src="/icons/folder.gif" alt="[DIR]"> <a href="coin/">coin/</a> '.COIN_DATE.' - '.entityencode($coin{'server'})."\n";
576 print $indexfile '</div>'."\n";
578 print $indexfile '<div id="command">'."\n";
580 print $indexfile '[<span class="br">EE</span>:<span class="br">EE</span>:<span class="br">EE</span>]'."\n";
583 print $indexfile '[<span class="ni">EE</span>:<span class="ni">EE</span>:<span class="ni">EE</span>]'."\n";
585 elsif ($mode == 16) {
587 print $indexfile '[<span class="br">00</span>:<span class="br">00</span>:<span class="br">00</span>]<br>'."\n";
588 print $indexfile '><a href="'.CGI_VIEWER_PATH.'/1">'.entityencode($nextframedata{'title'}).'</a>'."\n";
591 print $indexfile '[<span class="ni">--</span>:<span class="ni">--</span>:<span class="ni">--</span>]<br>'."\n";
592 print $indexfile '>...<span class="inp">_</span>'."\n";
597 print $indexfile '</div>'."\n";
599 print $indexfile '</div>'."\n";
601 print $indexfile '</div>'."\n";
602 print $indexfile '<a href="/" class="cz">'.WEBSITE.'</a>'."\n";
604 print $indexfile '</body></html>'."\n";
607 unless (ref($indexpath)) {
611 truncate ($indexfile , tell($indexfile));