Last active
October 21, 2025 23:17
-
-
Save thecompez/8a8a6835833315140e5bb722eab30772 to your computer and use it in GitHub Desktop.
send-notification
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| // Set up error logging. | |
| ini_set('log_errors', 1); | |
| ini_set('error_log', 'logs/error_log_file.log'); | |
| // Disable caching. | |
| header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); | |
| header('Pragma: no-cache'); | |
| header('Expires: 0'); | |
| header('Content-Type: application/json'); | |
| /** | |
| * Interface NotificationStrategy | |
| * | |
| * Defines the structure for notification messages. | |
| */ | |
| interface NotificationStrategy { | |
| public function getTitle(): string; | |
| public function getBody(): string; | |
| public function getTargetUrl(): string; | |
| } | |
| /** | |
| * Welcome Notification | |
| */ | |
| class WelcomeNotification implements NotificationStrategy { | |
| public function getTitle(): string { | |
| return "🎉 Welcome to GenyTask!"; // 22 chars | |
| } | |
| public function getBody(): string { | |
| return "Thank you for adding our mini app! You will receive notifications for new tasks and updates."; | |
| } | |
| public function getTargetUrl(): string { | |
| return "https://task.genyapps.xyz"; | |
| } | |
| } | |
| /** | |
| * Update Notification | |
| */ | |
| class UpdateNotification implements NotificationStrategy { | |
| public function getTitle(): string { | |
| return "🚀 GenyTask Updated!"; | |
| } | |
| public function getBody(): string { | |
| return "New features have been added to the miniapp. Let's check it out."; | |
| } | |
| public function getTargetUrl(): string { | |
| return "https://task.genyapps.xyz"; | |
| } | |
| } | |
| /** | |
| * Security Alert Notification | |
| */ | |
| class SecurityAlertNotification implements NotificationStrategy { | |
| public function getTitle(): string { | |
| return "⚠️ Security Alert!"; | |
| } | |
| public function getBody(): string { | |
| return "Unusual activity detected on your account. Take action!"; | |
| } | |
| public function getTargetUrl(): string { | |
| return "https://task.genyapps.xyz"; | |
| } | |
| } | |
| /** | |
| * New Task Available Notification | |
| */ | |
| class NewTaskNotification implements NotificationStrategy { | |
| private string $taskType; | |
| private string $taskPoints; | |
| private string $taskPlatform; | |
| public function __construct(string $taskType = "regular", string $taskPoints = "", string $taskPlatform = "") { | |
| $this->taskType = $taskType; | |
| $this->taskPoints = $taskPoints; | |
| $this->taskPlatform = $taskPlatform; | |
| } | |
| public function getTitle(): string { | |
| return "🎯 New Task Available!"; // 22 chars | |
| } | |
| public function getBody(): string { | |
| $body = "A new "; | |
| // Add task type | |
| switch ($this->taskType) { | |
| case 'premium': | |
| $body .= "⭐ PREMIUM task"; | |
| break; | |
| case 'holding': | |
| $body .= "💎 HOLDING task"; | |
| break; | |
| case 'activities': | |
| $body .= "🎯 ACTIVITY task"; | |
| break; | |
| default: | |
| $body .= "🔄 task"; | |
| } | |
| $body .= " is waiting for you"; | |
| // Add platform if available | |
| if (!empty($this->taskPlatform)) { | |
| $body .= " on " . $this->taskPlatform; | |
| } | |
| // Add points if available | |
| if (!empty($this->taskPoints)) { | |
| $body .= " - Earn " . $this->taskPoints . " points!"; | |
| } else { | |
| $body .= " - Complete it now to earn rewards!"; | |
| } | |
| return $body; | |
| } | |
| public function getTargetUrl(): string { | |
| return "https://task.genyapps.xyz"; | |
| } | |
| } | |
| /** | |
| * Task Completion Notification | |
| */ | |
| class TaskCompletionNotification implements NotificationStrategy { | |
| private string $pointsEarned; | |
| private string $taskName; | |
| public function __construct(string $pointsEarned = "", string $taskName = "") { | |
| $this->pointsEarned = $pointsEarned; | |
| $this->taskName = $taskName; | |
| } | |
| public function getTitle(): string { | |
| return "✅ Task Completed!"; // 18 chars | |
| } | |
| public function getBody(): string { | |
| if (!empty($this->taskName) && !empty($this->pointsEarned)) { | |
| return "You completed '{$this->taskName}' and earned {$this->pointsEarned} points! Great work!"; | |
| } elseif (!empty($this->pointsEarned)) { | |
| return "You earned {$this->pointsEarned} points! Your GENY balance has increased!"; | |
| } else { | |
| return "Task completed successfully! Your progress is updated."; | |
| } | |
| } | |
| public function getTargetUrl(): string { | |
| return "https://task.genyapps.xyz"; | |
| } | |
| } | |
| /** | |
| * Leaderboard Achievement Notification | |
| */ | |
| class LeaderboardNotification implements NotificationStrategy { | |
| private string $rank; | |
| private string $achievement; | |
| public function __construct(string $rank = "", string $achievement = "") { | |
| $this->rank = $rank; | |
| $this->achievement = $achievement; | |
| } | |
| public function getTitle(): string { | |
| return "🏆 Leaderboard Update!"; // 22 chars | |
| } | |
| public function getBody(): string { | |
| if (!empty($this->rank)) { | |
| return "You're now ranked #{$this->rank} on the leaderboard! Keep climbing!"; | |
| } elseif (!empty($this->achievement)) { | |
| return "Achievement unlocked: {$this->achievement}! Check your leaderboard position."; | |
| } else { | |
| return "Your leaderboard position has changed! See where you stand in the community."; | |
| } | |
| } | |
| public function getTargetUrl(): string { | |
| return "https://task.genyapps.xyz"; | |
| } | |
| } | |
| /** | |
| * Factory to create notifications dynamically. | |
| */ | |
| class NotificationFactory { | |
| public static function createNotification(string $type, array $params = []): NotificationStrategy { | |
| return match ($type) { | |
| 'update' => new UpdateNotification(), | |
| 'alert' => new SecurityAlertNotification(), | |
| 'new_task' => new NewTaskNotification( | |
| $params['task_type'] ?? 'regular', | |
| $params['task_points'] ?? '', | |
| $params['task_platform'] ?? '' | |
| ), | |
| 'task_complete' => new TaskCompletionNotification( | |
| $params['points_earned'] ?? '', | |
| $params['task_name'] ?? '' | |
| ), | |
| 'leaderboard' => new LeaderboardNotification( | |
| $params['rank'] ?? '', | |
| $params['achievement'] ?? '' | |
| ), | |
| default => new WelcomeNotification(), // Default to 'welcome' | |
| }; | |
| } | |
| } | |
| /** | |
| * Class NotificationSender | |
| * | |
| * Handles storing and sending Farcaster notification details from database. | |
| */ | |
| class NotificationSender { | |
| /** | |
| * Gets database connection | |
| */ | |
| private function getDatabaseConnection() { | |
| require_once '../lib/db.php'; | |
| $conn = getDatabaseConnection('geny'); | |
| if (!$conn || $conn->connect_error) { | |
| throw new Exception("Database connection failed: " . ($conn ? $conn->connect_error : 'Connection failed')); | |
| } | |
| return $conn; | |
| } | |
| /** | |
| * Helper method to ensure title length compliance | |
| */ | |
| private function ensureTitleLength(string $title): string { | |
| $maxLength = 32; | |
| if (strlen($title) <= $maxLength) { | |
| return $title; | |
| } | |
| // Truncate and add ellipsis if needed | |
| return substr($title, 0, $maxLength - 3) . '...'; | |
| } | |
| /** | |
| * Gets all active notification tokens from database | |
| */ | |
| public function getNotificationTokens(): array { | |
| $conn = $this->getDatabaseConnection(); | |
| $sql = "SELECT fid, app_fid, notification_url, notification_token | |
| FROM user_notification_tokens | |
| WHERE notification_url IS NOT NULL | |
| AND notification_token IS NOT NULL | |
| AND notification_url != '' | |
| AND notification_token != ''"; | |
| $result = $conn->query($sql); | |
| $tokens = []; | |
| if ($result && $result->num_rows > 0) { | |
| while ($row = $result->fetch_assoc()) { | |
| $tokens[] = [ | |
| 'fid' => $row['fid'], | |
| 'app_fid' => $row['app_fid'], | |
| 'url' => $row['notification_url'], | |
| 'token' => $row['notification_token'] | |
| ]; | |
| } | |
| } | |
| $conn->close(); | |
| return $tokens; | |
| } | |
| /** | |
| * Gets notification tokens for a specific user | |
| */ | |
| public function getUserNotificationTokens(int $fid, int $appFid): ?array { | |
| $conn = $this->getDatabaseConnection(); | |
| $sql = "SELECT notification_url, notification_token | |
| FROM user_notification_tokens | |
| WHERE fid = ? AND app_fid = ? | |
| AND notification_url IS NOT NULL | |
| AND notification_token IS NOT NULL | |
| AND notification_url != '' | |
| AND notification_token != ''"; | |
| $stmt = $conn->prepare($sql); | |
| $stmt->bind_param("ii", $fid, $appFid); | |
| $stmt->execute(); | |
| $result = $stmt->get_result(); | |
| $tokenData = null; | |
| if ($row = $result->fetch_assoc()) { | |
| $tokenData = [ | |
| 'url' => $row['notification_url'], | |
| 'token' => $row['notification_token'] | |
| ]; | |
| } | |
| $stmt->close(); | |
| $conn->close(); | |
| return $tokenData; | |
| } | |
| /** | |
| * Sends notification to all users with proper success handling | |
| */ | |
| public function sendBroadcastNotification(string $type, array $params = []): array { | |
| $tokens = $this->getNotificationTokens(); | |
| if (empty($tokens)) { | |
| throw new Exception("No active notification tokens found in database"); | |
| } | |
| $notification = NotificationFactory::createNotification($type, $params); | |
| $results = [ | |
| 'success_count' => 0, | |
| 'fail_count' => 0, | |
| 'total' => count($tokens), | |
| 'details' => [] | |
| ]; | |
| foreach ($tokens as $tokenData) { | |
| try { | |
| $this->sendSingleNotification($tokenData, $notification, $type); | |
| $results['success_count']++; | |
| $results['details'][] = [ | |
| 'fid' => $tokenData['fid'], | |
| 'app_fid' => $tokenData['app_fid'], | |
| 'status' => 'success' | |
| ]; | |
| } catch (Exception $e) { | |
| $results['fail_count']++; | |
| $results['details'][] = [ | |
| 'fid' => $tokenData['fid'], | |
| 'app_fid' => $tokenData['app_fid'], | |
| 'status' => 'failed', | |
| 'error' => $e->getMessage() | |
| ]; | |
| error_log("Failed to send notification to FID: {$tokenData['fid']} - {$e->getMessage()}"); | |
| } | |
| } | |
| error_log("Broadcast completed: {$results['success_count']} successful, {$results['fail_count']} failed"); | |
| return $results; | |
| } | |
| /** | |
| * Sends notification to specific user | |
| */ | |
| public function sendUserNotification(int $fid, int $appFid, string $type, array $params = []): void { | |
| $tokenData = $this->getUserNotificationTokens($fid, $appFid); | |
| if (!$tokenData) { | |
| throw new Exception("No active notification tokens found for user FID: {$fid}"); | |
| } | |
| $notification = NotificationFactory::createNotification($type, $params); | |
| $this->sendSingleNotification(array_merge(['fid' => $fid, 'app_fid' => $appFid], $tokenData), $notification, $type); | |
| } | |
| /** | |
| * Sends a single notification with proper Farcaster API response handling | |
| */ | |
| private function sendSingleNotification(array $tokenData, NotificationStrategy $notification, string $type): void { | |
| $payload = [ | |
| 'notificationId' => uniqid('notif_', true), | |
| 'title' => $this->ensureTitleLength($notification->getTitle()), // Use the helper here | |
| 'body' => $notification->getBody(), | |
| 'targetUrl' => $notification->getTargetUrl(), | |
| 'tokens' => [$tokenData['token']], | |
| ]; | |
| $ch = curl_init($tokenData['url']); | |
| curl_setopt_array($ch, [ | |
| CURLOPT_RETURNTRANSFER => true, | |
| CURLOPT_HTTPHEADER => ['Content-Type: application/json'], | |
| CURLOPT_POST => true, | |
| CURLOPT_POSTFIELDS => json_encode($payload), | |
| CURLOPT_TIMEOUT => 10 | |
| ]); | |
| $response = curl_exec($ch); | |
| $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); | |
| if ($response === false) { | |
| $error = curl_error($ch); | |
| curl_close($ch); | |
| throw new Exception("cURL error: $error"); | |
| } | |
| curl_close($ch); | |
| $responseData = json_decode($response, true); | |
| // Handle Farcaster API response | |
| if ($httpCode === 200) { | |
| if (isset($responseData['result'])) { | |
| $result = $responseData['result']; | |
| // SUCCESS: If we have successfulTokens, the notification was delivered | |
| if (isset($result['successfulTokens']) && count($result['successfulTokens']) > 0) { | |
| // This is a SUCCESS - notification was delivered to at least one token | |
| return; | |
| } | |
| // Check for other scenarios | |
| if (isset($result['invalidTokens']) && count($result['invalidTokens']) > 0) { | |
| throw new Exception("Invalid tokens: " . implode(', ', $result['invalidTokens'])); | |
| } | |
| if (isset($result['rateLimitedTokens']) && count($result['rateLimitedTokens']) > 0) { | |
| throw new Exception("Rate limited tokens: " . implode(', ', $result['rateLimitedTokens'])); | |
| } | |
| // If we get here but no errors, it's still a success | |
| return; | |
| } else { | |
| // Response doesn't have the expected structure, but HTTP 200 means success | |
| return; | |
| } | |
| } elseif ($httpCode === 400) { | |
| // Bad request - usually validation errors | |
| if (isset($responseData['message'])) { | |
| throw new Exception("Validation error: " . $responseData['message']); | |
| } else { | |
| throw new Exception("Bad request - HTTP: {$httpCode}"); | |
| } | |
| } else { | |
| throw new Exception("Notification failed - HTTP: {$httpCode}, Response: " . substr($response, 0, 200)); | |
| } | |
| } | |
| /** | |
| * Gets notification parameters from request | |
| */ | |
| private function getNotificationParams(string $type): array { | |
| $params = []; | |
| switch ($type) { | |
| case 'new_task': | |
| $params = [ | |
| 'task_type' => $_GET['task_type'] ?? 'regular', | |
| 'task_points' => $_GET['task_points'] ?? '', | |
| 'task_platform' => $_GET['task_platform'] ?? '' | |
| ]; | |
| break; | |
| case 'task_complete': | |
| $params = [ | |
| 'points_earned' => $_GET['points_earned'] ?? '', | |
| 'task_name' => $_GET['task_name'] ?? '' | |
| ]; | |
| break; | |
| case 'leaderboard': | |
| $params = [ | |
| 'rank' => $_GET['rank'] ?? '', | |
| 'achievement' => $_GET['achievement'] ?? '' | |
| ]; | |
| break; | |
| } | |
| return $params; | |
| } | |
| /** | |
| * Processes the incoming request | |
| */ | |
| public function processRequest(): void { | |
| try { | |
| if ($_SERVER['REQUEST_METHOD'] === 'GET') { | |
| $type = $_GET['type'] ?? 'welcome'; | |
| $fid = isset($_GET['fid']) ? (int)$_GET['fid'] : null; | |
| $appFid = isset($_GET['app_fid']) ? (int)$_GET['app_fid'] : null; | |
| $params = $this->getNotificationParams($type); | |
| // If specific user is requested, send only to that user | |
| if ($fid !== null) { | |
| if ($appFid === null) { | |
| // If no app_fid provided, get all tokens for the FID | |
| $tokens = $this->getNotificationTokens(); | |
| $userTokens = array_filter($tokens, function($token) use ($fid) { | |
| return $token['fid'] == $fid; | |
| }); | |
| if (empty($userTokens)) { | |
| throw new Exception("No active notification tokens found for user FID: {$fid}"); | |
| } | |
| $results = [ | |
| 'success_count' => 0, | |
| 'fail_count' => 0, | |
| 'total' => count($userTokens), | |
| 'details' => [] | |
| ]; | |
| $notification = NotificationFactory::createNotification($type, $params); | |
| foreach ($userTokens as $tokenData) { | |
| try { | |
| $this->sendSingleNotification($tokenData, $notification, $type); | |
| $results['success_count']++; | |
| $results['details'][] = [ | |
| 'fid' => $tokenData['fid'], | |
| 'app_fid' => $tokenData['app_fid'], | |
| 'status' => 'success' | |
| ]; | |
| } catch (Exception $e) { | |
| $results['fail_count']++; | |
| $results['details'][] = [ | |
| 'fid' => $tokenData['fid'], | |
| 'app_fid' => $tokenData['app_fid'], | |
| 'status' => 'failed', | |
| 'error' => $e->getMessage() | |
| ]; | |
| } | |
| } | |
| echo json_encode([ | |
| 'status' => 'success', | |
| 'message' => "Notification sent to user FID: {$fid}", | |
| 'type' => $type, | |
| 'target' => 'user', | |
| 'fid' => $fid, | |
| 'results' => $results | |
| ]); | |
| } else { | |
| // Send to specific user with app_fid | |
| $this->sendUserNotification($fid, $appFid, $type, $params); | |
| echo json_encode([ | |
| 'status' => 'success', | |
| 'message' => "Notification sent to user FID: {$fid}", | |
| 'type' => $type, | |
| 'target' => 'user', | |
| 'fid' => $fid, | |
| 'app_fid' => $appFid | |
| ]); | |
| } | |
| } else { | |
| // Broadcast to all users (existing behavior) | |
| $results = $this->sendBroadcastNotification($type, $params); | |
| echo json_encode([ | |
| 'status' => 'success', | |
| 'message' => "Broadcast notification sent to {$results['success_count']} users", | |
| 'type' => $type, | |
| 'target' => 'all', | |
| 'results' => $results | |
| ]); | |
| } | |
| } else { | |
| echo json_encode([ | |
| 'status' => 'error', | |
| 'message' => 'Only GET requests allowed for sending notifications' | |
| ]); | |
| } | |
| } catch (Exception $e) { | |
| echo json_encode([ | |
| 'status' => 'error', | |
| 'message' => $e->getMessage() | |
| ]); | |
| } | |
| } | |
| } | |
| // Create an instance and process the request. | |
| $notificationSender = new NotificationSender(); | |
| $notificationSender->processRequest(); | |
| ?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment