328 lines
16 KiB
Java
328 lines
16 KiB
Java
package com.rit.whatsapp.controller;
|
|
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import com.rit.whatsapp.handler.GrievanceHandler;
|
|
import com.rit.whatsapp.handler.LoginHandler;
|
|
import com.rit.whatsapp.handler.StudentMenuHandler;
|
|
import com.rit.whatsapp.model.UserState;
|
|
import com.rit.whatsapp.service.SessionService;
|
|
import com.rit.whatsapp.service.WhatsAppMessageService;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
import org.springframework.http.HttpStatus;
|
|
import org.springframework.http.ResponseEntity;
|
|
import org.springframework.web.bind.annotation.*;
|
|
|
|
@RestController
|
|
@RequestMapping("/webhook")
|
|
public class WebhookController {
|
|
|
|
@Value("${whatsapp.verify-token:my_secure_verify_token}")
|
|
private String verifyToken;
|
|
|
|
@Value("${whatsapp.phone-number-id:}")
|
|
private String configuredPhoneId;
|
|
|
|
@Value("${whatsapp.dev-webhook-forward-url:}")
|
|
private String devWebhookUrl;
|
|
|
|
private final WhatsAppMessageService messageService;
|
|
private final SessionService sessionService;
|
|
private final StudentMenuHandler studentMenuHandler;
|
|
private final GrievanceHandler grievanceHandler;
|
|
private final LoginHandler loginHandler;
|
|
private final ObjectMapper objectMapper;
|
|
private final com.rit.whatsapp.handler.FacultyMenuHandler facultyMenuHandler;
|
|
|
|
@Autowired
|
|
public WebhookController(WhatsAppMessageService messageService,
|
|
SessionService sessionService,
|
|
StudentMenuHandler studentMenuHandler,
|
|
GrievanceHandler grievanceHandler,
|
|
LoginHandler loginHandler,
|
|
com.rit.whatsapp.handler.FacultyMenuHandler facultyMenuHandler) {
|
|
this.messageService = messageService;
|
|
this.sessionService = sessionService;
|
|
this.studentMenuHandler = studentMenuHandler;
|
|
this.grievanceHandler = grievanceHandler;
|
|
this.loginHandler = loginHandler;
|
|
this.facultyMenuHandler = facultyMenuHandler;
|
|
this.objectMapper = new ObjectMapper();
|
|
}
|
|
|
|
@GetMapping
|
|
public ResponseEntity<String> verifyWebhook(
|
|
@RequestParam(name = "hub.mode", required = false) String mode,
|
|
@RequestParam(name = "hub.verify_token", required = false) String token,
|
|
@RequestParam(name = "hub.challenge", required = false) String challenge) {
|
|
|
|
if ("subscribe".equals(mode) && verifyToken.equals(token)) {
|
|
return ResponseEntity.ok(challenge);
|
|
} else {
|
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
|
|
}
|
|
}
|
|
|
|
@PostMapping
|
|
public ResponseEntity<String> receiveMessage(@RequestBody String payload) {
|
|
System.out.println("Received Webhook Payload: " + payload);
|
|
try {
|
|
JsonNode rootNode = objectMapper.readTree(payload);
|
|
|
|
if (rootNode.has("entry") && rootNode.get("entry").isArray()) {
|
|
JsonNode entryNode = rootNode.get("entry").get(0);
|
|
|
|
if (entryNode.has("changes") && entryNode.get("changes").isArray()) {
|
|
JsonNode changesNode = entryNode.get("changes").get(0);
|
|
JsonNode valueNode = changesNode.get("value");
|
|
|
|
if (valueNode != null) {
|
|
if (valueNode.has("metadata")) {
|
|
JsonNode metadataNode = valueNode.get("metadata");
|
|
if (metadataNode.has("phone_number_id")) {
|
|
String incomingPhoneId = metadataNode.get("phone_number_id").asText();
|
|
if (configuredPhoneId != null && !configuredPhoneId.trim().isEmpty()
|
|
&& !incomingPhoneId.trim().equals(configuredPhoneId.trim())) {
|
|
if (devWebhookUrl != null && !devWebhookUrl.trim().isEmpty()) {
|
|
System.out.println("Incoming phone ID " + incomingPhoneId + " does not match configured ID " + configuredPhoneId + ". Forwarding to " + devWebhookUrl);
|
|
try {
|
|
org.springframework.web.client.RestTemplate tempTemplate = new org.springframework.web.client.RestTemplate();
|
|
org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders();
|
|
headers.setContentType(org.springframework.http.MediaType.APPLICATION_JSON);
|
|
org.springframework.http.HttpEntity<String> entity = new org.springframework.http.HttpEntity<>(payload, headers);
|
|
tempTemplate.postForEntity(devWebhookUrl, entity, String.class);
|
|
} catch (Exception forwardEx) {
|
|
System.err.println("Failed to forward webhook to dev bot: " + forwardEx.getMessage());
|
|
}
|
|
return ResponseEntity.ok("EVENT_FORWARDED");
|
|
} else {
|
|
System.out.println("Incoming phone ID " + incomingPhoneId + " does not match configured ID " + configuredPhoneId + ". Ignoring webhook payload.");
|
|
return ResponseEntity.ok("EVENT_IGNORED");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (valueNode != null && valueNode.has("messages") && valueNode.get("messages").isArray()) {
|
|
JsonNode messageNode = valueNode.get("messages").get(0);
|
|
|
|
String fromPhoneNumber = messageNode.get("from").asText();
|
|
String messageType = messageNode.get("type").asText();
|
|
|
|
UserState currentState = sessionService.getUserState(fromPhoneNumber);
|
|
|
|
boolean isInMemory = sessionService.isSessionCached(fromPhoneNumber);
|
|
boolean hasDbRecord = sessionService.hasSavedCredentials(fromPhoneNumber);
|
|
if (!isInMemory && hasDbRecord) {
|
|
messageService.sendMessage(fromPhoneNumber, "Authenticating User with IMS... ⏳");
|
|
}
|
|
|
|
boolean isAuthenticated = sessionService.isAuthenticated(fromPhoneNumber);
|
|
|
|
// Global reset command
|
|
if ("text".equals(messageType) && messageNode.get("text").get("body").asText().trim().equalsIgnoreCase("reset")) {
|
|
sessionService.resetState(fromPhoneNumber);
|
|
if (isAuthenticated) {
|
|
if (sessionService.isFaculty(fromPhoneNumber)) {
|
|
facultyMenuHandler.sendMainMenu(fromPhoneNumber);
|
|
} else {
|
|
studentMenuHandler.sendMainMenu(fromPhoneNumber);
|
|
}
|
|
} else {
|
|
loginHandler.startLoginFlow(fromPhoneNumber);
|
|
}
|
|
return ResponseEntity.ok("EVENT_RECEIVED");
|
|
}
|
|
|
|
// Global erase command
|
|
if ("text".equals(messageType) && messageNode.get("text").get("body").asText().trim().equalsIgnoreCase("erase")) {
|
|
sessionService.deleteUser(fromPhoneNumber);
|
|
messageService.sendMessage(fromPhoneNumber, "Your IMS credentials have been deleted from our records. Type 'Hi' to start again.");
|
|
return ResponseEntity.ok("EVENT_RECEIVED");
|
|
}
|
|
|
|
// Route unauthenticated users strictly to Login Flow
|
|
if (!isAuthenticated) {
|
|
if ("text".equals(messageType)) {
|
|
String body = messageNode.get("text").get("body").asText();
|
|
handleUnauthenticatedUser(fromPhoneNumber, body, currentState);
|
|
} else {
|
|
handleUnauthenticatedUser(fromPhoneNumber, "", currentState);
|
|
}
|
|
return ResponseEntity.ok("EVENT_RECEIVED");
|
|
}
|
|
|
|
// Authenticated users flow
|
|
if ("text".equals(messageType)) {
|
|
String messageBody = messageNode.get("text").get("body").asText();
|
|
handleAuthenticatedTextMessage(fromPhoneNumber, messageBody, currentState);
|
|
} else if ("image".equals(messageType)) {
|
|
if (currentState == UserState.AWAITING_GRIEVANCE_IMAGE) {
|
|
JsonNode imageNode = messageNode.get("image");
|
|
String mediaId = imageNode.get("id").asText();
|
|
String mimeType = imageNode.has("mime_type") ? imageNode.get("mime_type").asText() : "image/jpeg";
|
|
grievanceHandler.handleImageInput(fromPhoneNumber, mediaId, mimeType);
|
|
} else {
|
|
if (sessionService.isFaculty(fromPhoneNumber)) {
|
|
facultyMenuHandler.sendMainMenu(fromPhoneNumber);
|
|
} else {
|
|
studentMenuHandler.sendMainMenu(fromPhoneNumber);
|
|
}
|
|
}
|
|
} else if ("interactive".equals(messageType)) {
|
|
JsonNode interactiveNode = messageNode.get("interactive");
|
|
String interactiveType = interactiveNode.get("type").asText();
|
|
|
|
if ("list_reply".equals(interactiveType)) {
|
|
String selectedListId = interactiveNode.get("list_reply").get("id").asText();
|
|
handleInteractiveListSelection(fromPhoneNumber, selectedListId, currentState);
|
|
} else if ("button_reply".equals(interactiveType)) {
|
|
String selectedButtonId = interactiveNode.get("button_reply").get("id").asText();
|
|
handleInteractiveButtonSelection(fromPhoneNumber, selectedButtonId, currentState);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
System.err.println("Error processing webhook payload: " + e.getMessage());
|
|
}
|
|
|
|
return ResponseEntity.ok("EVENT_RECEIVED");
|
|
}
|
|
|
|
private void handleUnauthenticatedUser(String phoneNumber, String messageBody, UserState currentState) {
|
|
switch (currentState) {
|
|
case AWAITING_LOGIN_ID:
|
|
loginHandler.handleLoginIdInput(phoneNumber, messageBody);
|
|
break;
|
|
case AWAITING_LOGIN_PASSCODE:
|
|
loginHandler.handlePasscodeInput(phoneNumber, messageBody);
|
|
break;
|
|
default:
|
|
loginHandler.startLoginFlow(phoneNumber);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void handleAuthenticatedTextMessage(String phoneNumber, String messageBody, UserState currentState) {
|
|
switch (currentState) {
|
|
case AWAITING_GRIEVANCE_TITLE:
|
|
grievanceHandler.handleTitleInput(phoneNumber, messageBody);
|
|
break;
|
|
case AWAITING_GRIEVANCE_DESC:
|
|
grievanceHandler.handleDescriptionInput(phoneNumber, messageBody);
|
|
break;
|
|
case AWAITING_GRIEVANCE_IMAGE:
|
|
if (messageBody.trim().equalsIgnoreCase("skip")) {
|
|
grievanceHandler.handleSkipImage(phoneNumber);
|
|
} else {
|
|
messageService.sendMessage(phoneNumber, "Please send a photo or type 'skip' / click 'Skip Image' button.");
|
|
}
|
|
break;
|
|
case AWAITING_ABSENTEES:
|
|
facultyMenuHandler.handleAbsenteesInput(phoneNumber, messageBody);
|
|
break;
|
|
case MAIN_MENU:
|
|
default:
|
|
if (sessionService.isFaculty(phoneNumber)) {
|
|
facultyMenuHandler.sendMainMenu(phoneNumber);
|
|
} else {
|
|
studentMenuHandler.sendMainMenu(phoneNumber);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void handleInteractiveListSelection(String phoneNumber, String selectedListId, UserState currentState) {
|
|
if (selectedListId.startsWith("grv_action_")) {
|
|
grievanceHandler.handleActionSelection(phoneNumber, selectedListId);
|
|
} else if (selectedListId.startsWith("grv_dept_")) {
|
|
grievanceHandler.handleDepartmentSelection(phoneNumber, selectedListId);
|
|
} else if (selectedListId.startsWith("grv_status_")) {
|
|
grievanceHandler.handleStatusSelection(phoneNumber, selectedListId);
|
|
} else if (selectedListId.startsWith("grv_delete_")) {
|
|
grievanceHandler.handleDeleteSelection(phoneNumber, selectedListId);
|
|
} else if (selectedListId.startsWith("student_")) {
|
|
studentMenuHandler.handleMenuSelection(phoneNumber, selectedListId);
|
|
} else if (selectedListId.startsWith("faculty_")) {
|
|
facultyMenuHandler.handleMenuSelection(phoneNumber, selectedListId);
|
|
} else if (selectedListId.startsWith("sem_")) {
|
|
studentMenuHandler.handleSemesterSelection(phoneNumber, selectedListId);
|
|
} else if (selectedListId.startsWith("receipt_") || selectedListId.startsWith("exam_") || selectedListId.startsWith("acad_") || selectedListId.startsWith("cons_")) {
|
|
studentMenuHandler.handleReceiptSelection(phoneNumber, selectedListId);
|
|
} else {
|
|
if (sessionService.isFaculty(phoneNumber)) {
|
|
facultyMenuHandler.sendMainMenu(phoneNumber);
|
|
} else {
|
|
studentMenuHandler.sendMainMenu(phoneNumber);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void handleInteractiveButtonSelection(String phoneNumber, String selectedButtonId, UserState currentState) {
|
|
if (selectedButtonId.startsWith("faculty_")) {
|
|
facultyMenuHandler.handleButtonSelection(phoneNumber, selectedButtonId);
|
|
return;
|
|
}
|
|
|
|
if ("student_logout".equals(selectedButtonId)) {
|
|
sessionService.logoutUser(phoneNumber);
|
|
messageService.sendMessage(phoneNumber, "You have been logged out successfully. Type 'Hi' to log in again.");
|
|
return;
|
|
}
|
|
|
|
if ("student_weekly_timetable".equals(selectedButtonId)) {
|
|
studentMenuHandler.handleWeeklyTimetable(phoneNumber);
|
|
return;
|
|
}
|
|
|
|
if ("student_show_menu".equals(selectedButtonId)) {
|
|
studentMenuHandler.sendStudentMenuDirectly(phoneNumber);
|
|
return;
|
|
}
|
|
|
|
if ("student_exam_fees".equals(selectedButtonId)) {
|
|
studentMenuHandler.handleExamFees(phoneNumber);
|
|
return;
|
|
}
|
|
|
|
if ("student_cat1_marks".equals(selectedButtonId)) {
|
|
studentMenuHandler.handleCat1Marks(phoneNumber);
|
|
return;
|
|
}
|
|
|
|
if ("student_cat2_marks".equals(selectedButtonId)) {
|
|
studentMenuHandler.handleCat2Marks(phoneNumber);
|
|
return;
|
|
}
|
|
|
|
if ("student_cat3_marks".equals(selectedButtonId)) {
|
|
studentMenuHandler.handleCat3Marks(phoneNumber);
|
|
return;
|
|
}
|
|
|
|
if ("student_assignment_marks".equals(selectedButtonId)) {
|
|
studentMenuHandler.handleAssignmentMarks(phoneNumber);
|
|
return;
|
|
}
|
|
|
|
if ("student_payment_history".equals(selectedButtonId)) {
|
|
studentMenuHandler.handlePaymentHistory(phoneNumber);
|
|
return;
|
|
}
|
|
|
|
if ("grv_skip_image".equals(selectedButtonId)) {
|
|
grievanceHandler.handleSkipImage(phoneNumber);
|
|
} else {
|
|
if (sessionService.isFaculty(phoneNumber)) {
|
|
facultyMenuHandler.sendMainMenu(phoneNumber);
|
|
} else {
|
|
studentMenuHandler.sendMainMenu(phoneNumber);
|
|
}
|
|
}
|
|
}
|
|
}
|