Skip to content

Commit fcdd95e

Browse files
committed
initial release
0 parents  commit fcdd95e

File tree

6 files changed

+463
-0
lines changed

6 files changed

+463
-0
lines changed

.distignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/.wordpress-org
2+
/.git
3+
/.github
4+
/node_modules
5+
6+
.distignore
7+
.gitignore
8+
.gitattributes

.gitattributes

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Directories
2+
/.wordpress-org export-ignore
3+
/.github export-ignore
4+
5+
# Files
6+
/.gitattributes export-ignore
7+
/.gitignore export-ignore

.github/workflows/deploy.yml

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
name: GitHub Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "*"
7+
8+
jobs:
9+
release:
10+
name: Build & Release
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout repository
15+
uses: actions/checkout@v3
16+
17+
- name: Debug File List
18+
run: ls -R
19+
20+
- name: Find Readme File
21+
id: find_readme
22+
run: |
23+
for file in readme.txt Readme.txt README.txt README.md Readme.md readme.md; do
24+
if [ -f "$file" ]; then
25+
echo "Readme file found: $file"
26+
echo "readme_file=$file" >> $GITHUB_ENV
27+
break
28+
fi
29+
done
30+
31+
source $GITHUB_ENV
32+
33+
if [ -z "$readme_file" ]; then
34+
echo "::error::Readme file not found."
35+
exit 1
36+
fi
37+
38+
- name: Extract Release Notes
39+
id: release_notes
40+
run: |
41+
changelog_section_start="== Changelog =="
42+
readme_file="$readme_file"
43+
44+
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
45+
plugin_version="${GITHUB_REF#refs/tags/}"
46+
echo "Detected tag version: $plugin_version"
47+
else
48+
echo "::error::Workflow must be triggered by a tag push."
49+
exit 1
50+
fi
51+
52+
in_changelog=0
53+
found_version=0
54+
release_notes=""
55+
56+
while IFS= read -r line; do
57+
58+
if [[ "$line" == "$changelog_section_start" ]]; then
59+
in_changelog=1
60+
continue
61+
fi
62+
63+
if [[ $in_changelog -eq 0 ]]; then
64+
continue
65+
fi
66+
67+
if [[ "$line" == "= ${plugin_version} =" ]]; then
68+
found_version=1
69+
continue
70+
fi
71+
72+
if [[ $found_version -eq 1 ]] && echo "$line" | grep -qE '^= [0-9]+\.[0-9]+\.[0-9]+ =$'; then
73+
break
74+
fi
75+
76+
if [[ $found_version -eq 1 ]] && echo "$line" | grep -qE '^\*'; then
77+
release_notes+="${line}\n"
78+
continue
79+
fi
80+
81+
done < "$readme_file"
82+
83+
if [[ -z "$release_notes" ]]; then
84+
echo "::error::Failed to extract release notes for version ${plugin_version}."
85+
exit 1
86+
fi
87+
88+
echo "RELEASE_NOTES<<EOF" >> $GITHUB_ENV
89+
echo -e "$release_notes" >> $GITHUB_ENV
90+
echo "EOF" >> $GITHUB_ENV
91+
92+
- name: Create ZIP Archive
93+
run: |
94+
repo_name="${{ github.event.repository.name }}"
95+
zip_name="${repo_name}.zip"
96+
97+
zip -r "$zip_name" . \
98+
-x "*.git*" \
99+
".github/*"
100+
101+
echo "ZIP_FILE=$zip_name" >> $GITHUB_ENV
102+
103+
- name: Create GitHub Release
104+
uses: softprops/action-gh-release@v2
105+
with:
106+
tag_name: ${{ github.ref_name }}
107+
body: ${{ env.RELEASE_NOTES }}
108+
files: ${{ env.ZIP_FILE }}
109+
env:
110+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.gitignore

Whitespace-only changes.

