';
}).join('');
}
diff --git a/api/referrals.php b/api/referrals.php
index b335c9b..b0144c1 100644
--- a/api/referrals.php
+++ b/api/referrals.php
@@ -127,21 +127,128 @@ if ($method === 'GET') {
echo json_encode(['success'=>false,'error'=>'Unknown action']); exit;
}
+// ββ Helpers βββββββββββββββββββββββββββββββββββββββββββββββ
+
+function inferPlatformFromUrl(string $url): string {
+ $host = strtolower(parse_url($url, PHP_URL_HOST) ?? '');
+ if (str_contains($host, 'reddit.com')) return 'reddit';
+ if (str_contains($host, 'twitter.com') || str_contains($host, 'x.com')) return 'twitter';
+ if (str_contains($host, 'facebook.com')) return 'facebook';
+ if (str_contains($host, 'instagram.com')) return 'instagram';
+ if (str_contains($host, 'tiktok.com')) return 'tiktok';
+ if (str_contains($host, 'youtube.com')) return 'youtube';
+ if (str_contains($host, 'discord.com')) return 'discord';
+ if (str_contains($host, 'twitch.tv')) return 'twitch';
+ return 'other';
+}
+
+function scrapeForReferralCode(string $url, string $code): array {
+ // SSRF guard β only http/https, no private/reserved IPs
+ $parsed = parse_url($url);
+ if (!$parsed || !in_array(strtolower($parsed['scheme'] ?? ''), ['http','https'])) {
+ return ['verified'=>false,'reason'=>'invalid_url'];
+ }
+ $host = $parsed['host'] ?? '';
+ $ip = gethostbyname($host);
+ if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
+ return ['verified'=>false,'reason'=>'invalid_url'];
+ }
+
+ $ch = curl_init();
+ curl_setopt_array($ch, [
+ CURLOPT_URL => $url,
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_TIMEOUT => 8,
+ CURLOPT_FOLLOWLOCATION => true,
+ CURLOPT_MAXREDIRS => 3,
+ CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
+ CURLOPT_SSL_VERIFYPEER => false,
+ CURLOPT_HTTPHEADER => ['Accept: text/html,application/xhtml+xml,*/*;q=0.8'],
+ CURLOPT_BUFFERSIZE => 131072, // read up to 128KB
+ ]);
+ $html = curl_exec($ch);
+ $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ $curlErr = curl_error($ch);
+ curl_close($ch);
+
+ if ($curlErr || !$html) return ['verified'=>false,'reason'=>'unreachable'];
+ if ($httpCode === 401 || $httpCode === 403) return ['verified'=>false,'reason'=>'login_required'];
+ if ($httpCode >= 400) return ['verified'=>false,'reason'=>'unreachable'];
+
+ // Truncate to 512KB to avoid scanning huge pages
+ $content = substr($html, 0, 524288);
+
+ if (stripos($content, $code) !== false) return ['verified'=>true, 'reason'=>'code_found'];
+ if (stripos($content, 'tomtomgames.com') !== false) return ['verified'=>false,'reason'=>'site_found_no_code'];
+ return ['verified'=>false,'reason'=>'not_found'];
+}
+
// ββ POST actions ββββββββββββββββββββββββββββββββββββββββββ
if ($method !== 'POST') { echo json_encode(['success'=>false,'error'=>'Method not allowed']); exit; }
$d = json_decode(file_get_contents('php://input'), true);
if ($action === 'submit_share') {
+ $url = trim($d['url'] ?? '');
$platform = preg_replace('/[^a-z0-9_]/', '', strtolower(trim($d['platform'] ?? '')));
- if (!$platform) { echo json_encode(['success'=>false,'error'=>'Platform required']); exit; }
- // Get bonus tokens for this platform from tiers config
- $bonus = 5; // default
+
+ if (!$url) { echo json_encode(['success'=>false,'error'=>'Please paste the URL of your post']); exit; }
+ if (!filter_var($url, FILTER_VALIDATE_URL)) { echo json_encode(['success'=>false,'error'=>'That doesn\'t look like a valid URL']); exit; }
+
+ if (!$platform) $platform = inferPlatformFromUrl($url);
+
+ // Get referrer's code for scraping
+ $codeRow = db()->prepare("SELECT referral_code FROM users WHERE id=?");
+ $codeRow->execute([$userId]);
+ $referralCode = (string)$codeRow->fetchColumn();
+
+ // Reject duplicate URLs from same user
+ $dup = db()->prepare("SELECT id FROM referral_social_shares WHERE user_id=? AND share_url=?");
+ $dup->execute([$userId, $url]);
+ if ($dup->fetchColumn()) { echo json_encode(['success'=>false,'error'=>'You already submitted this URL']); exit; }
+
+ $bonus = 5;
+ $scrape = scrapeForReferralCode($url, $referralCode);
+ $status = $scrape['verified'] ? 'approved' : 'pending';
+
+ $reasonMessages = [
+ 'code_found' => null, // auto-verified, no extra message needed
+ 'login_required' => 'This post requires a login to view β our team will review it manually.',
+ 'unreachable' => "We couldn't reach that URL β our team will review it manually.",
+ 'not_found' => "Your referral code wasn't found on that page β our team will review it manually.",
+ 'site_found_no_code'=> 'TomTomGames was mentioned but your referral code wasn\'t found β our team will review it manually.',
+ 'invalid_url' => 'That URL doesn\'t look valid β please check and try again.',
+ ];
+
+ if ($scrape['reason'] === 'invalid_url') {
+ echo json_encode(['success'=>false,'error'=>$reasonMessages['invalid_url']]);
+ exit;
+ }
+
try {
- db()->prepare("INSERT INTO referral_social_shares (user_id,platform,bonus_tokens) VALUES (?,?,?)")
- ->execute([$userId, $platform, $bonus]);
- echo json_encode(['success'=>true]);
+ db()->beginTransaction();
+ db()->prepare("INSERT INTO referral_social_shares (user_id,platform,bonus_tokens,share_url,auto_verified,verify_result,status) VALUES (?,?,?,?,?,?,?)")
+ ->execute([$userId, $platform, $bonus, $url, $scrape['verified']?1:0, $scrape['reason'], $status]);
+ $shareId = (int)db()->lastInsertId();
+
+ if ($scrape['verified']) {
+ db()->prepare("UPDATE users SET tokens=tokens+? WHERE id=?")->execute([$bonus, $userId]);
+ logAdminAction('SOCIAL_SHARE_AUTO_VERIFIED', (int)$_SESSION['user_id'], 'referral_share', $shareId,
+ 'Auto-verified share on '.$platform.' β awarded '.$bonus.' tokens. URL: '.$url, '', 'approved', 'info');
+ }
+ db()->commit();
+
+ $resp = ['success'=>true,'auto_verified'=>$scrape['verified'],'platform'=>$platform];
+ if ($scrape['verified']) {
+ $resp['tokens'] = $bonus;
+ $resp['message'] = 'π Verified! +'.$bonus.' tokens awarded automatically.';
+ } else {
+ $resp['pending'] = true;
+ $resp['message'] = $reasonMessages[$scrape['reason']] ?? 'Submitted for manual review.';
+ }
+ echo json_encode($resp);
} catch(Exception $e) {
- echo json_encode(['success'=>false,'error'=>'Already submitted for this platform']);
+ db()->rollBack();
+ echo json_encode(['success'=>false,'error'=>'Failed to submit share']);
}
exit;
}
diff --git a/db/schema.sql b/db/schema.sql
index 846cc74..9a5c8cc 100644
--- a/db/schema.sql
+++ b/db/schema.sql
@@ -311,6 +311,9 @@ CREATE TABLE `referral_social_shares` (
`user_id` int(11) NOT NULL,
`platform` varchar(50) NOT NULL,
`bonus_tokens` decimal(10,2) DEFAULT 0.00,
+ `share_url` varchar(500) DEFAULT NULL,
+ `auto_verified` tinyint(1) DEFAULT 0,
+ `verify_result` varchar(100) DEFAULT NULL,
`status` enum('pending','approved','denied') DEFAULT 'pending',
`admin_id` int(11) DEFAULT NULL,
`created_at` datetime DEFAULT current_timestamp(),
@@ -318,7 +321,7 @@ CREATE TABLE `referral_social_shares` (
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
CONSTRAINT `referral_social_shares_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
-) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `referral_tiers`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
diff --git a/index.php b/index.php
index 3d222b7..11eaab6 100644
--- a/index.php
+++ b/index.php
@@ -969,14 +969,13 @@ body::before{content:'';position:fixed;inset:0;background-image:linear-gradient(
-
Social Share Bonus
-
Share TomTomGames on social media and earn bonus tokens when approved by our team.
-
-
-
-
-
+
Share & Earn Bonus Tokens
+
Share your referral link anywhere β Reddit, Twitter/X, a forum, your blog. Paste the link to your post below and we'll try to verify it automatically.
+
+
+
+
Public posts (Reddit, forums, blogs) verify instantly. Facebook/Instagram/TikTok need manual review.
@@ -2277,9 +2276,23 @@ async function loadReferralDashboard() {
// Social shares
const sharesList = document.getElementById('ref-shares-list');
- if (sharesList && (d.social_shares||[]).length) {
- sharesList.innerHTML = '