bot tacker
class BotTracker {
private $bot_map;
public function __construct() {
$this->bot_map = [
'Googlebot' => __('Googlebot', 'your-textdomain'),
'Bingbot' => __('Bingbot', 'your-textdomain'),
'Slurp' => __('Yahoo!', 'your-textdomain'),
'DuckDuckBot' => __('DuckDuckBot', 'your-textdomain'),
'Baiduspider' => __('Baiduspider', 'your-textdomain'),
'YandexBot' => __('YandexBot', 'your-textdomain'),
'Sogou' => __('Sogou', 'your-textdomain'),
'Exabot' => __('Exabot', 'your-textdomain'),
'ia_archiver' => __('Alexa/Archive', 'your-textdomain'),
'GPTBot' => __('ChatGPT', 'your-textdomain'),
'ChatGPT-User' => __('ChatGPT User', 'your-textdomain'),
'ClaudeBot' => __('Claude (Anthropic)', 'your-textdomain'),
'Google-Extended' => __('Google AI (Gemini)', 'your-textdomain'),
'CCBot' => __('Common Crawl', 'your-textdomain'),
'facebookexternalhit'=> __('Facebook', 'your-textdomain'),
'Twitterbot' => __('Twitter/X', 'your-textdomain'),
'LinkedInBot' => __('LinkedIn', 'your-textdomain'),
'Pinterestbot' => __('Pinterest', 'your-textdomain'),
'Slackbot' => __('Slack', 'your-textdomain'),
'TelegramBot' => __('Telegram', 'your-textdomain'),
'Applebot' => __('Applebot', 'your-textdomain'),
'Amazon' => __('Amazon.com', 'your-textdomain'),
];
// Only run the migration check once; skips instantly on future loads
if (!get_option('bot_tracker_migrated', false)) {
$this->rescue_and_migrate_stats();
}
add_action('init', [$this, 'track_bots']);
add_action('wp_dashboard_setup', [$this, 'register_dashboard_widget']);
add_action('wp_ajax_get_bot_stats', [$this, 'ajax_get_stats']);
add_action('wp_ajax_reset_bot_stats', [$this, 'ajax_reset_stats']);
}
private function rescue_and_migrate_stats() {
global $wpdb;
$existing_stats = get_option('bot_tracker_stats', []);
$old_options = $wpdb->get_results(
"SELECT option_name, option_value FROM {$wpdb->options} WHERE option_name LIKE 'last_visit\_%' OR option_name LIKE 'visit_count\_%'"
);
$migrated_stats = $existing_stats;
$legacy_keys_to_delete = [];
if (!empty($old_options)) {
foreach ($old_options as $opt) {
if (strpos($opt->option_name, 'last_visit_') === 0) {
$bot_name = str_replace('last_visit_', '', $opt->option_name);
if (!isset($migrated_stats[$bot_name])) {
$migrated_stats[$bot_name] = ['last_visit' => __('Never', 'your-textdomain'), 'count' => 0];
}
$migrated_stats[$bot_name]['last_visit'] = $opt->option_value;
$legacy_keys_to_delete[] = $opt->option_name;
} elseif (strpos($opt->option_name, 'visit_count_') === 0) {
$bot_name = str_replace('visit_count_', '', $opt->option_name);
if (!isset($migrated_stats[$bot_name])) {
$migrated_stats[$bot_name] = ['last_visit' => __('Never', 'your-textdomain'), 'count' => 0];
}
$migrated_stats[$bot_name]['count'] = (int) $opt->option_value;
$legacy_keys_to_delete[] = $opt->option_name;
}
}
}
// Save migrated stats (non-autoloaded) and mark migration as complete
update_option('bot_tracker_stats', $migrated_stats, false);
update_option('bot_tracker_migrated', true, true);
// Clean up legacy individual database rows
if (!empty($legacy_keys_to_delete)) {
foreach ($legacy_keys_to_delete as $old_key) {
delete_option($old_key);
}
}
}
public function get_tracked_bots_map() {
return $this->bot_map;
}
public function track_bots() {
$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
if (empty($user_agent)) return;
foreach ($this->bot_map as $key => $name) {
if (stripos($user_agent, $key) !== false) {
$stats = get_option('bot_tracker_stats', []);
if (!isset($stats[$name])) {
$stats[$name] = ['last_visit' => __('Never', 'your-textdomain'), 'count' => 0];
}
$stats[$name]['last_visit'] = current_time('M j, Y - g:i a');
$stats[$name]['count']++;
update_option('bot_tracker_stats', $stats, false);
break;
}
}
}
public function ajax_get_stats() {
if (!current_user_can('manage_options')) {
wp_die(__('Unauthorized', 'your-textdomain'));
}
$stats = get_option('bot_tracker_stats', []);
ob_start();
?>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th><?php _e('Bot', 'your-textdomain'); ?></th>
<th><?php _e('Last Visit', 'your-textdomain'); ?></th>
<th><?php _e('Hits', 'your-textdomain'); ?></th>
</tr>
</thead>
<tbody>
<?php
foreach ($this->bot_map as $key => $name) :
$time = $stats[$name]['last_visit'] ?? __('Never', 'your-textdomain');
$count = $stats[$name]['count'] ?? 0;
?>
<tr>
<td><strong><?php echo esc_html($name); ?></strong></td>
<td><?php echo esc_html($time); ?></td>
<td><span class="badge" style="background:#2271b1; color:#fff; padding:2px 8px; border-radius:10px;"><?php echo esc_html($count); ?></span></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php
echo ob_get_clean();
wp_die();
}
public function ajax_reset_stats() {
check_ajax_referer('reset_bot_stats_nonce');
if (!current_user_can('manage_options')) wp_die(__('Unauthorized', 'your-textdomain'));
delete_option('bot_tracker_stats');
delete_option('bot_tracker_migrated'); // Reset migration flag if they reset stats
wp_send_json_success();
}
public function register_dashboard_widget() {
if (current_user_can('manage_options')) {
wp_add_dashboard_widget(
'bot_indexer_tracker_widget',
__('🚀 Live Search & AI Tracker', 'your-textdomain'),
[$this, 'render_widget']
);
}
}
public function render_widget() {
?>
<div id="bot-tracker-container" style="max-height: 400px; overflow-y: auto;">
<p><?php _e('Connecting to tracker...', 'your-textdomain'); ?></p>
</div>
<hr />
<button id="reset-bot-stats" class="button button-link-delete" style="color: #d63638;">
<?php _e('Reset All Bot Data', 'your-textdomain'); ?>
</button>
<script type="text/javascript">
(function($) {
function updateBotStats() {
$.post(ajaxurl, { action: 'get_bot_stats' }, function(response) {
$('#bot-tracker-container').html(response);
});
}
updateBotStats();
setInterval(updateBotStats, 60000);
$('#reset-bot-stats').on('click', function(e) {
e.preventDefault();
if (confirm('<?php echo esc_js(__('Clear all timestamps and counters?', 'your-textdomain')); ?>')) {
$.post(ajaxurl, {
action: 'reset_bot_stats',
_ajax_nonce: '<?php echo wp_create_nonce("reset_bot_stats_nonce"); ?>'
}, function() {
updateBotStats();
});
}
});
})(jQuery);
</script>
<?php
}
}
new BotTracker();