4chan/plugins/robot9000.php
2025-04-17 14:46:47 -05:00

250 lines
7.2 KiB
PHP

<?php
# Parameters:
# com: user supplied comment field (before or after wordfilters? dunno)
# md5: md5 of the supplied image. null if no image.
# ip: the IP of the user, in integer (packed) form
#
# Return value: A string. If the string is "OK", the post should go through.
# If the string is anything else, abort posting and display that message
# to the user.
#
# Integration info;
# This should run before wordfilters (including >>num) and duplicate(md5) detection.
# It doesn't know about bans, so those need to be done seperately, but it
# doesn't care if that is before or after.
# It really should run after valid file checks (jpg/png/gif, >0x0, etc) but that's not critical.
#
# Synchronization: There is (in theory) a minor race condition because the tables are not locked.
# It's not exploitable for any useful purpose, and it's blocked by the floodcheck
#
# Changelog:
# 2008/02/20 04:20: Added changelog, fixed $txt error that killed all posts
# 2008/02/20 04:56: Fixed the signal-ratio filter to handle the stupid HTML
# 2008/02/20 05:21: Added a check for repeated characters
# 2008/02/20 07:05: Added a check for long spams
# 2008/02/20 13:02: Rearranged the filters for better results.
# 2008/02/20 23:58: Fixed a bug that broke posts with two quotes far apart
# 2008/02/21 01:57: Fixed a dumb bug with the number filter..
# 2008/02/21 02:06: Adding content-percentage info to the content filter.
# 2008/02/21 02:15: Adjusted long-text filter.
# 2008/02/21 02:18: Removed long-text filter.
# 2008/02/22 16:43: Added mute-expiring.
# 2008/02/22 17:54: Fixed mute-expiring.
# 2008/02/22 18:13: Added #nextnow and #muteinfo secret mod capcodes
# 2008/02/22 18:21: Fixed #muteinfo for mods.
# 2015/10/24 16:36: Cleanup the code and put the robot back.
# $email, $sub, $name fields aren't used anymore.
# removed $mod parameter.
# 2020/11/16 08:09: Update text hashes for every post to prune stale entries
define('R9K_SIGNAL_RATIO', 0.1);
define('R9K_MAX_DURATION', 31536000); // one year
define('R9K_DATE_FORMAT', '%m/%d/%y %H:%M:%S');
define('R9K_DEMUTE_PERIOD', 86400); // one day
define('R9K_SNR_MIN_LEN', 10); // minimum txt length for signal ratio check
define('R9K_OK', 'OK');
define('R9K_DB_ERROR', 'Database error.');
define('R9K_EMPTY_COM', 'Textless posts are not allowed.');
define('R9K_ASCII_ONLY', 'Non-ASCII text is not allowed.');
define('R9K_MUTED', "You're muted! You cannot post until %s, %s from now");
define('R9K_MUTE_ERROR', "You have been muted for %s, because %s");
define('R9K_LOW_SNR', 'your comment was too low in content (%0.2f%% content).');
define('R9K_DUP_TXT', 'your comment was not original.');
define('R9K_DUP_IMG', 'your image was not original.');
function r9k_process($com, $md5, $ip) {
// Blank file
if ($md5 == 'd41d8cd98f00b204e9800998ecf8427e') {
$md5 = null;
}
if ($com === ''){
return R9K_EMPTY_COM;
}
if (preg_match('/[\\x80-\\xFF]/', $com)) {
return R9K_ASCII_ONLY;
}
$table_mutes = ROBOT9000_MUTES;
$table_posts = ROBOT9000_POSTS;
$ip = (int)$ip;
$mute = false;
$demute = false;
$timeout_power = 0;
$query = <<<SQL
SELECT timeout_power,
UNIX_TIMESTAMP(mute_until) as mute_until,
UNIX_TIMESTAMP(next_expire) as next_expire
FROM `$table_mutes` WHERE ip = $ip
SQL;
$res = mysql_board_call($query);
if (!$res) {
//return R9K_OK;
return R9K_DB_ERROR;
}
$row = mysql_fetch_assoc($res);
if ($row) {
$now = time();
$timeout_power = $row['timeout_power'];
if ($row['mute_until'] > $now) {
$duration = r9k_pretty_duration($row['mute_until'] - $now);
$when = strftime(R9K_DATE_FORMAT, $row['mute_until']);
return sprintf(R9K_MUTED, $when, $duration);
}
if ($row['next_expire'] < $now){
$demute = true;
}
}
$txt = strtolower($com);
// Strip HTML
$stxt=preg_replace('/<.*?>/s','', $txt);
// Original byte length
$olength = strlen($stxt);
// Strip >>123 quotelinks
$stxt = preg_replace('/&gt;&gt;\d+/', '', $stxt);
// Strip html entities
$stxt = preg_replace('/&#?\w+;/', '', $stxt);
// Strip non-alnum chars
$stxt = preg_replace('/[^a-z\d-]+/', '', $stxt);
// Trim leading and trailing numeric characters
$stxt = preg_replace('/^\d*(.*)\d*$/', '\1', $stxt);
// Compress repeated characters: aaa -> a
$stxt = preg_replace('/(.)\\1{2,}/', '\\1', $stxt);
// Check signal ratio
if (strlen($txt) > R9K_SNR_MIN_LEN) {
$ratio = strlen($stxt) / $olength;
if ($ratio < R9K_SIGNAL_RATIO) {
$mute = sprintf(R9K_LOW_SNR, $ratio * 100.0);
}
}
if ($mute === false) {
$txt_hash = md5($stxt);
// Check if hashes match
$query = "SELECT text, image FROM `$table_posts` WHERE text = '%s'";
/*
if ($md5) {
$query .= " OR image = '%s'";
$res = mysql_board_call($query, $txt_hash, $md5);
}
else {*/
$res = mysql_board_call($query, $txt_hash);
//}
if (!$res) {
//return R9K_OK;
return R9K_DB_ERROR;
}
// Post is good. Insert hashes.
if (mysql_num_rows($res) < 1) {
$query = "INSERT INTO `$table_posts` (text) VALUES('%s')";
mysql_board_call($query, $txt_hash);
}
// Duplicates found.
else {
//$row = mysql_fetch_assoc($res);
//if ($row['text'] === $txt_hash) {
$mute = R9K_DUP_TXT;
//}
//else if ($md5 && $row['image'] === $md5) {
// $mute = R9K_DUP_IMG;
//}
// Update the hash with a new timestamp
$query = "UPDATE `$table_posts` SET created_on = NOW() WHERE text = '%s' LIMIT 1";
mysql_board_call($query, $txt_hash);
}
}
// Muted
if ($mute !== false) {
++$timeout_power;
$mute_duration = pow(2, $timeout_power);
if ($mute_duration > R9K_MAX_DURATION) {
$timeout_power--;
$mute_duration = R9K_MAX_DURATION;
}
$next_expire = R9K_DEMUTE_PERIOD;
$query = <<<SQL
INSERT INTO `$table_mutes` (ip, timeout_power, mute_until, next_expire)
VALUES ($ip, $timeout_power, DATE_ADD(NOW(), INTERVAL $mute_duration SECOND),
DATE_ADD(NOW(), INTERVAL $mute_duration SECOND))
ON DUPLICATE KEY
UPDATE timeout_power = $timeout_power, mute_until = VALUES(mute_until),
next_expire = VALUES(next_expire)
SQL;
$res = mysql_board_call($query);
return sprintf(R9K_MUTE_ERROR, r9k_pretty_duration($mute_duration), $mute);
}
// Not muted
else {
if ($demute === true) {
$next_expire = R9K_DEMUTE_PERIOD;
$query = <<<SQL
UPDATE `$table_mutes` SET
timeout_power = IF(timeout_power > 0, timeout_power - 1, 0),
next_expire = DATE_ADD(NOW(), INTERVAL $next_expire SECOND)
WHERE ip = $ip
SQL;
$res = mysql_board_call($query);
}
return R9K_OK;
}
}
function r9k_pretty_duration($secs){
$w = (int)($secs / 604800);
$d = (int)($secs / 86400) % 7;
$h = (int)($secs / 3600) % 24;
$m = ((int)($secs / 60)) % 60;
$s = ((int)$secs) % 60;
$out = array();
$pairs = array(
array($w, 'week'),
array($d, 'day'),
array($h, 'hour'),
array($m, 'minute'),
array($s, 'second')
);
foreach($pairs as $v){
if ($v[0] !== 0) {
$out[] = $v[0] . ' ' . $v[1] . ($v[0] === 1 ? '' : 's');
}
}
return implode(' ', $out);
}