This commit is contained in:
Your Name 2025-10-13 13:49:07 -04:00
commit a54152fcc0
18 changed files with 658 additions and 0 deletions

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
*~
#*
secrets/
*.secret
*.db
*.ttf

3
README.md Executable file
View file

@ -0,0 +1,3 @@
# PHP scripts for a site-sharing website
Needs php imagemagick & sqlite modules.
Edit `config.php` to customize

1
attribution.md Normal file
View file

@ -0,0 +1 @@
https://commons.wikimedia.org/wiki/File:Snowflake11_2.png

BIN
banner.webp Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

52
captcha.php Executable file
View file

@ -0,0 +1,52 @@
<?php
session_start();
/*
A very primitive captcha implementation
*/
/* Create Imagick object */
$Imagick = new Imagick();
/* Create the ImagickPixel object (used to set the background color on image) */
$bg = new ImagickPixel();
/* Set the pixel color to white */
$bg->setColor( 'white');
/* Create a drawing object and set the font size */
$ImagickDraw = new ImagickDraw();
/* Set font and font size. You can also specify /path/to/font.ttf */
$ImagickDraw->setFont( 'captchafont.ttf' );
$ImagickDraw->setFontSize( 25 );
/* Create the text */
$alphanum = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789abcdefghijklmnopqrstuvwxyz';
$string = substr( str_shuffle( $alphanum ), 2, 5 );
$_SESSION['captcha_code'] = $string;
/* Create new empty image */
$Imagick->newImage( 80, 25, $bg );
/* Write the text on the image */
$Imagick->annotateImage( $ImagickDraw, 4, 20, 0, $string );
/* Add some swirl */
$Imagick->swirlImage( rand(10,20) );
/* Create a few random lines */
/*
$ImagickDraw->line( rand( 0, 70 ), rand( 0, 30 ), rand( 0, 70 ), rand( 0, 30 ) );
$ImagickDraw->line( rand( 0, 70 ), rand( 0, 30 ), rand( 0, 70 ), rand( 0, 30 ) );
$ImagickDraw->line( rand( 0, 70 ), rand( 0, 30 ), rand( 0, 70 ), rand( 0, 30 ) );
$ImagickDraw->line( rand( 0, 70 ), rand( 0, 30 ), rand( 0, 70 ), rand( 0, 30 ) );
$ImagickDraw->line( rand( 0, 70 ), rand( 0, 30 ), rand( 0, 70 ), rand( 0, 30 ) );
*/
/* Draw the ImagickDraw object contents to the image. */
$Imagick->drawImage( $ImagickDraw );
/* Give the image a format */
$Imagick->setImageFormat( 'jpg' );
/* Send headers and output the image */
header( "Content-Type: image/{$Imagick->getImageFormat()}" );
echo $Imagick->getImageBlob( );
?>

133
common.php Executable file
View file

