Pk11 3 years ago committed by GitHub
parent 218258e13b
commit 9c80fd61f6

@ -25,6 +25,8 @@
* For more information, please refer to <http://unlicense.org/> * For more information, please refer to <http://unlicense.org/>
*/ */
$start_time = microtime(true);
// Return source code // Return source code
if(isset($_GET['source'])) { if(isset($_GET['source'])) {
header("Content-Type: text/plain"); header("Content-Type: text/plain");
@ -40,7 +42,6 @@
* define('ADMIN_ID', '<SHA1 uid from your cookies>'); // uid that is able to delete anything * define('ADMIN_ID', '<SHA1 uid from your cookies>'); // uid that is able to delete anything
* define('UID_SALT', '<some string to salt uids with>'); * define('UID_SALT', '<some string to salt uids with>');
* define('MAX_POSTS', 50); // Max posts after which old posts will be auto deleted. Only existing posts count, not manually deleted ones. * define('MAX_POSTS', 50); // Max posts after which old posts will be auto deleted. Only existing posts count, not manually deleted ones.
* define('DISCORD_WEBHOOK', 'https://discord.com/api/webhooks/<webhook id>/<webhook token>'); // A message will be sent to this webhook on post, for easier moderation
* *
* // Database constants for PostgreSQL database * // Database constants for PostgreSQL database
* $DB_HOST = 'localhost'; * $DB_HOST = 'localhost';
@ -55,6 +56,7 @@
* post_time TIMESTAMPTZ NOT NULL DEFAULT NOW(), * post_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
* user_id VARCHAR(40), * user_id VARCHAR(40),
* name VARCHAR(256), * name VARCHAR(256),
* trip VARCHAR(10),
* email VARCHAR(256), * email VARCHAR(256),
* comment VARCHAR(2048), * comment VARCHAR(2048),
* img VARCHAR(256) * img VARCHAR(256)
@ -81,21 +83,30 @@
&& ($img['size'] > 0) && ($img['size'] <= MAX_FILE_SIZE)) { && ($img['size'] > 0) && ($img['size'] <= MAX_FILE_SIZE)) {
if($img['error'] == 0) { if($img['error'] == 0) {
// Move the file to the target upload folder // Move the file to the target upload folder
$name = time(); $img_name = time();
$target = UPLOAD_PATH . $name . $extensions[$img['type']]; $ext = $extensions[$img['type']];
if(move_uploaded_file($img['tmp_name'], $target)) { $temp = UPLOAD_PATH . "__temp$ext";
$target = UPLOAD_PATH . $img_name . $ext;
if(move_uploaded_file($img['tmp_name'], $temp)) {
$output = null; $output = null;
$ret = null; $ret = null;
exec("ffmpeg -y -loglevel error -i '$target' -vf 'scale=-1:min(100\,ih)' " . THUMB_PATH . "$name.jpg", $output, $ret);
// Strip EXIF data
exec("ffmpeg -y -loglevel error -i $temp '$target'", $output, $ret);
@unlink($temp);
if($ret != 0)
return 'Unable to upload image, please contact the webmaster.';
// Create thumbnail
exec("ffmpeg -y -loglevel error -i '$target' -filter_complex 'color=#ffccdd[c];[c][0]scale2ref[cs][0s];[cs][0s]overlay=shortest=1[o];[o]scale=min(200\,iw):-1[o];[o]scale=-1:min(100\,ih)' " . THUMB_PATH . "$img_name.jpg", $output, $ret);
if($ret != 0) { if($ret != 0) {
// The new image file move failed, so delete the temporary file and return an error
@unlink($target); @unlink($target);
return 'Unable to create thumbnail, please contact the webmaster.'; return 'Unable to create thumbnail, please contact the webmaster.';
} }
} else { } else {
// The new image file move failed, so delete the temporary file and return an error // The new image file move failed, so delete the temporary file and return an error
@unlink($img['tmp_name']); @unlink($img['tmp_name']);
return 'Unable to upload image, please contact the webmaster.'; return 'Unable to rename image, please contact the webmaster.';
} }
} }
} else { } else {
@ -131,16 +142,18 @@
} }
} }
$trip_name = trip_name($name);
// Add post to database // Add post to database
$query = "INSERT INTO posts (user_id, name, email, comment, img) VALUES ($1, $2, $3, $4, $5)"; $query = "INSERT INTO posts (user_id, name, trip, email, comment, img) VALUES ($1, $2, $3, $4, $5, $6)";
$params = [ $params = [
empty($uid) ? NULL : $uid, empty($uid) ? NULL : $uid,
empty($name) ? 'Anonymous' : htmlspecialchars($name), empty($trip_name['name']) ? 'Anonymous' : htmlspecialchars($trip_name['name']),
empty($trip_name['trip']) ? NULL : htmlspecialchars($trip_name['trip']),
empty($email) ? NULL : $email, empty($email) ? NULL : $email,
empty($comment) ? NULL : htmlspecialchars($comment), empty($comment) ? NULL : htmlspecialchars($comment),
empty($target) ? NULL : basename($target) empty($target) ? NULL : basename($target)
]; ];
webhook($params[1], $params[3], 'http://' . $_SERVER['SERVER_NAME'] . dirname($_SERVER['PHP_SELF']) . $target); // Send to discord for moderation
pg_query_params($query, $params) or die('Query failed: ' . pg_last_error()); pg_query_params($query, $params) or die('Query failed: ' . pg_last_error());
return ""; // Success, no error return ""; // Success, no error
@ -160,7 +173,7 @@
// Prints the post list // Prints the post list
function show_posts() { function show_posts() {
$query = 'SELECT post_id, user_id, name, email, comment, img, TO_CHAR(post_time, \'YYYY-MM-DD HH24:MI (TZ)\') AS post_time FROM posts ORDER BY posts.post_time'; $query = 'SELECT post_id, user_id, name, trip, email, comment, img, post_time FROM posts ORDER BY posts.post_time';
$result = pg_query($query) or die('Query failed: ' . pg_last_error()); $result = pg_query($query) or die('Query failed: ' . pg_last_error());
// Clean up old posts // Clean up old posts
@ -174,28 +187,39 @@
// Print posts // Print posts
$show_delete = FALSE; $show_delete = FALSE;
echo '<form action="' . $_SERVER['PHP_SELF'] .'#bottom" method="post">'; echo '<form action="' . $_SERVER['PHP_SELF'] . '#bottom" method="post">';
while($row = pg_fetch_array($result)) { while($row = pg_fetch_array($result)) {
echo "<fieldset id=\"p{$row['post_id']}\">"; echo "<div id=\"p{$row['post_id']}\" class=\"post\">";
$time_rel = time_ago_en($row['post_time']);
$time_abs = date('Y-m-d H:m (T)', strtotime($row['post_time']));
echo '<legend>'; echo '<p class="posthead">';
if((!empty($row['user_id']) && ($row['user_id'] == $_COOKIE['uid'])) || (!empty(ADMIN_ID) && ($_COOKIE['uid'] == ADMIN_ID))) { if((!empty($row['user_id']) && ($row['user_id'] == $_COOKIE['uid'])) || (!empty(ADMIN_ID) && ($_COOKIE['uid'] == ADMIN_ID))) {
echo '<input type="checkbox" name="delete[]" value="' . $row['post_id'] . '" /> '; echo '<input type="checkbox" name="delete[]" value="' . $row['post_id'] . '" /> ';
$show_delete = TRUE; $show_delete = TRUE;
} }
echo '<strong class="name">';
if(!empty($row['email'])) if(!empty($row['email']))
echo "<strong><a href=\"mailto:{$row['email']}\">{$row['name']}</a></strong> {$row['post_time']} "; echo "<a href=\"mailto:{$row['email']}\">{$row['name']}</a>";
else else
echo "<strong>{$row['name']}</strong> {$row['post_time']} "; echo $row['name'];
echo '</strong>';
if(!empty($row['trip']))
echo "<span class=\"trip\">◆{$row['trip']}</span>";
echo " <span title=\"$time_abs\">$time_rel</span> ";
echo "<a href=\"#p{$row['post_id']}\">#{$row['post_id']}</a> "; echo "<a href=\"#p{$row['post_id']}\">#{$row['post_id']}</a> ";
// Find references // Find references
$post_id = pg_escape_string($row['post_id']); $post_id = pg_escape_string($row['post_id']);
$ref_query = "SELECT post_id FROM posts WHERE comment LIKE '%&gt;&gt;$post_id%' ORDER BY post_time"; $ref_query = "SELECT post_id FROM posts WHERE comment LIKE '%&gt;&gt;$post_id%' ORDER BY post_time";
$ref_result = pg_query_params($ref_query, []) or die('Query failed: ' . pg_last_error()); $ref_result = pg_query_params($ref_query, []) or die('Query failed: ' . pg_last_error());
while($ref = pg_fetch_array($ref_result)) while($ref = pg_fetch_array($ref_result))
echo "<a href=\"#p{$ref['post_id']}\">&gt;&gt;{$ref['post_id']}</a> "; echo "<a href=\"#p{$ref['post_id']}\">&gt;&gt;{$ref['post_id']}</a> ";
echo '</legend>'; echo '</p>';
echo '<div class="postbody">';
if(!empty($row['img'])){ if(!empty($row['img'])){
echo '<a href="' . UPLOAD_PATH . $row['img'] . '" target="_blank">'; echo '<a href="' . UPLOAD_PATH . $row['img'] . '" target="_blank">';
echo '<img src="' . THUMB_PATH . preg_replace('/\.(?:png|gif|bmp)$/', '.jpg', $row['img']) . '" alt="' . $row['img'] . '" />'; echo '<img src="' . THUMB_PATH . preg_replace('/\.(?:png|gif|bmp)$/', '.jpg', $row['img']) . '" alt="' . $row['img'] . '" />';
@ -205,14 +229,15 @@
// Process quotes, links, and newlines // Process quotes, links, and newlines
if(!empty($row['comment'])) { if(!empty($row['comment'])) {
$comment = $row['comment']; $comment = $row['comment'];
$comment = preg_replace('/^&gt;(?!&gt;\d).+/m', '<strong>$0</strong>', $comment); $comment = preg_replace('/^&gt;(?!&gt;\d).+/m', '<strong class="quote">$0</strong>', $comment);
$comment = preg_replace('/(?:https?|mailto|tel|ftp):[^\s]+/m', '<a href="$0">$0</a>', $comment); $comment = preg_replace('/(?:https?|mailto|tel|ftp):[^\s]+/m', '<a href="$0">$0</a>', $comment);
$comment = preg_replace_callback('/&gt;&gt;\s*(\d+)/', quote_link, $comment); $comment = preg_replace_callback('/&gt;&gt;\s*(\d+)/', quote_link, $comment);
$comment = str_replace("\n", "<br />", $comment); $comment = str_replace("\n", "<br />", $comment);
echo "<p>$comment</p>"; echo "<div>$comment</div>";
} }
echo '</div>';
echo '</fieldset>'; echo '</div>';
} }
pg_free_result($result); pg_free_result($result);
@ -234,37 +259,91 @@
pg_query_params($query, [$id]) or die('Query failed: ' . pg_last_error()); pg_query_params($query, [$id]) or die('Query failed: ' . pg_last_error());
} }
} }
// https://stackoverflow.com/a/9619947
function time_ago_en($time) {
if(!is_numeric($time))
$time = strtotime($time);
$periods = ['second', 'minute', 'hour', 'day', 'week', 'month', 'year', 'age'];
$lengths = [60, 60, 24, 7, 4.35, 12, 100];
$now = time();
$difference = $now - $time;
if($difference <= 10 && $difference >= 0)
return $tense = 'just now';
else if($difference > 0)
$tense = 'ago';
else if($difference < 0)
$tense = 'later';
for($j = 0; $difference >= $lengths[$j] && $j < count($lengths)-1; $j++) {
$difference /= $lengths[$j];
}
// Sends a webhook to Discord $difference = round($difference);
function webhook($name, $message, $img) {
if(empty(DISCORD_WEBHOOK)) $period = $periods[$j] . ($difference >1 ? 's' :'');
return; return "{$difference} {$period} {$tense} ";
}
$data = [
'username' => $name, function trip_name($name) {
'embeds' => [ $split = explode('#', $name);
[ $name = array_shift($split);
'title' => "New Post", if(empty($split[0]))
'url' => 'http://' . $_SERVER['SERVER_NAME'] . $_SERVER['PHP_SELF'] . '#bottom', return ['name' => $name];
'description' => $message,
'image' => [ $key = implode('#', $split);
'url' => $img $salt = substr($key . 'H.', 1, 2);
] $salt = preg_replace('/[^\.-z]/', '.', $salt);
] $salt = strtr($salt, ':;<=>?@[\\]^_`', 'ABCDEFGabcdef');
] $trip = crypt($key, $salt);
$trip = substr($trip, -10);
return [
'name' => $name,
'trip' => $trip
]; ];
}
$curl = curl_init(DISCORD_WEBHOOK); //// code start ////
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); // Connect to the database
curl_setopt($curl, CURLOPT_HTTPHEADER, array("Content-Type: application/json")); $dbc = pg_connect("host=$DB_HOST dbname=$DB_NAME user=$DB_USER password=$DB_PASSWORD")
curl_setopt($curl, CURLOPT_POST, true); or die('Could not connect: ' . pg_last_error());
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
curl_exec($curl); if($_POST['submit'] == 'Post') {
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE); // Grab the data from the POST
curl_close($curl); $name = trim($_POST['name']);
if($status != 204) $email = trim($_POST['email']);
die("Error: Sending webhook failed with status $status."); $comment = trim($_POST['comment']);
$img = $_FILES['img'];
$save_cookie = isset($_POST['save_cookie']);
$err = post($name, $email, $comment, $img, $save_cookie);
if($err == "") {
# Redirect to latest post
$query = 'SELECT post_id FROM posts ORDER BY posts.post_time DESC LIMIT 1';
$result = pg_query($query) or die('Query failed: ' . pg_last_error());
$row = pg_fetch_array($result);
if($row) {
pg_close($dbc);
header("Location: {$_SERVER['PHP_SELF']}#p{$row['post_id']}", true, 303);
die();
}
}
} else if($_POST['submit'] == 'Delete' && !empty($_COOKIE['uid'])) {
foreach($_POST['delete'] as $id) {
cleanup($id);
}
}
$mobile = preg_match('/Mobile|Nintendo DSi/', $_SERVER['HTTP_USER_AGENT']);
if($mobile) {
$header_image = 'header-mobile.gif';
} else {
$header_image = 'header.gif';
} }
?> ?>
<!DOCTYPE html <!DOCTYPE html
@ -276,87 +355,244 @@
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>BBS | ピケ.コム</title> <title>BBS | ピケ.コム</title>
<link rel="alternate" type="application/rss+xml" title="RSS feed" href="/rss.php" />
<style type="text/css">
body {
max-width: 960px;
background-color: #ebc;
background: url(bg2.gif);
background-attachment: fixed;
color: #201;
}
hr {
border: 0;
border-top: 1px solid #534;
}
textarea {
max-width: 100%;
}
input[type=submit],
button {
background-color: #ebc;
border: 2px outset #dab;
}
input[type=submit]:active,
button:active {
background-color: #dab;
border: 2px inset #dab;
color: #201;
}
strong.error, del {
color: red;
}
strong.quote,
strong.name, strong.name > a,
span.trip {
color: green;
}
strong.quote {
font-weight: normal;
}
#newpost {
max-width: 500px;
padding-bottom: 10px;
}
.center {
margin-left: auto;
margin-right: auto;
display: block;
}
.post,
#newpost {
background-color: #fcd;
border: 5px outset #dab;
margin-bottom: 10px;
}
#newpost > h2 {
margin: 0;
}
#newpost input:not([type=submit]),
#newpost textarea {
background-color: #ebc;
border: 2px inset #dab;
}
#footer {
background-color: #ebc;
border: 5px inset #dab;
padding: 10px;
}
#footer p:first-of-type {
margin-top: 0;
}
#footer p:last-of-type {
margin-bottom: 0;
}
.post {
display: table;
padding: 0 10px 10px 0;
}
.posthead {
margin: 0 0 5px 0;
}
.postbody {
padding-left: 10px;
}
.postbody img {
float: left;
margin-right: 10px;
}
</style>
</head> </head>
<body> <body class="center">
<img class="center" src="<?php echo $header_image; ?>" alt="BBS.ピケ.コム" />
<p> <p>
[<a href="#bottom">bottom</a>] [<a href="#bottom">bottom</a>]
[<a href="<?php echo $_SERVER['PHP_SELF']; ?>">reload</a>]
[<a href="?source">source</a>]
[<a href="rss.php">feed</a>]
[<a href="rss.php?source">feed source</a>]
</p> </p>
<?php <hr />
// Connect to the database
$dbc = pg_connect("host=$DB_HOST dbname=$DB_NAME user=$DB_USER password=$DB_PASSWORD") <form enctype="multipart/form-data" method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>#newpost">
or die('Could not connect: ' . pg_last_error()); <input type="hidden" name="MAX_FILE_SIZE" value="<?php echo MAX_FILE_SIZE; ?>" />
<div id="newpost" class="center">
if($_POST['submit'] == 'Post') { <h2>New Post</h2>
// Grab the data from the POST
$name = trim($_POST['name']); <div class="postbody">
$email = trim($_POST['email']); <?php if($mobile) { ?>
$comment = trim($_POST['comment']); <label for="name">Name:</label> (Optional)
$img = $_FILES['img']; <br />
$save_cookie = isset($_POST['save_cookie']); <input id="name" name="name" value="<?php if(!empty($err)) echo htmlspecialchars($name); ?>" />
<br />
$err = post($name, $email, $comment, $img, $save_cookie); <br />
} else if($_POST['submit'] == 'Delete' && !empty($_COOKIE['uid'])) {
foreach($_POST['delete'] as $id) { <label for="email">Email:</label> (Optional)
cleanup($id); <br />
} <input id="email" name="email" value="<?php if(!empty($err)) echo htmlspecialchars($email); ?>" />
} <br />
<br />
<label for="comment">Comment:</label>
<br />
<textarea id="comment" name="comment" rows="10" cols="27"><?php if(!empty($err)) echo htmlspecialchars($comment); ?></textarea>
<br />
<br />
<label for="img">Image:</label> (Limit: <?php echo MAX_FILE_SIZE >> 10; ?> KiB)
<br />
<input type="file" id="img" name="img" />
<br />
<br />
<label for="save-cookie">Save cookie:</label>
<input type="checkbox" id="save-cookie" name="save_cookie" <?php if($_COOKIE['uid']) echo 'checked'; ?> />
<br />
(Allows deleting your own posts)
<br />
<br />
<input type="submit" value="Post" name="submit" />
<?php } else { ?>
<table>
<tr>
<td><label for="name">Name:</label></td>
<td><input id="name" name="name" value="<?php if(!empty($err)) echo htmlspecialchars($name); ?>" /> (Optional)</td>
</tr>
<tr>
<td><label for="email">Email:</label></td>
<td><input id="email" name="email" value="<?php if(!empty($err)) echo htmlspecialchars($email); ?>" /> (Optional)</td>
</tr>
<tr>
<td><label for="comment">Comment:</label></td>
<td><textarea id="comment" name="comment" rows="10" cols="40"><?php if(!empty($err)) echo htmlspecialchars($comment); ?></textarea></td>
</tr>
<tr>
<td><label for="img">Image:</label></td>
<td><input type="file" id="img" name="img" /> (Limit: <?php echo MAX_FILE_SIZE >> 10; ?> KiB)</td>
</tr>
<tr>
<td><label for="save-cookie">Save cookie:</label></td>
<td><input type="checkbox" id="save-cookie" name="save_cookie" <?php if($_COOKIE['uid']) echo 'checked'; ?> /> (Allows deleting your own posts)</td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Post" name="submit" /></td>
</tr>
</table>
<?php } ?>
<?php if(!empty($err)) echo "<p><strong class=\"error\">$err</strong></p>"; ?>
</div>
</div>
</form>
<hr />
<?php
show_posts(); show_posts();
pg_close($dbc); pg_close($dbc);
?> ?>
<form enctype="multipart/form-data" method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>#bottom"> <hr />
<input type="hidden" name="MAX_FILE_SIZE" value="<?php echo MAX_FILE_SIZE; ?>" />
<fieldset id="bottom"> <div id="footer">
<legend>New Post</legend> <p>
Welcome to my silly little bulletin board. It's primarily intended
<table> for personal use transferring images from devices where that is
<tr> otherwise difficult, however anyone is free to use it.
<td><label for="name">Name:</label></td> Old posts are automatically deleted once there are more than
<td><input id="name" name="name" value="<?php if(!empty($err)) echo htmlspecialchars($name); ?>" /> (Optional)</td> <?php echo MAX_POSTS; ?>, anything inappropriate will be deleted.
</tr> </p>
<tr> <p>
<td><label for="email">Email:</label></td> Formatting is very simple, just <strong class="quote">&gt;quote</strong>
<td><input id="email" name="email" value="<?php if(!empty($err)) echo htmlspecialchars($email); ?>" /> (Optional)</td> by starting a line with &gt; and link to other posts with two &gt
</tr> and the post number, <del>&gt;&gt;1</del>. No other formatting is
<tr> supported besides the automatic conversion of hyperlinks.
<td><label for="comment">Comment:</label></td> You may provide a name and/or email address when posting if you wish
<td><textarea id="comment" name="comment" rows="10" cols="40"><?php if(!empty($err)) echo htmlspecialchars($comment); ?></textarea></td> to identify yourself, however it is not required. A tripcode can be
</tr> used by writing "Name#Password" in the name field.
<tr> </p>
<td><label for="img">Image:</label></td> <p>
<td><input type="file" id="img" name="img" /> (Limit: <?php echo MAX_FILE_SIZE >> 10; ?> KiB)</td> The year is <?php echo date('Y'); ?>, but no &copy;. This page was generated in <?php printf("%.2f", microtime(true) - $start_time); ?> seconds.
</tr> </p>
<tr> </div>
<td><label for="save-cookie">Save cookie:</label></td>
<td><input type="checkbox" id="save-cookie" name="save_cookie" <?php if($_COOKIE['uid']) echo 'checked'; ?> /> (Allows deleting your own posts)</td> <p id="bottom">
</tr>
<tr>
<td></td>
<td><input type="submit" value="Post" name="submit" /></td>
</tr>
</table>
<?php if(!empty($err)) echo "<br /><strong>$err</strong>"; ?>
</fieldset>
</form>
<p>
Old posts are automatically deleted once there are more than <?php echo MAX_POSTS; ?>, anything inappropriate will be deleted.
</p>
<p>
[<a href="#top">top</a>] [<a href="#top">top</a>]
[<a href="<?php echo $_SERVER['PHP_SELF']; ?>">reload</a>] [<a href="<?php echo $_SERVER['PHP_SELF']; ?>">reload</a>]
[<a href="?source">source</a>] [<a href="?source">source</a>]
[<a href="rss.php">feed</a>]
[<a href="rss.php?source">feed source</a>]
</p> </p>
<p> <p>
<a href="//validator.w3.org/check?uri=<?php echo urlencode('http://' . $_SERVER['SERVER_NAME'] . $_SERVER['PHP_SELF']); ?>" target="_blank"> <a href="//validator.w3.org/check?uri=<?php echo urlencode('http://' . $_SERVER['SERVER_NAME'] . $_SERVER['PHP_SELF']); ?>" target="_blank"><img src="//www.w3.org/Icons/valid-xhtml10" alt="Valid XHTML 1.0 Transitional" height="31" width="88" /></a>
<img src="//www.w3.org/Icons/valid-xhtml10" alt="Valid XHTML 1.0 Transitional" height="31" width="88" /> <a href="https://jigsaw.w3.org/css-validator/check/referer"><img style="border:0;width:88px;height:31px" src="https://jigsaw.w3.org/css-validator/images/vcss" alt="Valid CSS!" /></a>
</a>
</p> </p>
</body> </body>
</html> </html>

Loading…
Cancel
Save