diff --git a/inc/config.php b/inc/config.php
index 71b0fbf4..e324fcf1 100644
--- a/inc/config.php
+++ b/inc/config.php
@@ -943,6 +943,10 @@
// Location of thumbnail to use for deleted images.
$config['image_deleted'] = 'static/deleted.png';
+ // When a thumbnailed image is going to be the same (in dimension), just copy the entire file and use
+ // that as a thumbnail instead of resizing/redrawing.
+ $config['minimum_copy_resize'] = false;
+
// Maximum image upload size in bytes.
$config['max_filesize'] = 10 * 1024 * 1024; // 10MB
// Maximum image dimensions.
@@ -981,6 +985,15 @@
// Set this to true if you're using Linux and you can execute `md5sum` binary.
$config['gnu_md5'] = false;
+ // Use Tesseract OCR to retrieve text from images, so you can use it as a spamfilter.
+ $config['tesseract_ocr'] = false;
+
+ // Tesseract parameters
+ $config['tesseract_params'] = '';
+
+ // Tesseract preprocess command
+ $config['tesseract_preprocess_command'] = 'convert -monochrome %s -';
+
// Number of posts in a "View Last X Posts" page
$config['noko50_count'] = 50;
// Number of posts a thread needs before it gets a "View Last X Posts" page.
@@ -1218,6 +1231,12 @@
'
],
+ [
+ '/^https?:\/\/(\w+\.)?tiktok\.com\/@[a-z0-9\-_]+\/video\/([0-9]+)\?.*$/i',
+ '
+
+
'
+ ],
array(
'/^https?:\/\/(\w+\.)?vimeo\.com\/(\d{2,10})(\?.+)?$/i',
''
diff --git a/js/post-menu.js b/js/post-menu.js
index c2155c00..79cfd868 100644
--- a/js/post-menu.js
+++ b/js/post-menu.js
@@ -104,10 +104,8 @@ function buildMenu(e) {
function addButton(post) {
var $ele = $(post);
- // Use unicode code with ascii variant selector
- // https://stackoverflow.com/questions/37906969/how-to-prevent-ios-from-converting-ascii-into-emoji
$ele.find('input.delete').after(
- $('', {href: '#', class: 'post-btn', title: 'Post menu'}).text('\u{25B6}\u{fe0e}')
+ $('', {href: '#', class: 'post-btn', title: 'Post menu'}).text('►')
);
}
diff --git a/js/tiktok.js b/js/tiktok.js
new file mode 100644
index 00000000..f8f88746
--- /dev/null
+++ b/js/tiktok.js
@@ -0,0 +1,80 @@
+/*
+ * Don't load the 3rd party embedded content player unless the image is clicked.
+ * This increases performance issues when many videos are embedded on the same page and privacy.
+ *
+ * Released under the MIT license
+ * Copyright (c) 2013 Michael Save
+ * Copyright (c) 2013-2014 Marcin Łabanowski
+ * Copyright (c) 2025 Zankaria Auxa
+ *
+ * Usage:
+ * $config['embedding'][0] = figure it out plz;
+ * $config['additional_javascript'][] = 'js/tiktok.js';
+ */
+
+onReady(function() {
+ const REMOVE = '[Remove]';
+ const EMBED = '[Embed]';
+
+ function makeEmbedNode(videoId, width, height) {
+ const iframe = document.createElement('iframe');
+ iframe.setAttribute('type', 'text/html');
+ iframe.width = width;
+ iframe.height = height;
+ iframe.referrerPolicy = 'no-referrer';
+ iframe.src = `https://www.tiktok.com/embed/v2/${videoId}`;
+ iframe.sandbox.add('allow-popups', 'allow-popups-to-escape-sandbox', 'allow-scripts', 'allow-top-navigation', 'allow-same-origin')
+ iframe.classList.add('full-image');
+ return iframe;
+ }
+
+ function addEmbedButton(node) {
+ const contents = node.firstElementChild;
+ const embedUrl = node.dataset.videoId;
+ const embedWidth = node.dataset.iframeWidth;
+ const embedHeight = node.dataset.iframeHeight;
+
+ const span = document.createElement('span');
+ span.textContent = EMBED;
+
+ let iframeDefault = null;
+
+ node.addEventListener('click', function(e) {
+ e.preventDefault();
+
+ if (span.textContent == REMOVE) {
+ contents.style.display = '';
+
+ if (iframeDefault !== null) {
+ iframeDefault.remove();
+ }
+
+ span.textContent = EMBED;
+ } else {
+ if (iframeDefault === null) {
+ iframeDefault = makeEmbedNode(embedUrl, embedWidth, embedHeight);
+ }
+ node.insertBefore(iframeDefault, node.firstElementChild);
+
+ contents.style.display = 'none';
+ span.text(REMOVE);
+ }
+ });
+
+ node.appendChild(span);
+ }
+
+ const embeds = document.getElementsByClassName('tiktok-embed');
+ for (let i = 0; i < embeds.length; i++) {
+ addEmbedButton(embeds[i]);
+ }
+
+ // Allow to work with auto-reload.js, etc.
+ document.addEventListener('new_post', function(e)) {
+ const post = e.detail;
+ const embeds = post.getElementsByClassName('tiktok-embed');
+ for (let i = 0; i < embeds.length; i++) {
+ addEmbedButton(embeds[i]);
+ }
+ }
+});
diff --git a/post.php b/post.php
index 5483600d..27a45413 100644
--- a/post.php
+++ b/post.php
@@ -1402,13 +1402,13 @@ function handle_post(Context $ctx)
$file['thumbwidth'] = $size[0];
$file['thumbheight'] = $size[1];
} elseif (
- (($config['strip_exif'] && isset($file['exif_stripped']) && $file['exif_stripped']) || !$config['strip_exif']) &&
+ $config['minimum_copy_resize'] &&
$image->size->width <= $config['thumb_width'] &&
$image->size->height <= $config['thumb_height'] &&
$file['extension'] == ($config['thumb_ext'] ? $config['thumb_ext'] : $file['extension'])
) {
// Copy, because there's nothing to resize
- copy($file['tmp_name'], $file['thumb']);
+ coopy($file['tmp_name'], $file['thumb']);
$file['thumbwidth'] = $image->size->width;
$file['thumbheight'] = $image->size->height;
@@ -1551,6 +1551,35 @@ function handle_post(Context $ctx)
}
}
+ if ($config['tesseract_ocr'] && $file['thumb'] != 'file') {
+ // Let's OCR it!
+ $fname = $file['tmp_name'];
+
+ if ($file['height'] > 500 || $file['width'] > 500) {
+ $fname = $file['thumb'];
+ }
+
+ if ($fname == 'spoiler') {
+ // We don't have that much CPU time, do we?
+ } else {
+ $tmpname = __DIR__ . "/tmp/tesseract/" . rand(0, 10000000);
+
+ // Preprocess command is an ImageMagick b/w quantization
+ $error = shell_exec_error(sprintf($config['tesseract_preprocess_command'], escapeshellarg($fname)) . " | " .
+ 'tesseract stdin ' . escapeshellarg($tmpname) . ' ' . $config['tesseract_params']);
+ $tmpname .= ".txt";
+
+ $value = @file_get_contents($tmpname);
+ @unlink($tmpname);
+
+ if ($value && trim($value)) {
+ // This one has an effect, that the body is appended to a post body. So you can write a correct
+ // spamfilter.
+ $post['body_nomarkup'] .= "" . htmlspecialchars($value) . "";
+ }
+ }
+ }
+
if (!isset($dont_copy_file) || !$dont_copy_file) {
if (isset($file['file_tmp'])) {
if (!@rename($file['tmp_name'], $file['file'])) {
@@ -1598,6 +1627,11 @@ function handle_post(Context $ctx)
}
}
+ // Do filters again if OCRing
+ if ($config['tesseract_ocr'] && !hasPermission($config['mod']['bypass_filters'], $board['uri']) && !$dropped_post) {
+ do_filters($ctx, $post);
+ }
+
if (!hasPermission($config['mod']['postunoriginal'], $board['uri']) && $config['robot_enable'] && checkRobot($post['body_nomarkup']) && !$dropped_post) {
undoImage($post);
if ($config['robot_mute']) {
diff --git a/static/banners/just-monika-opt.webp b/static/banners/just-monika-opt.webp
new file mode 100644
index 00000000..731f985a
Binary files /dev/null and b/static/banners/just-monika-opt.webp differ
diff --git a/stylesheets/style.css b/stylesheets/style.css
index 4a090f33..26b5cb75 100644
--- a/stylesheets/style.css
+++ b/stylesheets/style.css
@@ -380,7 +380,6 @@ form table tr td div.center {
.file {
float: left;
- min-width: 100px;
}
.file:not(.multifile) .post-image {
@@ -391,10 +390,6 @@ form table tr td div.center {
float: none;
}
-.file.multifile {
- margin: 0 10px 0 0;
-}
-
.file.multifile > p {
width: 0px;
min-width: 100%;
@@ -2086,3 +2081,24 @@ span.orangeQuote {
float: right;
margin: 0em 1em;
}
+
+/* Included embeds */
+
+.tiktok-embed {
+ float: left;
+ margin: 0.6em 1em 0.2em 0.2em;
+ border-radius: 8px;
+ overflow: hidden;
+ max-width: min-content;
+ min-width: 325px;
+}
+
+.tiktok-embed > iframe {
+ display: block;
+ visibility: unset;
+ border: none;
+ overflow: hidden;
+ width: 325px;
+ height: 739px;
+ max-height: 739px;
+}
diff --git a/templates/themes/categories/frames.html b/templates/themes/categories/frames.html
index 1c4673cc..6b2fac38 100644
--- a/templates/themes/categories/frames.html
+++ b/templates/themes/categories/frames.html
@@ -16,13 +16,13 @@
border-width: 2px;
margin-right: 15px;
}
-
+
.introduction {
grid-column: 2 / 9;
grid-row: 1;
width: 100%;
}
-
+
.content {
grid-column: 2 / 9;
grid-row: 2;
@@ -35,7 +35,7 @@
gap: 20px;
height: 100vh;
}
-
+
.modlog {
width: 50%;
text-align: left;
@@ -69,7 +69,7 @@
li a.system {
font-weight: bold;
}
-
+
@media (max-width:768px) {
body{
display: grid;
@@ -78,7 +78,7 @@
height: 100vh;
width: 100%;
}
-
+
.introduction {
grid-column: 1;
grid-row: 1;
@@ -96,18 +96,17 @@
grid-column: 1;
grid-row: 3;
width: 100%;
- word-break: break-all;
}
-
+
.modlog {
width: 100%;
text-align: center;
}
-
+
table {
table-layout: fixed;
}
-
+
table.modlog tr th {
white-space: normal;
word-wrap: break-word;
diff --git a/templates/themes/categories/news.html b/templates/themes/categories/news.html
index 76722e41..484c4eb0 100644
--- a/templates/themes/categories/news.html
+++ b/templates/themes/categories/news.html
@@ -11,7 +11,7 @@
.home-description {
margin: 20px auto 0 auto;
text-align: center;
- max-width: 700px;
+ max-width: 700px;"
}
{{ boardlist.top }}
diff --git a/tmp/tesseract/.gitkeep b/tmp/tesseract/.gitkeep
new file mode 100644
index 00000000..e69de29b