@ -0,0 +1,133 @@
<?php
$db = new SQLite3(constant("dbfile"), SQLITE3_OPEN_CREATE | SQLITE3_OPEN_READWRITE);
$db->enableExceptions(true);
$db->query('CREATE TABLE IF NOT EXISTS "sites" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"name" VARCHAR NOT NULL,
"url" VARCHAR NOT NULL,
"summary" VARCHAR NOT NULL,
"category" VARCHAR NOT NULL,
"time" DATETIME NOT NULL,
"nsfw" INTEGER NOT NULL
)');
function submitSite($name, $url, $summary, $category) {
global $db;
$stm = $db->prepare('INSERT INTO "sites" ("name", "url", "summary", "category", "time", "nsfw") VALUES (?,?,?,?,?,?)');
$date = date('Y-m-d H:i:s');
$nsfw = 0;
$stm->bindParam(1, $name);
$stm->bindParam(2, $url);
$stm->bindParam(3, $summary);
$stm->bindParam(4, $category);
$stm->bindParam(5, $date);
$stm->bindParam(6, $nsfw);
return $stm->execute();
}
function getRowCount($cat=null) {
global $db;
if ($cat) {
$stm = $db->prepare('SELECT COUNT(*) as count FROM "sites" WHERE "category" = ? ;');
$stm->bindParam(1, $cat);
$results = $stm->execute();
} else {
$stm = $db->prepare('SELECT COUNT(*) as count FROM "sites";');
$results = $stm->execute();
}
$row = $results->fetchArray();
return intval($row['count']);
}
function getRandom() {
global $db;
$results = $db->query('SELECT * FROM "sites" ORDER BY RANDOM() LIMIT 1;');
$row = $results->fetchArray();
return $row['url'];
}
function renderPage($page=1, $cat) {
global $db;
global $manage;
$no = ($page-1) * 15;
if ($cat) {
$stm = $db->prepare('SELECT * FROM "sites" WHERE "category" = ? ORDER BY "time" DESC LIMIT 15 OFFSET ? ;');
$stm->bindParam(1, $cat);
$stm->bindParam(2, $no);
$results = $stm->execute();
} else {
$stm = $db->prepare('SELECT * FROM "sites" ORDER BY "time" DESC LIMIT 15 OFFSET ? ;');
$stm->bindParam(1, $no);
$results = $stm->execute();
}
while ($row = $results->fetchArray()) {
if ($row["nsfw"]) {
echo("<tr class='nsfw'>");
} else {
echo("<tr>");
}
for ($i=1; $i < 5; $i++) {
echo("<td>");
if ($i == 1) {
if ($manage) {
echo ("<p>[<a href='manage.php?delete=" . $row["id"] . "&manage=" . $_GET["manage"] . "'>Delete</a>]");
echo (" [<a href='manage.php?nsfw=" . $row["id"] . "&manage=" . $_GET["manage"] . "'>+ NSFW</a>]");
echo (" [<a href='manage.php?unnsfw=" . $row["id"] . "&manage=" . $_GET["manage"] . "'>- NSFW</a>]");
echo (" [<a href='manage.php?blacklist=" . $row["id"] . "&manage=" . $_GET["manage"] . "'>Blacklist</a>]</p>");
}
$url = parse_url($row[$i + 1]);
if ($url['scheme'] == 'https' || $url['scheme'] == 'http') {
echo('<img src="https://icons.duckduckgo.com/ip3/' . $url['host'] . '.ico" class="siteicon"> ');
} else {
echo("(" . $url['scheme'] . ")");
}
echo(' <a href="' . $row[$i+1] . '">' . $row[$i] . '</a> ');
if ($row["nsfw"]) {
echo("<small>(NSFW)</small>");
}
} elseif ($i ==2) {
echo($row[$i+1]);
} elseif ($i ==3) {
$name = constant("categories")[$row[$i + 1]];
echo("<a href='?cat=" . $row[$i+1] . "' rel='noreferrer'>" . $name . "</a>");
} else {
echo($row[$i+1]);
}
echo("</td>");
}
echo("</tr>");
}
}
# manage stuff
function deleteSite($id) {
global $db;
$stm = $db->prepare('DELETE FROM "sites" WHERE id = ?;');
$stm->bindParam(1, $id);
return $stm->execute();
}
function markSiteNSFW($id, $nsfw) {
global $db;
$stm = $db->prepare('UPDATE "sites" SET nsfw = ? WHERE id = ?;');
$stm->bindParam(1, $nsfw);
$stm->bindParam(2, $id);
return $stm->execute();
}
if (isset($_GET["manage"])) {
$fh = fopen('secrets/password.secret','r');
if (!$fh) {
$manage = false;
} else {
while ($hash = fgets($fh)) {
if (password_verify($_GET["manage"], $hash)) {
$manage = true;
} else {
$manage = false;
}
}
fclose($fh);
}
} else {
$manage = false;
}
?>

30
config.php Executable file
View file

@ -0,0 +1,30 @@
<?php
error_reporting(E_ERROR | E_PARSE);
define("bannedhosts", array("google.com"));
define("sitename", "Eternal Winter");
define("dbfile", "secrets/eternalwinter.db");
define("footer", '<small>- 🄯 <a href="https://www.xmpub.org">xmpub</a> -<br>Est. Oct 12, 2025</small>');
define("categories", array(
"blog" => "Blog",
"forum" => "Forum",
"forum-gaming" => "Forum/Gaming",
"forum-hobby" => "Forum/Hobby",
"forum-int" => "Forum/International",
"forum-anonymous" => "Forum/Anonymous",
"search" => "Search engine",
"chat" => "Chat",
"wiki" => "Wiki",
"wiki-personal" => "Wiki/Personal",
"music" => "Music",
"anime" => "Anime",
"publicserver" => "Public server",
"Other" => "Other",
"xmpp-server" => "XMPP server",
"xmpp-muc" => "XMPP room",
"irc" => "IRC",
"matrix-room" => "Matrix room",
"other-nothttp" => "Other (nonhttp)"
));
?>

