init 4chan
This commit is contained in:
commit
c850337f1e
390 changed files with 195936 additions and 0 deletions
161
plugins/broomcloset.php
Normal file
161
plugins/broomcloset.php
Normal file
|
@ -0,0 +1,161 @@
|
|||
<?php
|
||||
// broom closet - yotsuba plugin
|
||||
// implementation of janitor discussion system.
|
||||
|
||||
/* Features:
|
||||
* - "latest" mode - display last post number, etc, so that it can be polled by a script.
|
||||
* - force posting with logged-in moderator/janitor name.
|
||||
* - never expire posts.
|
||||
* - allow no-file posts.
|
||||
* - emit PHP instead of html in order to do admin-validation checking.
|
||||
* - give everyone a capcode, and give janitors a tooltip saying which board they're in charge of.
|
||||
*/
|
||||
|
||||
// Config enforcement... (too late to change it now)
|
||||
if( NO_TEXTONLY == 1 ) die( 'Config NO_TEXTONLY should be turned off!' );
|
||||
if( PHP_EXT == '.html' ) die( 'Config PHP_EXT should end in .php!' );
|
||||
if( PAGE_MAX > 0 ) die( 'Config PAGE_MAX should be 0!' );
|
||||
|
||||
|
||||
/* register_callback('mode_default_case', 'broomcloset_mode');
|
||||
register_callback('regist_before', 'broomcloset_regist');
|
||||
register_callback('trim_db_before', 'broomcloset_trim');
|
||||
register_callback('head_before', 'broomcloset_head');
|
||||
register_callback('form_after', 'broomcloset_form');
|
||||
register_callback('post_before', 'broomcloset_post');
|
||||
register_callback('capcode', 'broomcloset_capcode');
|
||||
*/
|
||||
|
||||
// add the 'latest' mode
|
||||
function broomcloset_latest()
|
||||
{
|
||||
//if (!valid('janitor_board')) die('');
|
||||
$query = mysql_board_call( "SELECT * FROM `" . SQLLOG . "` ORDER BY no DESC LIMIT 1" );
|
||||
if( $row = mysql_fetch_assoc( $query ) ) {
|
||||
foreach( $row as &$val ) $val = addslashes( $val );
|
||||
echo <<<EOJSON
|
||||
{"no":{$row['no']}}
|
||||
EOJSON;
|
||||
}
|
||||
die( '' );
|
||||
}
|
||||
|
||||
function refresh_mod_cache()
|
||||
{
|
||||
global $mod_cache;
|
||||
|
||||
if( !isset( $mod_cache ) ) {
|
||||
$admin_salt = file_get_contents('/www/keys/2014_admin.salt');
|
||||
|
||||
if (!$admin_salt) {
|
||||
die('Internal Server Error (rmc0)');
|
||||
}
|
||||
|
||||
$query = mysql_global_call( "SELECT id,username,allow,level from mod_users" );
|
||||
$mod_cache = array();
|
||||
while( list( $id, $username, $allow, $level ) = mysql_fetch_row( $query ) ) {
|
||||
if( $allow ) {
|
||||
$hashed_bits = hash_hmac('sha256', $username, $admin_salt, true);
|
||||
|
||||
$username = base64_encode($hashed_bits);
|
||||
|
||||
$mod_cache[$username] = array();
|
||||
|
||||
$board = '';
|
||||
|
||||
if( $level == 'janitor' ) {
|
||||
$level = 'Janitor';
|
||||
$color = '#4169E1';
|
||||
$board = str_replace( ',janitor', '', $allow );
|
||||
} elseif( $id == 2 ) {
|
||||
$level = 'Admin';
|
||||
$color = '#FF0000';
|
||||
} elseif( $level == 'manager' ) { // disabled until mootapproval
|
||||
$level = 'Manager';
|
||||
$color = '#FF0080';
|
||||
} else {
|
||||
$level = 'Mod';
|
||||
$color = '#800080';
|
||||
}
|
||||
|
||||
$mod_cache[$username]['level'] = $level;
|
||||
$mod_cache[$username]['color'] = $color;
|
||||
$mod_cache[$username]['id'] = $id;
|
||||
|
||||
if( $board )
|
||||
$mod_cache[$username]['board'] = $board;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function broomcloset_name( $name )
|
||||
{
|
||||
global $mod_cache;
|
||||
refresh_mod_cache();
|
||||
|
||||
if( !isset( $mod_cache[$name] ) ) { // user not found
|
||||
return 'Anonymous';
|
||||
}
|
||||
|
||||
return 'Anonymous ## ' . $mod_cache[$name]['level'];
|
||||
}
|
||||
|
||||
|
||||
function broomcloset_style( $name )
|
||||
{
|
||||
global $mod_cache;
|
||||
refresh_mod_cache();
|
||||
|
||||
if( !isset( $mod_cache[$name] ) ) { // user not found
|
||||
return ' style="color:#aaa"';
|
||||
}
|
||||
|
||||
if( $mod_cache[$name]['board'] ) {
|
||||
$tooltip = " style='color: {$mod_cache[$name]['color']}'";
|
||||
} else {
|
||||
$tooltip = " style='color: {$mod_cache[$name]['color']}'";
|
||||
}
|
||||
|
||||
return $tooltip;
|
||||
}
|
||||
|
||||
// auto-set name
|
||||
function broomcloset_new_post( $caller )
|
||||
{
|
||||
// set textonly to 1 - this is ok even if they're posting a picture
|
||||
// now imgboard won't complain about no picture EVER
|
||||
$caller['textonly'] = 1;
|
||||
|
||||
$caller['name'] = $_COOKIE['4chan_auser'];
|
||||
if( !has_level( 'janitor' ) ) die;
|
||||
}
|
||||
|
||||
function broomcloset_form( $dat )
|
||||
{ // modify the form to hide name, email, and textonly
|
||||
$newform = str_replace( '<tr><td></td><td class="postblock" align="left"><b>Name</b></td><td><input type=text name=name size="28"><span id="tdname"></span></td></tr>', '<input type=hidden name=name>', $dat );
|
||||
$newform = str_replace( '<tr><td></td><td class="postblock" align="left"><b>E-mail</b></td><td><input type=text name=email size="28"><span id="tdemail"></span></td></tr>', '<input type=hidden name=email>', $newform );
|
||||
$newform = str_replace( '[<label><input type=checkbox name=textonly value=on>No File</label>]', '', $newform );
|
||||
$newform = str_replace( 'name=sub size="35">', 'name=sub size="35"><span id="tdname"></span><span id="tdemail"></span>', $newform ); // move admin ext. placeholders next to subject
|
||||
return $newform;
|
||||
}
|
||||
|
||||
// this function is last because it screws up syntax coloring in my editor :(
|
||||
function broomcloset_head( $dat )
|
||||
{
|
||||
$dat .= <<<'BUTTCODE'
|
||||
<?php if( !isset( $_COOKIE['4chan_auser'] ) || !isset( $_COOKIE['apass'] ) ) { http_response_code(403); die(); }
|
||||
|
||||
require_once 'lib/admin.php';
|
||||
require_once 'lib/auth.php';
|
||||
|
||||
header('Content-Security-Policy: connect-src *.4chan.org *.4cdn.org');
|
||||
header('X-Content-Security-Policy: connect-src *.4chan.org *.4cdn.org');
|
||||
|
||||
auth_user();
|
||||
|
||||
if( !has_level('janitor') ) { http_response_code(403); die(); } ?>
|
||||
BUTTCODE;
|
||||
|
||||
return $dat;
|
||||
}
|
42
plugins/enhance_q.php
Normal file
42
plugins/enhance_q.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
/**
|
||||
* Enhance meta - Yotsuba plugin
|
||||
* Enhances meta with various fabulous bits and bobs.
|
||||
*/
|
||||
|
||||
function meta_is_thread_flagged( $resline )
|
||||
{
|
||||
global $log;
|
||||
|
||||
$rep = 0;
|
||||
$posts = array('admin' => '', 'developer' => '', 'mod' => '', 'manager' => '');
|
||||
|
||||
while( list( $resrow ) = each( $resline ) ) {
|
||||
|
||||
if( !$log[ $resrow ][ 'no' ] ) {
|
||||
break;
|
||||
}
|
||||
|
||||
if( $log[ $resrow ][ 'capcode' ] === 'none' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$capcode = ( $log[ $resrow ][ 'capcode' ] == 'admin_highlight' ) ? 'admin' : $log[$resrow]['capcode'];
|
||||
$no = $log[$resrow]['no'];
|
||||
|
||||
$posts[$capcode] .= "$no,";
|
||||
|
||||
}
|
||||
|
||||
unset( $posts['none'] );
|
||||
|
||||
foreach( $posts as $key => $value ) {
|
||||
if( $posts[$key] != '' ) {
|
||||
$posts[$key] = substr($posts[$key], 0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return array( $posts['admin'], $posts['developer'], $posts['mod'], $posts['manager'] );
|
||||
}
|
||||
?>
|
250
plugins/robot9000.php
Normal file
250
plugins/robot9000.php
Normal file
|
@ -0,0 +1,250 @@
|
|||
<?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('/>>\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);
|
||||
}
|
28
plugins/yotsuba_plugins.php
Normal file
28
plugins/yotsuba_plugins.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?
|
||||
|
||||
// yotsuba plugin API
|
||||
|
||||
// add a function $function to the list of callbacks named by $hook.
|
||||
function register_callback($hook, $function) {
|
||||
global $HOOKS;
|
||||
if( !isset($HOOKS[$hook]) )
|
||||
$HOOKS[$hook] = array($function);
|
||||
else
|
||||
array_push($HOOKS[$hook], $function);
|
||||
}
|
||||
|
||||
// run all the callback functions associated with the name $hook.
|
||||
// they will be passed $args.
|
||||
function run_callback($hook, $args) {
|
||||
global $HOOKS;
|
||||
if(isset($HOOKS[$hook]))
|
||||
foreach($HOOKS[$hook] as $function)
|
||||
if(function_exists($function))
|
||||
$function($args);
|
||||
}
|
||||
|
||||
// load plugins
|
||||
if(defined('PLUGIN_DIR'))
|
||||
foreach(explode(',', PLUGINS) as $plugin)
|
||||
include_once PLUGIN_DIR . trim($plugin) . '.php';
|
||||
?>
|
Loading…
Add table
Add a link
Reference in a new issue