diff --git a/LICENSE b/LICENSE index 3d98fad..5a5ab3e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014-2015, flashwave <me@flash.moe> +Copyright (c) 2014-2025, flashwave <me@flash.moe> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 4823356..0571088 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,9 @@ -ninechan -======== +# ninechan -A simple text based discussion board of which you can find the demo, live development, suggestions and support boards on http://ninechan.flash.moe/ +After the release of the final ninechan package/version, I've done a bunch of maintenance. +This repository makes all of that public. +So far this is not a continuation of the project and 2.1x isn't a drop-in update since it has a bunch of changes specific to my environment, most of which being an attempt to stop the flood of spam... -The version on GitHub is _usually_ in sync with the one on the development board. +The source code is still licenced under the MIT licence. -When downloading ninechan make sure to download it from the Releases tab thing and not master to ensure the stability of your board. - -Requirements ------------- - -| *Server-Side* | *Client-Side* | -| ---------------------------------- | ---------------------------- | -| PHP >= 5.3 | Enabled JavaScript | -| Any PDO compatible Database Engine | CSS Compatibility | -| Short PHP Tags | Cookies enabled | - -Features --------- -- Easy Installation and Upgrading -- Moderation -- Tripcodes -- Youtube Embedding -- Easy language changing and adding -- Quoting/"greentexting" -- Multiple styles -- reCAPTCHAs -- Password based post deletion - -Help ----- -Although I don't really see when you need help with the script just send me an email (email address is on github profile) or ask about it in the issues section +Thank you for your interest in ninechan :) diff --git a/config.example.php b/config.example.php index e2a9429..492feb8 100644 --- a/config.example.php +++ b/config.example.php @@ -119,3 +119,14 @@ $ninechan['reCaptchaPublic'] = "6LdQwAYTAAAAAKSW9Q7U6qS6HFTwotccCMr1Ejri"; // reCAPTCHA Private key $ninechan['reCaptchaPrivate'] = "6LdQwAYTAAAAAJ3NUhUmFyc814B7AM1LGbVwWKp2"; + +$navBoards = [ + 'dev' => ['ninechan', 'dev', 'Board'], + //'sup' => ['ninechan', 'sup', 'Support'], + //'sug' => ['ninechan', 'sug', 'Suggestions'], +]; + +$navLinks = [ + 'Home' => '/', + 'Downloads' => '/dl', +]; diff --git a/index.php b/index.php index d5f787c..ec35fff 100644 --- a/index.php +++ b/index.php @@ -6,13 +6,21 @@ */ // Set ninechan version, don't change this or it'll probably break things. -$version = '2.1'; +$version = '2.1x'; // Language file versions this version is compatible with $langCompat = [ '2.1' ]; +function ncCheckRemoteAddr(string $addr): float { + $addr = rawurlencode($addr); + $mail = rawurlencode('ninechan-ipintel@flash.moe'); + // banned from the system, bother with this at some point + //$result = file_get_contents("https://check.getipintel.net/check.php?ip={$addr}&contact={$mail}"); + return 0; +} + // Error messages function error($data) { @@ -219,7 +227,7 @@ function nHead() { foreach($styles as $url => $name) { // Get the key of the first entry - $main = key($styles); + $main = key($styles); // Append styles to the header $header .= '<link rel="'. ($url == $main ? null : 'alternate ') .'stylesheet" type="text/css" href="'. $url .'" title="'. $name .'" '. ($url == $main ? null : 'disabled ') .'/>'; @@ -232,7 +240,14 @@ function nHead() { <body> <h1><a href="./">'. getConfig('title') .'</a></h1>'. (getConfig('desc') ? ' <i>'. getConfig('desc') .'</i>' : '') - .'<hr /><h6>[<a href="?v=index">'. getLang('INDEX') .'</a>] [<a href="?v=post">'. getLang('NEWTHREAD') .'</a>] [<a href="?v=mod">'. getLang('MANAGE') .'</a>]</h6><hr />'; + .'<hr />'; + + $header .= '<h5>'; + foreach($GLOBALS['navLinks'] as $title => $url) + $header .= " [<a href='{$url}'>{$title}</a>]"; + foreach($GLOBALS['navBoards'] as $link => $board) + $header .= " [<a href='/{$link}/'>{$board[2]}</a>]"; + $header .= '</h5><hr/><h6>[<a href="?v=index">'. getLang('INDEX') .'</a>] [<a href="?v=soup">'. getLang('NEWTHREAD') .'</a>]</h6><hr />'; return $header; @@ -269,9 +284,9 @@ function nFoot() { */ $footer .= '<h6> - <a href="http://ninechan.flash.moe/" target="_blank">ninechan</a> + <a href="https://patchii.net/flash/ninechan" target="_blank">ninechan</a> '. (getConfig('showVersion') ? $version : '') .' - © <a href="http://flash.moe/" target="_blank">Flashwave</a> + © <a href="https://flash.moe" target="_blank">flashwave</a> </h6> </body> </html>'; @@ -413,6 +428,15 @@ CREATE TABLE IF NOT EXISTS `". $sql['table'] ."` ( session_start(); // Start a session $auth = @$_SESSION['mod']; // Set an alias for mod +$remoteAddr = $_SERVER['REMOTE_ADDR']; +$remoteAddrKey = 'ip_' . $remoteAddr; +if(true || !isset($_SESSION[$remoteAddrKey]) || $_SESSION[$remoteAddrKey] < 0) { + $_SESSION[$remoteAddrKey] = $remoteAddrState = ncCheckRemoteAddr($remoteAddr); + if($remoteAddrState >= .8) + file_put_contents(__DIR__ . '/../shame.txt', sprintf('[%s] %s - %F - %s%s', date('Y-m-d H:i:s'), $remoteAddr, $remoteAddrState, htmlspecialchars($_SERVER['HTTP_USER_AGENT'] ?? '(none)'), PHP_EOL), FILE_APPEND | LOCK_EX); +} else + $remoteAddrState = $_SESSION[$remoteAddrKey]; + // Print the header print nHead(); @@ -438,6 +462,10 @@ if(isset($_GET['t'])) { } +// Check if the current IP is banned +if($remoteAddrState >= .6) + print '<div class="banmsg">You have been automatically banned from posting on this board. <a href="/bot.html">Click here for more info</a>.</div><hr />'; + // Check if the current IP is banned if(checkBan($_SERVER['REMOTE_ADDR'])) print '<div class="banmsg">'. getLang('USERBANNEDMSG') .'</div><hr />'; @@ -458,7 +486,7 @@ if(isset($_GET['v'])) { print '<h2>'. getLang('THREADS') .'</h2>'; // New thread link - print '<h3><a href="'. $_SERVER['PHP_SELF'] .'?v=post">'. getLang('NEWTHREAD') .'</a></h3>'; + print '<h3><a href="'. $_SERVER['PHP_SELF'] .'?v=soup">'. getLang('NEWTHREAD') .'</a></h3>'; // Get threads $threads = array_chunk(getPosts(), getConfig('threadsPerPage'), true); @@ -469,7 +497,7 @@ if(isset($_GET['v'])) { print '<ol>'; foreach($threads[(isset($_GET['p']) && ($_GET['p'] - 1) >= 0 && array_key_exists(($_GET['p'] - 1), $threads)) ? $_GET['p'] - 1 : 0] as $thread) - print '<li><a href="'. $_SERVER['PHP_SELF'] .'?v=thread&id='. $thread['tid'] .'">'. $thread['title'] .'</a> <span style="font-size: x-small;">[<b title="'. date(getConfig('dateFormat'), $thread['lastreply']) .'">'. (count(getPosts($thread['tid'])) - 1) .'</b>]</span>'; + print '<li><a href="'. $_SERVER['PHP_SELF'] .'?v=thread&id='. $thread['tid'] .'">'. $thread['title'] .'</a> <span style="font-size: x-small;">[<b title="'. date(getConfig('dateFormat'), $thread['lastreply']) .'">'. (count(getPosts($thread['tid'])) - 1) .'</b>]</span>'; print '</ol>'; @@ -489,7 +517,7 @@ if(isset($_GET['v'])) { } // New thread link - print '<h3><a href="'. $_SERVER['PHP_SELF'] .'?v=post">'. getLang('NEWTHREAD') .'</a></h3>'; + print '<h3><a href="'. $_SERVER['PHP_SELF'] .'?v=soup">'. getLang('NEWTHREAD') .'</a></h3>'; break; // Thread view @@ -518,7 +546,7 @@ if(isset($_GET['v'])) { if($thread[0]['locked']) print '<h3>'. getLang('LOCKED') .'</h3>'; else - print '<h3><a href='. $_SERVER['PHP_SELF'] .'?v=post&id='. $thread[0]['tid'] .'>'. getLang('NEWREPLY') .'</a></h3>'; + print '<h3><a href='. $_SERVER['PHP_SELF'] .'?v=soup&id='. $thread[0]['tid'] .'>'. getLang('NEWREPLY') .'</a></h3>'; // Moderator tools if($auth == $ninechan['modPass']) { @@ -526,12 +554,12 @@ if(isset($_GET['v'])) { print '<h6>'; // Purge button - print '[<a href="'. $_SERVER['PHP_SELF'] .'?v=mod&del=purge&id='. $thread[0]['tid'] .'">'. getLang('PURGE') .'</a>]'."\r\n"; + print '[<a href="'. $_SERVER['PHP_SELF'] .'?v=mod&del=purge&id='. $thread[0]['tid'] .'">'. getLang('PURGE') .'</a>]'."\r\n"; if($thread[0]['locked']) - print '[<a href="'. $_SERVER['PHP_SELF'] .'?v=mod&lock=false&id='. $thread[0]['tid'] .'">'. getLang('UNLOCK') .'</a>]'; + print '[<a href="'. $_SERVER['PHP_SELF'] .'?v=mod&lock=false&id='. $thread[0]['tid'] .'">'. getLang('UNLOCK') .'</a>]'; else - print '[<a href="'. $_SERVER['PHP_SELF'] .'?v=mod&lock=true&id='. $thread[0]['tid'] .'">'. getLang('LOCK') .'</a>]'; + print '[<a href="'. $_SERVER['PHP_SELF'] .'?v=mod&lock=true&id='. $thread[0]['tid'] .'">'. getLang('LOCK') .'</a>]'; print '</h6>'; @@ -595,15 +623,15 @@ if(isset($_GET['v'])) { if($auth == $ninechan['modPass']) { print '<h6>'; - print '[<a href="'. $_SERVER['PHP_SELF'] .'?v=mod&del=true&id='. $post['id'] .'&id='. $post['tid'] .'">'. getLang('DELETE') .'</a>]'."\r\n"; - print '[<a href="'. $_SERVER['PHP_SELF'] .'?v=mod&ban='. ($post['ban'] ? 'false' : 'true') .'&id='. $post['id'] .'&id='. $post['tid'] .'">'. getLang($post['ban'] ? 'UNBAN' : 'BAN') .'</a>]'."\r\n"; + print '[<a href="'. $_SERVER['PHP_SELF'] .'?v=mod&del=true&id='. $post['id'] .'&tid='. $post['tid'] .'">'. getLang('DELETE') .'</a>]'."\r\n"; + print '[<a href="'. $_SERVER['PHP_SELF'] .'?v=mod&ban='. ($post['ban'] ? 'false' : 'true') .'&id='. $post['id'] .'&tid='. $post['tid'] .'">'. getLang($post['ban'] ? 'UNBAN' : 'BAN') .'</a>]'."\r\n"; print '[IP: '.base64_decode($post['ip']).']'; print '</h6>'; } // Date and ID - print '<h6><i>'. date(getConfig('dateFormat'), $post['date']) .' <a href="#'. $post['id'] .'">No.</a> <a href="'. $_SERVER['PHP_SELF'] .'?v=post&id='. $post['tid'] .'&text=>>'. $post['id'] .'">'. $post['id'] .'</a> [<a href="'. $_SERVER['PHP_SELF'] .'?v=del&id='. $post['id'] .'" title="'. getLang('DELPOST') .'">X</a>]</i></h6>'; + print '<h6><i>'. date(getConfig('dateFormat'), $post['date']) .' <a href="#'. $post['id'] .'">No.</a> <a href="'. $_SERVER['PHP_SELF'] .'?v=soup&id='. $post['tid'] .'&text=>>'. $post['id'] .'">'. $post['id'] .'</a> [<a href="'. $_SERVER['PHP_SELF'] .'?v=del&id='. $post['id'] .'" title="'. getLang('DELPOST') .'">X</a>]</i></h6>'; print '</fieldset>'; @@ -615,12 +643,12 @@ if(isset($_GET['v'])) { print '<h6>'; // Purge button - print '[<a href="'. $_SERVER['PHP_SELF'] .'?v=mod&del=purge&id='. $thread[0]['tid'] .'">'. getLang('PURGE') .'</a>]'."\r\n"; + print '[<a href="'. $_SERVER['PHP_SELF'] .'?v=mod&del=purge&id='. $thread[0]['tid'] .'">'. getLang('PURGE') .'</a>]'."\r\n"; if($thread[0]['locked']) - print '[<a href="'. $_SERVER['PHP_SELF'] .'?v=mod&lock=false&id='. $thread[0]['tid'] .'">'. getLang('UNLOCK') .'</a>]'; + print '[<a href="'. $_SERVER['PHP_SELF'] .'?v=mod&lock=false&id='. $thread[0]['tid'] .'">'. getLang('UNLOCK') .'</a>]'; else - print '[<a href="'. $_SERVER['PHP_SELF'] .'?v=mod&lock=true&id='. $thread[0]['tid'] .'">'. getLang('LOCK') .'</a>]'; + print '[<a href="'. $_SERVER['PHP_SELF'] .'?v=mod&lock=true&id='. $thread[0]['tid'] .'">'. getLang('LOCK') .'</a>]'; print '</h6>'; @@ -630,7 +658,7 @@ if(isset($_GET['v'])) { if($thread[0]['locked']) print '<h3>'. getLang('LOCKED') .'</h3>'; else - print '<h3><a href="'. $_SERVER['PHP_SELF'] .'?v=post&id='. $thread[0]['tid'] .'">'. getLang('NEWREPLY') .'</a></h3>'; + print '<h3><a href="'. $_SERVER['PHP_SELF'] .'?v=soup&id='. $thread[0]['tid'] .'">'. getLang('NEWREPLY') .'</a></h3>'; } else { @@ -642,7 +670,11 @@ if(isset($_GET['v'])) { break; // Posting - case 'post': + case 'soup': + if($remoteAddrState >= .6) { + header('Location: /bot.html'); + exit; + } // Check if user is banned and if so don't display the form at all if(checkBan($_SERVER['REMOTE_ADDR'])) { @@ -651,7 +683,7 @@ if(isset($_GET['v'])) { } // Print "global" form elements - print '<form method="post" action="'. $_SERVER['PHP_SELF'] .'?v=submit">'; + print '<form method="post" action="'. $_SERVER['PHP_SELF'] .'?v=boat">'; print '<table id="postForm" class="postForm">'; // Predefine that we're creating a new thread @@ -700,7 +732,7 @@ if(isset($_GET['v'])) { if($locked) { print '<h2>'. getLang('LOCKEDMSG') .'</h2>'; - print '<meta http-equiv="refresh" content="2; URL="'. $_SERVER['PHP_SELF'] .'?v=thread&id='. $thread['tid'] .'" />'; + print '<meta http-equiv="refresh" content="2; URL="'. $_SERVER['PHP_SELF'] .'?v=thread&id='. $thread['tid'] .'" />'; } else { @@ -749,7 +781,11 @@ if(isset($_GET['v'])) { break; // Submitting posts - case 'submit': + case 'boat': + if($remoteAddrState >= .6) { + header('Location: /bot.html'); + exit; + } // Check if IP banned if(checkBan($_SERVER['REMOTE_ADDR'])) { @@ -858,10 +894,14 @@ if(isset($_GET['v'])) { print '<h1>'. getLang('POSTED') .'</h1>'; - print '<meta http-equiv="refresh" content="1; URL='. $_SERVER['PHP_SELF'] . ($submitData['noredir'] ? '?v=index' : '?v=thread&id='. $submitData['id']) .'" />'; + print '<meta http-equiv="refresh" content="1; URL='. $_SERVER['PHP_SELF'] . ($submitData['noredir'] ? '?v=index' : '?v=thread&id='. $submitData['id']) .'" />'; break; case 'del': + if($remoteAddrState >= .6) { + header('Location: /bot.html'); + exit; + } // Check if IP banned if(checkBan($_SERVER['REMOTE_ADDR'])) { @@ -963,103 +1003,10 @@ if(isset($_GET['v'])) { } break; - // Moderator Authentication - case 'mod': - if($auth == getConfig('modPass')) { // Check if authenticated - - // Page title - print '<h2>'. getLang('MODLOGOUT') .'</h2>'; - - if(isset($_POST['modkill'])) { // POST request modkill is set... - - session_destroy(); // ...kill moderator session... - header('Location: '. $_SERVER['PHP_SELF'] .'?v=mod'); // ...and redirect to ?v=mod - print '<meta http-equiv="refresh" content="0; url='. $_SERVER['PHP_SELF'] .'?v=mod" />'; // fallback - exit; - - } - - // Print logout form - print '<form method="post" action="'.$_SERVER['PHP_SELF'].'?v=mod">'; - print getLang('MODTOOLS') .'<br />'; - print '<input type="submit" value="'. getLang('LOGOUT') .'" name="modkill" />'; - print '</form>'; - - // Ban handler - if(isset($_GET['ban']) && isset($_GET['id']) && isset($_GET['id'])) { - - if($_GET['ban'] == "true") - nMod('ban', $_GET['id'], true); - else - nMod('ban', $_GET['id'], false); - - header('Location: '. $_SERVER['PHP_SELF'] .'?v=thread&id='.$_GET['id']); - print '<meta http-equiv="refresh" content="0; url='. $_SERVER['PHP_SELF'] .'?v=thread&id='.$_GET['id'].'" />'; // fallback - exit; - - } - - // Deletion handler - if(isset($_GET['del']) && isset($_GET['id'])) { - - if($_GET['del'] == "purge") { - - nMod('prune', $_GET['id'], true); - - header('Location: '. $_SERVER['PHP_SELF'] .'?v=index'); - print '<meta http-equiv="refresh" content="0; url='. $_SERVER['PHP_SELF'] .'?v=index" />'; // fallback - exit; - - } else { - - if($_GET['del'] == "true") - nMod('del', $_GET['id'], true); - else - nMod('del', $_GET['id'], false); - - header('Location: '. $_SERVER['PHP_SELF'] .'?v=thread&id='.$_GET['id']); - print '<meta http-equiv="refresh" content="0; url='. $_SERVER['PHP_SELF'] .'?v=thread&id='.$_GET['id'].'" />'; // fallback - exit; - - } - - } - - // Lock handler - if(isset($_GET['lock']) && isset($_GET['id'])) { - - if($_GET['lock'] == "true") - nMod('lock', $_GET['id'], true); - else - nMod('lock', $_GET['id'], false); - - header('Location: '. $_SERVER['PHP_SELF'] .'?v=thread&id='.$_GET['id']); - print '<meta http-equiv="refresh" content="0; url='. $_SERVER['PHP_SELF'] .'?v=thread&id='.$_GET['id'].'" />'; // fallback - exit; - - } - - } else { - - // Else display login screen - if(isset($_POST['modPass'])) { - - if($_POST['modPass'] == $ninechan['modPass']) - $_SESSION['mod'] = $ninechan['modPass']; - - header('Location: '. $_SERVER['PHP_SELF'] .'?v=mod'); - print '<meta http-equiv="refresh" content="0; url='. $_SERVER['PHP_SELF'] .'?v=mod" />'; // fallback - exit; - - } - - print '<h2>'. getLang('MODLOGIN') .'</h2>'; - print '<form method="post" action="'. $_SERVER['PHP_SELF'] .'?v=mod">'; - print '<input type="password" name="modPass" /><input type="submit" value="'. getLang('LOGIN') .'" />'; - print '</form>'; - - } - break; + case 'destroy': + header('Location: ./'); + session_destroy(); + exit; // Default action default: diff --git a/lang/en.php b/lang/en.php index 58e8405..26d0d38 100644 --- a/lang/en.php +++ b/lang/en.php @@ -12,8 +12,8 @@ $language = [ // Board 'THREADS' => 'Threads', 'THREAD' => 'Thread', - 'NEWTHREAD' => 'New Thread', - 'NEWREPLY' => 'New Reply', + 'NEWTHREAD' => '⁠N⁠e⁠w⁠ ⁠T⁠h⁠r⁠e⁠a⁠d⁠', + 'NEWREPLY' => '⁠N⁠e⁠w⁠ ⁠R⁠e⁠p⁠l⁠y⁠', 'LOCKED' => 'Locked', 'BY' => 'by', 'RETURN' => 'Return to Index',