BIN
css/img/bg.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
css/img/paper.webp Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 972 B

107
css/main.css Executable file
View file

@ -0,0 +1,107 @@
html {
display: table;
font-family: "MS Gothic";
margin: auto;
background-color: #a9f;
background-image: url("/css/img/bg.png");
background-size: 100px 100px;
}
body {
vertical-align: middle;
width: 90vw;
padding: 15px;
background-color: #fcc;
border: 2px outset #fee;
box-shadow: 5px 5px;
text-align: center;
}
hr {
border: 1px inset #ecc;
}
.banner {
border: 2px inset #c99;
margin-bottom: 10px;
}
.about {
background-color: lightyellow;
border: 1px dashed black;
background-image: url("/css/img/paper.webp");
background-blend-mode: multiply;
margin-top: 10px;
margin-bottom: 10px;
display: inline-block;
padding: 5px;
}
.pages {
background-color: #999;
border: 1px solid white;
color: yellow;
margin-top: 10px;
margin-bottom: 10px;
}
table {
margin: 0px auto;
border-collapse: collapse;
border: inset #3bb;
background-color: #eef;
}
.captchacontainer {
border: 1px solid #999;
padding: 5px;
border-radius: 3px;
background-color: white;
margin: auto;
display: inline-block;
text-align: center;
}
.captcha {
border: 2px inset #999;
border-radius: 3px;
margin-bottom: 5px;
}
tr, td, th {
padding: 5px;
}
td {
border-right: 1px solid #999;
border-bottom: 1px solid #999;
}
tr:nth-child(even) {
background-color: #ffe;
}
th {
border-bottom: 1px solid #177;
background-color: #9f9;
border-right: 1px solid #000;
}
.siteicon {
height: 32px;
width: 32px;
float: left;
margin-right: 10px;
border: 2px inset black;
}
button, input[type=submit] {
border-radius: 2px;
background-color: #EEFFFF;
border: 2px outset #33AAAA;
}
input[type=text] {
background-color: #fcf;
border: 1px inset #black;
}
select {
background-color: #fcf;
}
.nsfw {
background-color: #f99;
}
.selectedpage {
background-color: #ef9;
}

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

44
index.php Executable file
View file

@ -0,0 +1,44 @@
<?php
include 'config.php';
?>
<html>
<head>
<link rel="stylesheet" href="/css/main.css" id="pagestyle">
<title><?php echo constant("sitename"); ?></title>
</head>
<body>
<img class="banner" src="/banner.webp" /><br>
<div class="about">
<p><?php echo constant("sitename"); ?> lets you discover and share niche, non-corporate, hobby websites with others by submitting them to our database.<br>It's intended to help small forum operators, blog authors, and anyone else with a small website recieve traffic without having to market their site.</p>
</div>
<hr>
<table>
<tr>
<th>name</th>
<th>summary</th>
<th>category</th>
<th>added</th>
</tr>
<?php
require 'pagination.php';
getEntries();
?>
</table>
<br>
<table>
<tr>
<?php
pageButtons();
?>
</tr>
</table>
<br>
<a href="submit.php"><button>+ Add a Site</button></a>
<a href="random.php"><button>Random Site</button></a>
<a href="rules.php"><button>Rules</button></a>
<footer>
<hr>
<?php echo constant("footer"); ?>
</footer>
</body>
</html>

35
manage.php Executable file
View file

@ -0,0 +1,35 @@
<?php
require 'config.php';
require 'common.php';
if (!$manage) {
die();
}
function manage($action) {
switch($action) {
case "delete":
deleteSite(intval($_GET["delete"]));
break;
case "nsfw":
markSiteNSFW(intval($_GET["nsfw"]), 1);
break;
case "unnsfw":
markSiteNSFW(intval($_GET["unnsfw"]), 0);
break;
}
echo($action . " action finished");
die();
}
if ($manage) {
if (isset($_GET["delete"])) {
manage("delete");
}
if (isset($_GET["nsfw"])) {
manage("nsfw");
}
if (isset($_GET["unnsfw"])) {
manage("unnsfw");
}
echo("No action");
die();
}
?>