cbxaibottracker.php

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
<?php
2+
/**
3+
* Plugin Name: CBX AI Bot Tracker
4+
* Description: Tracks AI bot visits (OpenAI, Anthropic, Perplexity, etc.) and shows visit logs in admin dashboard.
5+
* Version: 1.0.0
6+
* Author: Codeboxr
7+
* Text Domain: cbxaibottracker
8+
* Domain Path: /languages
9+
*/
10+
11+
if ( ! defined( 'ABSPATH' ) ) exit;
12+
13+
class CBXAIBotTracker {
14+
15+
private $table_name;
16+
17+
private $known_ai_bots = [
18+
'GPTBot' => 'OpenAI',
19+
'ChatGPT-User' => 'OpenAI',
20+
'ClaudeBot' => 'Anthropic',
21+
'Claude-Web' => 'Anthropic',
22+
'PerplexityBot' => 'Perplexity',
23+
'Google-Extended' => 'Google AI',
24+
'CCBot' => 'Common Crawl',
25+
'Amazonbot' => 'Amazon AI'
26+
];
27+
28+
public function __construct() {
29+
global $wpdb;
30+
$this->table_name = $wpdb->prefix . 'cbxaibottracker_visits';
31+
32+
register_activation_hook(__FILE__, [$this, 'create_table']);
33+
34+
add_action('plugins_loaded', [$this, 'load_textdomain']);
35+
add_action('init', [$this, 'track_bot_visit']);
36+
add_action('admin_menu', [$this, 'register_admin_menu']);
37+
add_action('admin_init', [$this, 'handle_delete']);
38+
}
39+
40+
/* ----------------------------
41+
* Load Translation
42+
* ---------------------------- */
43+
public function load_textdomain() {
44+
load_plugin_textdomain(
45+
'cbxaibottracker',
46+
false,
47+
dirname(plugin_basename(__FILE__)) . '/languages'
48+
);
49+
}
50+
51+
/* ----------------------------
52+
* Create DB Table
53+
* ---------------------------- */
54+
public function create_table() {
55+
global $wpdb;
56+
$charset_collate = $wpdb->get_charset_collate();
57+
58+
$sql = "CREATE TABLE {$this->table_name} (
59+
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
60+
bot_name VARCHAR(150) NOT NULL,
61+
bot_source VARCHAR(150) NOT NULL,
62+
request_url TEXT NOT NULL,
63+
visit_count BIGINT(20) UNSIGNED DEFAULT 1,
64+
last_visit DATETIME DEFAULT CURRENT_TIMESTAMP,
65+
PRIMARY KEY (id),
66+
UNIQUE KEY bot_url (bot_name, request_url(191))
67+
) $charset_collate;";
68+
69+
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
70+
dbDelta($sql);
71+
}
72+
73+
/* ----------------------------
74+
* Track AI Bot Visit
75+
* ---------------------------- */
76+
public function track_bot_visit() {
77+
78+
if ( is_admin() ) return;
79+
80+
if ( empty($_SERVER['HTTP_USER_AGENT']) ) return;
81+
82+
$user_agent = $_SERVER['HTTP_USER_AGENT'];
83+
$bot_detected = false;
84+
$bot_name = '';
85+
$bot_source = '';
86+
87+
foreach ( $this->known_ai_bots as $signature => $source ) {
88+
if ( stripos($user_agent, $signature) !== false ) {
89+
$bot_detected = true;
90+
$bot_name = $signature;
91+
$bot_source = $source;
92+
break;
93+
}
94+
}
95+
96+
if ( ! $bot_detected ) return;
97+
98+
$request_url = esc_url_raw(home_url($_SERVER['REQUEST_URI']));
99+
100+
global $wpdb;
101+
102+
$existing = $wpdb->get_row(
103+
$wpdb->prepare(
104+
"SELECT * FROM {$this->table_name} WHERE bot_name = %s AND request_url = %s",
105+
$bot_name,
106+
$request_url
107+
)
108+
);
109+
110+
if ( $existing ) {
111+
$wpdb->update(
112+
$this->table_name,
113+
[
114+
'visit_count' => $existing->visit_count + 1,
115+
'last_visit' => current_time('mysql')
116+
],
117+
['id' => $existing->id]
118+
);
119+
} else {
120+
$wpdb->insert(
121+
$this->table_name,
122+
[
123+
'bot_name' => $bot_name,
124+
'bot_source' => $bot_source,
125+
'request_url' => $request_url,
126+
'visit_count' => 1,
127+
'last_visit' => current_time('mysql')
128+
]
129+
);
130+
}
131+
}
132+
133+
/* ----------------------------
134+
* Admin Menu
135+
* ---------------------------- */
136+
public function register_admin_menu() {
137+
add_menu_page(
138+
__('AI Bot Tracker', 'cbxaibottracker'),
139+
__('AI Bot Tracker', 'cbxaibottracker'),
140+
'manage_options',
141+
'cbxaibottracker',
142+
[$this, 'admin_page'],
143+
'dashicons-visibility',
144+
27
145+
);
146+
}
147+
148+
/* ----------------------------
149+
* Handle Delete
150+
* ---------------------------- */
151+
public function handle_delete() {
152+
153+
if ( ! isset($_GET['cbx_delete']) ) return;
154+
if ( ! current_user_can('manage_options') ) return;
155+
156+
$id = intval($_GET['cbx_delete']);
157+
check_admin_referer('cbx_delete_bot_' . $id);
158+
159+
global $wpdb;
160+
$wpdb->delete($this->table_name, ['id' => $id]);
161+
162+
wp_redirect(admin_url('admin.php?page=cbxaibottracker&deleted=1'));
163+
exit;
164+
}
165+
166+
/* ----------------------------
167+
* Admin Page
168+
* ---------------------------- */
169+
public function admin_page() {
170+
171+
global $wpdb;
172+
173+
$results = $wpdb->get_results(
174+
"SELECT * FROM {$this->table_name} ORDER BY last_visit DESC LIMIT 200"
175+
);
176+
177+
echo '<div class="wrap">';
178+
echo '<h1>' . esc_html__('AI Bot Visits', 'cbxaibottracker') . '</h1>';
179+
180+
if ( isset($_GET['deleted']) ) {
181+
echo '<div class="notice notice-success is-dismissible">';
182+
echo '<p>' . esc_html__('Entry deleted successfully.', 'cbxaibottracker') . '</p>';
183+
echo '</div>';
184+
}
185+
186+
echo '<table class="widefat striped">';
187+
echo '<thead><tr>';
188+
echo '<th>' . esc_html__('Bot Name', 'cbxaibottracker') . '</th>';
189+
echo '<th>' . esc_html__('Source', 'cbxaibottracker') . '</th>';
190+
echo '<th>' . esc_html__('URL', 'cbxaibottracker') . '</th>';
191+
echo '<th>' . esc_html__('Visits', 'cbxaibottracker') . '</th>';
192+
echo '<th>' . esc_html__('Last Visit', 'cbxaibottracker') . '</th>';
193+
echo '<th>' . esc_html__('Action', 'cbxaibottracker') . '</th>';
194+
echo '</tr></thead>';
195+
echo '<tbody>';
196+
197+
if ( $results ) {
198+
foreach ( $results as $row ) {
199+
200+
$delete_url = wp_nonce_url(
201+
admin_url('admin.php?page=cbxaibottracker&cbx_delete=' . $row->id),
202+
'cbx_delete_bot_' . $row->id
203+
);
204+
205+
echo '<tr>';
206+
echo '<td>' . esc_html($row->bot_name) . '</td>';
207+
echo '<td>' . esc_html($row->bot_source) . '</td>';
208+
echo '<td><a href="' . esc_url($row->request_url) . '" target="_blank">' . esc_html($row->request_url) . '</a></td>';
209+
echo '<td>' . esc_html($row->visit_count) . '</td>';
210+
echo '<td>' . esc_html($row->last_visit) . '</td>';
211+
echo '<td><a href="' . esc_url($delete_url) . '" class="button button-small">' . esc_html__('Delete', 'cbxaibottracker') . '</a></td>';
212+
echo '</tr>';
213+
}
214+
} else {
215+
echo '<tr><td colspan="6">' . esc_html__('No AI bot visits recorded yet.', 'cbxaibottracker') . '</td></tr>';
216+
}
217+
218+
echo '</tbody></table>';
219+
echo '</div>';
220+
}
221+
}
222+
223+
new CBXAIBotTracker();

0 commit comments

Comments
 (0)