@ -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 '%> > $post_id%' ORDER BY post_time";
$ref_query = "SELECT post_id FROM posts WHERE comment LIKE '%> > $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']}\" > > > {$ref['post_id']}< / a > ";
echo "< a href = \"#p{$ref['post_id']}\" > > > {$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('/^> (?!> \d).+/m', '< strong > $0< / strong > ', $comment);
$comment = preg_replace('/^> (?!> \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('/> > \s*(\d+)/', quote_link, $comment);
$comment = preg_replace_callback('/> > \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);
@ -235,54 +260,54 @@
}
}
}
}
// Sends a webhook to Discord
// https://stackoverflow.com/a/9619947
function webhook($name, $message, $img) {
function time_ago_en($time) {
if(empty(DISCORD_WEBHOOK))
if(!is_numeric($time))
return;
$time = strtotime($time);
$data = [
$periods = ['second', 'minute', 'hour', 'day', 'week', 'month', 'year', 'age'];
'username' => $name,
$lengths = [60, 60, 24, 7, 4.35, 12, 100];
'embeds' => [
[
'title' => "New Post",
'url' => 'http://' . $_SERVER['SERVER_NAME'] . $_SERVER['PHP_SELF'] . '#bottom',
'description' => $message,
'image' => [
'url' => $img
]
]
]
];
$curl = curl_init(DISCORD_WEBHOOK);
$now = time();
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$difference = $now - $time;
curl_setopt($curl, CURLOPT_HTTPHEADER, array("Content-Type: application/json"));
if($difference < = 10 & & $difference >= 0)
curl_setopt($curl, CURLOPT_POST, true);
return $tense = 'just now';
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
else if($difference > 0)
curl_exec($curl);
$tense = 'ago';
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
else if($difference < 0 )
curl_close($curl);
$tense = 'later';
if($status != 204)
die("Error: Sending webhook failed with status $status.");
for($j = 0; $difference >= $lengths[$j] & & $j < count ( $ lengths ) -1 ; $ j + + ) {
$difference /= $lengths[$j];
}
}
?>
< !DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
< html xmlns = "http://www.w3.org/1999/xhtml" xml:lang = "en" lang = "en" >
< head >
< meta http-equiv = "content-type" content = "text/html;charset=utf-8" / >
< meta name = "viewport" content = "width=device-width, initial-scale=1" / >
< title > BBS | ピケ.コム< / title >
$difference = round($difference);
< / head >
< body >
$period = $periods[$j] . ($difference >1 ? 's' :'');
< p >
return "{$difference} {$period} {$tense} ";
[< a href = "#bottom" > bottom< / a > ]
}
< / p >
function trip_name($name) {
$split = explode('#', $name);
$name = array_shift($split);
if(empty($split[0]))
return ['name' => $name];
$key = implode('#', $split);
$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
];
}
//// code start ////
<?php
// Connect to the database
// Connect to the database
$dbc = pg_connect("host=$DB_HOST dbname=$DB_NAME user=$DB_USER password=$DB_PASSWORD")
$dbc = pg_connect("host=$DB_HOST dbname=$DB_NAME user=$DB_USER password=$DB_PASSWORD")
or die('Could not connect: ' . pg_last_error());
or die('Could not connect: ' . pg_last_error());
@ -296,22 +321,202 @@
$save_cookie = isset($_POST['save_cookie']);
$save_cookie = isset($_POST['save_cookie']);
$err = post($name, $email, $comment, $img, $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'])) {
} else if($_POST['submit'] == 'Delete' & & !empty($_COOKIE['uid'])) {
foreach($_POST['delete'] as $id) {
foreach($_POST['delete'] as $id) {
cleanup($id);
cleanup($id);
}
}
}
}
show_posts();
$mobile = preg_match('/Mobile|Nintendo DSi/', $_SERVER['HTTP_USER_AGENT']);
if($mobile) {
$header_image = 'header-mobile.gif';
} else {
$header_image = 'header.gif';
}
?>
< !DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
< html xmlns = "http://www.w3.org/1999/xhtml" xml:lang = "en" lang = "en" >
< head >
< meta http-equiv = "content-type" content = "text/html;charset=utf-8" / >
< meta name = "viewport" content = "width=device-width, initial-scale=1" / >
pg_close($dbc);
< title > BBS | ピケ.コム< / title >
?>
< form enctype = "multipart/form-data" method = "post" action = " <?php echo $_SERVER [ 'PHP_SELF' ]; ?> #bottom" >
< link rel = "alternate" type = "application/rss+xml" title = "RSS feed" href = "/rss.php" / >
< input type = "hidden" name = "MAX_FILE_SIZE" value = " <?php echo MAX_FILE_SIZE ; ?> " />
< fieldset id = "bottom" >
< style type = "text/css" >
< legend > New Post< / legend >
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 >
< body class = "center" >
< img class = "center" src = " <?php echo $header_image ; ?> " alt = "BBS.ピケ.コム" />
< p >
[< 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 >
< hr / >
< form enctype = "multipart/form-data" method = "post" action = " <?php echo $_SERVER [ 'PHP_SELF' ]; ?> #newpost" >
< input type = "hidden" name = "MAX_FILE_SIZE" value = " <?php echo MAX_FILE_SIZE ; ?> " />
< div id = "newpost" class = "center" >
< h2 > New Post< / h2 >
< div class = "postbody" >
<?php if ( $mobile ) { ?>
< label for = "name" > Name:< / label > (Optional)
< br / >
< input id = "name" name = "name" value = " <?php if ( ! empty ( $err )) echo htmlspecialchars ( $name ); ?> " />
< br / >
< br / >
< label for = "email" > Email:< / label > (Optional)
< 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 >
< table >
< tr >
< tr >
< td > < label for = "name" > Name:< / label > < / td >
< td > < label for = "name" > Name:< / label > < / td >
@ -338,25 +543,56 @@
< td > < input type = "submit" value = "Post" name = "submit" / > < / td >
< td > < input type = "submit" value = "Post" name = "submit" / > < / td >
< / tr >
< / tr >
< / table >
< / table >
<?php } ?>
<?php if ( ! empty ( $err )) echo "<br /><strong> $err </strong>" ; ?>
<?php if ( ! empty ( $err )) echo "<p><strong class= \" error \" > $err </strong></p>" ; ?>
< / fieldset >
< / div >
< / div >
< / form >
< / form >
< hr / >
<?php
show_posts();
pg_close($dbc);
?>
< hr / >
< div id = "footer" >
< p >
< p >
Old posts are automatically deleted once there are more than <?php echo MAX_POSTS ; ?> , anything inappropriate will be deleted.
Welcome to my silly little bulletin board. It's primarily intended
for personal use transferring images from devices where that is
otherwise difficult, however anyone is free to use it.
Old posts are automatically deleted once there are more than
<?php echo MAX_POSTS ; ?> , anything inappropriate will be deleted.
< / p >
< / p >
< p >
< p >
Formatting is very simple, just < strong class = "quote" > > quote< / strong >
by starting a line with > and link to other posts with two & gt
and the post number, < del > > > 1< / del > . No other formatting is
supported besides the automatic conversion of hyperlinks.
You may provide a name and/or email address when posting if you wish
to identify yourself, however it is not required. A tripcode can be
used by writing "Name#Password" in the name field.
< / p >
< p >
The year is <?php echo date ( 'Y' ); ?> , but no © . This page was generated in <?php printf ( "%.2f" , microtime ( true ) - $start_time ); ?> seconds.
< / p >
< / div >
< p id = "bottom" >
[< 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 >