83
pagination.php Executable file
View file

@ -0,0 +1,83 @@
<?php
require 'common.php';
if (isset($_GET["manage"])) {
$fh = fopen('secrets/password.secret','r');
if (!$fh) {
$manage = false;
} else {
while ($hash = fgets($fh)) {
if (password_verify($_GET["manage"], $hash)) {
$manage = true;
} else {
$manage = false;
}
}
fclose($fh);
}
} else {
$manage = false;
}
if (isset($_GET["page"])) {
$page = intval($_GET["page"]);
} else {
$page = 1;
}
if (isset($_GET["cat"])) {
$cat = $_GET["cat"];
} else {
$cat = null;
}
function getEntries() {
global $cat;
global $page;
renderPage($page, $cat);
}
function params($pageARG=null) {
global $cat;
global $page;
$params = "?page=" . $pageARG;
if ($cat) {
$params = $params . "&cat=" . $cat;
}
return $params;
}
function backButton($params) {
echo("<th><a href='" . $params . "'><button>Back</button></a></th>");
}
function nextButton($params) {
echo("<th><a href='" . $params . "'><button>Next</button></a></th>");
}
function pageLinks() {
global $page;
global $cat;
for ($i=0; $i < (intval(getRowCount($cat)/14)+1); $i++) {
if ($page == $i+1) {
echo("<td class='selectedpage'>");
} else {
echo("<td>");
}
echo("<a href='" . params($i+1) . "'>" . $i + 1 . "</a></td>");
}
}
function pageButtons() {
global $cat;
global $page;
if (1 < $page) {
backButton(params($page-1));
}
pageLinks();
if ($page < intval(getRowCount($cat)/14)+1) {
nextButton(params($page+1));
}
}
?>

3
password.sh Executable file
View file

@ -0,0 +1,3 @@
#!/bin/sh
php -r "echo password_hash('$1', PASSWORD_BCRYPT);" > secrets/password.secret

15
random.php Executable file
View file

@ -0,0 +1,15 @@
<html>
<head>
<link rel="stylesheet" href="/css/main.css" id="pagestyle">
<?php
require 'config.php';
require 'common.php';
echo('<meta http-equiv="refresh" content="5; url=' . getRandom() . '"/>');
?>
<title>Redirecting</title>
</head>
<body>
<p>redirecting you to a random site...</p>
</body>
</html>

28
rules.php Executable file
View file

@ -0,0 +1,28 @@
<?php
include 'config.php';
?>
<html>
<head>
<link rel="stylesheet" href="/css/main.css" id="pagestyle">
<title><?php echo constant("sitename"); ?> | Rules</title>
</head>
<body>
<h1>Rules</h1>
<div style="text-align: left;">
<li><b>Tor/i2p hidden services are not allowed.</b></li>
<li><b>Broken or dead URLs will be rejected.</b> This one's obvious.</li>
<li><b>Sites that require JavaScript to access content or post will be rejected.</b> Sorry. The point of this list is to generate a user-friendly list of sites.</li>
<ul>Sites that are behind Cloudflare, Anubis, or anything like it will be rejected for the same reason. (This rule is subject to change if Anubis gets updated to not require JS)</ul>
<ul>Sites that block Tor or open proxies will be rejected also.</ul>
<ul>This rule doesn't apply to sites that offer the option to let you generate a token to post using an external script or program.</ul>
<li><b>Imageboard websites are not allowed.</b> Textboards, however, are. Reasoning is that imageboards are popping up all the time just to go down a week or month later.</li>
<li><b>Sites with little content will be rejected.</b> This rule does not apply to forums, unless the forum software is incorrectly configured or broken, or there are no posts. I will be liberal about the enforcement about this rule and say that the minimum amount of content your site should have is at <i>least</i> one paragraph.</li>
<li><b>Sites with offensive or upsetting content will be rejected.</b> Edginess is fine, but gore/nsfl will not fly. Sensitive websites will be labelled.</li>
<li>Most importantly, have fun. I don't wanna be antagonistic or rain on anyone's parade with these rules. And I'll never talk down to you or be mean to you on purpose. It can be assumed that if a site doesn't break any of these rules it's okay, but if I find something objectable about your site, I will try to give you some forewarning before just removing it outright.</i>
</div>
<footer>
<hr>
<?php echo constant("footer"); ?>
</footer>
</body>
</html>

118
submit.php Executable file
View file

@ -0,0 +1,118 @@
<?php
session_start();
include 'config.php';
if ($_SERVER["REQUEST_METHOD"] == "POST") {
if (!isset($_POST["name"]) || !isset($_POST["url"]) || !isset($_POST["category"]) || !isset($_POST["captcha"])) {
echo("One of the fields wasn't submitted.");
session_destroy();
die();
}
if (strtoupper($_SESSION['captcha_code']) != strtoupper($_POST["captcha"])) {
echo("Wrong captcha");
session_destroy();
die();
}
$name = $_POST["name"];
$url = $_POST["url"];
$category = $_POST["category"];
$summary = $_POST["summary"];
$url = filter_var($url, FILTER_SANITIZE_URL);
if (!filter_var($url, FILTER_VALIDATE_URL)) {
echo("The url you provided, '$url', is not a valid URL. <br> <small> (Try putting https:// or http:// at the beginning)</small>");
session_destroy();
die();
}
$tld = end(explode(".", parse_url($url, PHP_URL_HOST)));
if ($tld == "onion" || $tld == "i2p") {
echo("Hidden services are not allowed");
session_destroy();
die();
}
if (in_array(parse_url($url, PHP_URL_HOST), constant("bannedhosts"))) {
echo("Blacklisted host, sorry");
session_destroy();
die();
}
if (!array_key_exists($category, constant("categories"))) {
echo("You submitted an invalid category.");
session_destroy();
die();
}
$name = htmlspecialchars($name);
$url = htmlspecialchars($url);
$summary = htmlspecialchars($summary);
if (70 < strlen($name)) {
echo("Name too long");
session_destroy();
die();
}
if (100 < strlen($url)) {
echo("URL too long");
session_destroy();
die();
}
if (70 < strlen($summary)) {
echo("Summary too long");
session_destroy();
die();
}
if (100 < strlen($category)) {
echo("Category too long");
session_destroy();
die();
}
require 'common.php';
if (submitSite($name, $url, $summary, $category)) {
session_destroy();
echo("<html><head><link rel=\"stylesheet\" href=\"/css/main.css\" id=\"pagestyle\"><title>SiteShare | Success</title><body><p>Your site was submitted. Click <a href=\"/\">here</a> to go back to the homepage.</p></body></html>");
die();
} else {
echo("Error");
session_destroy();
die();
}
}
?>
<html>
<head>
<link rel="stylesheet" href="/css/main.css" id="pagestyle">
<title><?php echo constant("sitename"); ?> | Submit</title>
</head>
<body>
<h1>Submit Site</h1>
<div class="about" style="background-color: #eef;">
<p> <b>!</b> Please check out the <a href="rules.php">rules</a> before submitting.</p>
</div>
<form action="submit.php" method="POST">
<table>
<tr><th>Name</th><td><input type="text" name="name" id="name" maxlength="40"> <small>40c</small></td></tr>
<tr><th>URL</th><td><input type="text" name="url" id="url" maxlength="100" size="50"> <small>100c</small>
<tr><th>Summary</th><td><input type="text" name="summary" id="summary" maxlength="70" size="50"> <small>70c</small>
<tr><th>Category</th><td><select name="category" id="category">
<?php
foreach (constant("categories") as $key => $name) {
echo("<option value='");
echo($key);
echo("'>");
echo($name);
echo("</option>");
}
?>
</select></td></tr>
<tr><th>Captcha<br><small>(Case-insensitive)</small></th><td><div class="captchacontainer"><img class="captcha" src="/captcha.php"><br><input type="text" size="5" maxlength="5" name="captcha"></div></td></tr>
<tr><td><input type="submit"></td><td></td></tr>
</table>
</form>
<div style="text-align:left;">
<p>
<li>Wiki - Personal is for wikis with a single editor.</li>
<li>Forum - International is for forums where multiple languages are spoken.</li>
<li>Note: If your site is for a server for another protocol like a game server, pubnix, IRC, or Gemini/Gopher, categorize it as "Public server". <b>Note that non-HTTP links are allowed, but please categorize them as <i>Other (Not HTTP)</i> if there's not a category for them already.</b></li><p>
</div>
<footer>
<hr>
<?php echo constant("footer"); ?>
</footer>
</body>
</html>