From a0fbe836c4c85adab6acdbf75901c9c10ee9a261 Mon Sep 17 00:00:00 2001 From: RIT Services Date: Sat, 29 Aug 2026 07:22:13 +0000 Subject: [PATCH] feat: init development branch with permanent access token and menu handlers --- .env | 2 +- .../controller/WebhookController.java | 52 ++- .../whatsapp/handler/FacultyMenuHandler.java | 442 ++++++++++++++++++ .../rit/whatsapp/handler/LoginHandler.java | 18 +- .../com/rit/whatsapp/model/UserEntity.java | 30 +- .../com/rit/whatsapp/model/UserState.java | 3 +- .../whatsapp/repository/UserRepository.java | 3 + .../rit/whatsapp/service/ImsApiService.java | 83 +++- .../rit/whatsapp/service/SessionService.java | 106 ++++- .../rit/whatsapp/util/MessageFormatter.java | 306 ++++++++++++ src/main/resources/application.properties | 5 +- 11 files changed, 1014 insertions(+), 36 deletions(-) create mode 100644 src/main/java/com/rit/whatsapp/handler/FacultyMenuHandler.java diff --git a/.env b/.env index d312488..15c74f8 100644 --- a/.env +++ b/.env @@ -1,3 +1,3 @@ -DB_URL=jdbc:postgresql://localhost:5432/whatsapp_bot +DB_URL=jdbc:postgresql://localhost:5432/IMS%20data DB_USERNAME=whatsapp_user DB_PASSWORD=RITHosting123 diff --git a/src/main/java/com/rit/whatsapp/controller/WebhookController.java b/src/main/java/com/rit/whatsapp/controller/WebhookController.java index f9ddf80..fdcca8a 100644 --- a/src/main/java/com/rit/whatsapp/controller/WebhookController.java +++ b/src/main/java/com/rit/whatsapp/controller/WebhookController.java @@ -27,18 +27,21 @@ public class WebhookController { 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) { + 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(); } @@ -88,13 +91,24 @@ public class WebhookController { if ("text".equals(messageType) && messageNode.get("text").get("body").asText().trim().equalsIgnoreCase("reset")) { sessionService.resetState(fromPhoneNumber); if (isAuthenticated) { - studentMenuHandler.sendMainMenu(fromPhoneNumber); + 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)) { @@ -117,7 +131,11 @@ public class WebhookController { String mimeType = imageNode.has("mime_type") ? imageNode.get("mime_type").asText() : "image/jpeg"; grievanceHandler.handleImageInput(fromPhoneNumber, mediaId, mimeType); } else { - studentMenuHandler.sendMainMenu(fromPhoneNumber); + if (sessionService.isFaculty(fromPhoneNumber)) { + facultyMenuHandler.sendMainMenu(fromPhoneNumber); + } else { + studentMenuHandler.sendMainMenu(fromPhoneNumber); + } } } else if ("interactive".equals(messageType)) { JsonNode interactiveNode = messageNode.get("interactive"); @@ -170,9 +188,16 @@ public class WebhookController { 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: - studentMenuHandler.sendMainMenu(phoneNumber); + if (sessionService.isFaculty(phoneNumber)) { + facultyMenuHandler.sendMainMenu(phoneNumber); + } else { + studentMenuHandler.sendMainMenu(phoneNumber); + } break; } } @@ -188,16 +213,27 @@ public class WebhookController { 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 { - studentMenuHandler.sendMainMenu(phoneNumber); + 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."); @@ -247,7 +283,11 @@ public class WebhookController { if ("grv_skip_image".equals(selectedButtonId)) { grievanceHandler.handleSkipImage(phoneNumber); } else { - studentMenuHandler.sendMainMenu(phoneNumber); + if (sessionService.isFaculty(phoneNumber)) { + facultyMenuHandler.sendMainMenu(phoneNumber); + } else { + studentMenuHandler.sendMainMenu(phoneNumber); + } } } } diff --git a/src/main/java/com/rit/whatsapp/handler/FacultyMenuHandler.java b/src/main/java/com/rit/whatsapp/handler/FacultyMenuHandler.java new file mode 100644 index 0000000..3bf68c3 --- /dev/null +++ b/src/main/java/com/rit/whatsapp/handler/FacultyMenuHandler.java @@ -0,0 +1,442 @@ +package com.rit.whatsapp.handler; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.rit.whatsapp.model.UserState; +import com.rit.whatsapp.service.ImsApiService; +import com.rit.whatsapp.service.SessionService; +import com.rit.whatsapp.service.WhatsAppMessageBuilder; +import com.rit.whatsapp.service.WhatsAppMessageService; +import com.rit.whatsapp.util.MessageFormatter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.*; + +@Component +public class FacultyMenuHandler { + + private final WhatsAppMessageService messageService; + private final SessionService sessionService; + private final ImsApiService imsApiService; + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Autowired + public FacultyMenuHandler(WhatsAppMessageService messageService, + SessionService sessionService, + ImsApiService imsApiService) { + this.messageService = messageService; + this.sessionService = sessionService; + this.imsApiService = imsApiService; + } + + public void sendMainMenu(String phoneNumber) { + sessionService.setUserState(phoneNumber, UserState.MAIN_MENU); + String token = sessionService.getImsToken(phoneNumber); + String name = sessionService.getStudentName(phoneNumber, token); + + String text = "Welcome back, *" + name + "*! šŸ‘Øā€šŸ«\n\nWhat would you like to do today?"; + + java.util.Map interactiveList = WhatsAppMessageBuilder.buildListMessage( + "RIT Digital Assistant", + text, + "Faculty Menu", + getFacultyMenuRows() + ); + + messageService.sendInteractiveMessage(phoneNumber, interactiveList); + } + + public void sendFacultyMenuDirectly(String phoneNumber) { + sendMainMenu(phoneNumber); + } + + public void handleMenuSelection(String phoneNumber, String selectedListId) { + if (selectedListId.startsWith("faculty_markatt_select_")) { + try { + int index = Integer.parseInt(selectedListId.substring("faculty_markatt_select_".length())); + handleClassSelected(phoneNumber, index); + } catch (Exception e) { + sendResponseWithMenu(phoneNumber, "āŒ Invalid selection."); + } + return; + } + + switch (selectedListId) { + case "faculty_timetable": + handleTimetable(phoneNumber); + break; + case "faculty_subjects": + handleSubjects(phoneNumber); + break; + case "faculty_attendance": + handleAttendanceSubjects(phoneNumber); + break; + case "faculty_logout": + sessionService.logoutUser(phoneNumber); + messageService.sendMessage(phoneNumber, "You have been logged out successfully. Type 'Hi' to log in again."); + break; + default: + sendMainMenu(phoneNumber); + break; + } + } + + public void handleTimetable(String phoneNumber) { + String token = sessionService.getImsToken(phoneNumber); + if (token == null) { + messageService.sendMessage(phoneNumber, "Your session has expired. Please type 'Reset' to login again."); + return; + } + sendTimetableMenu(phoneNumber, token); + } + + public void sendTimetableMenu(String phoneNumber, String token) { + String timetableJson = imsApiService.fetchFromIms("/api/faculty/timetable", token); + if (timetableJson == null) { + sendResponseWithMenu(phoneNumber, "āŒ Failed to fetch timetable. Please try again later."); + return; + } + + String todaySchedule = MessageFormatter.formatFacultyTimetable(timetableJson); + if (todaySchedule.length() > 1020) { + todaySchedule = todaySchedule.substring(0, 1017) + "..."; + } + + List> buttons = new ArrayList<>(); + Map weeklyBtn = new HashMap<>(); + weeklyBtn.put("id", "faculty_weekly_timetable"); + weeklyBtn.put("title", "Weekly Timetable"); + buttons.add(weeklyBtn); + + Map menuBtn = new HashMap<>(); + menuBtn.put("id", "faculty_show_menu"); + menuBtn.put("title", "Faculty Menu"); + buttons.add(menuBtn); + + Map buttonMessage = WhatsAppMessageBuilder.buildButtonMessage(todaySchedule, buttons); + messageService.sendInteractiveMessage(phoneNumber, buttonMessage); + sendMainMenu(phoneNumber); + } + + public void handleWeeklyTimetable(String phoneNumber) { + String token = sessionService.getImsToken(phoneNumber); + if (token == null) { + messageService.sendMessage(phoneNumber, "Your session has expired. Please type 'Reset' to login again."); + return; + } + + String rawTimetable = imsApiService.fetchFromIms("/api/faculty/timetable", token); + String weeklyTimetable = MessageFormatter.formatFacultyWeeklyTimetable(rawTimetable); + + messageService.sendMessage(phoneNumber, weeklyTimetable); + sendTimetableMenu(phoneNumber, token); + } + + public void handleSubjects(String phoneNumber) { + String token = sessionService.getImsToken(phoneNumber); + String subjectsJson = imsApiService.fetchFromIms("/api/faculty/subjects", token); + + if (subjectsJson != null) { + String formattedSubjects = MessageFormatter.formatFacultySubjects(subjectsJson); + sendResponseWithMenu(phoneNumber, formattedSubjects); + } else { + sendResponseWithMenu(phoneNumber, "āŒ Failed to fetch subjects. Please try again later."); + } + } + + public void handleAttendanceSubjects(String phoneNumber) { + sendResponseWithMenu(phoneNumber, "🚧 *Feature under development*"); + } + + public void handleClassSelected(String phoneNumber, int index) { + Map params = sessionService.getAttendanceSubjectParams(phoneNumber, index); + if (params == null) { + sendResponseWithMenu(phoneNumber, "āŒ Failed to retrieve class details. Please try again."); + return; + } + + sessionService.setActiveAttendanceParams(phoneNumber, params); + + String subjectName = (String) params.get("subjectName"); + Object periodsObj = params.get("periods"); + Object classesObj = params.get("classes"); + + String periodStr = (periodsObj instanceof List) ? String.join(", ", (List) periodsObj) : String.valueOf(periodsObj); + String classStr = (classesObj instanceof List) ? String.join(", ", (List) classesObj) : String.valueOf(classesObj); + + String body = "Selected Class:\nšŸ“š *" + subjectName + "*\nšŸ“… Period: *" + periodStr + "*\nšŸ« Class: *" + classStr + "*\n\nChoose an action:"; + + List> buttons = new ArrayList<>(); + Map presentBtn = new HashMap<>(); + presentBtn.put("id", "faculty_mark_present"); + presentBtn.put("title", "Mark All Present"); + buttons.add(presentBtn); + + Map absBtn = new HashMap<>(); + absBtn.put("id", "faculty_mark_absentees"); + absBtn.put("title", "Enter Absentees"); + buttons.add(absBtn); + + Map buttonMessage = WhatsAppMessageBuilder.buildButtonMessage(body, buttons); + messageService.sendInteractiveMessage(phoneNumber, buttonMessage); + } + + public void handleButtonSelection(String phoneNumber, String selectedButtonId) { + switch (selectedButtonId) { + case "faculty_mark_present": + handleMarkAllPresent(phoneNumber); + break; + case "faculty_mark_absentees": + handleStartAbsenteesFlow(phoneNumber); + break; + case "faculty_weekly_timetable": + handleWeeklyTimetable(phoneNumber); + break; + case "faculty_show_menu": + sendMainMenu(phoneNumber); + break; + } + } + + private Map mapToApiParams(Map params) { + Map apiParams = new HashMap<>(); + apiParams.put("date", params.get("date")); + apiParams.put("period", params.get("periods")); + apiParams.put("subject", params.get("subjectId")); + apiParams.put("staff", params.get("staffId")); + apiParams.put("classes", params.get("classes")); + apiParams.put("special", params.get("specialSub") != null ? params.get("specialSub") : 0); + apiParams.put("regular", params.get("regularSub") != null ? params.get("regularSub") : 1); + apiParams.put("batch", ""); + return apiParams; + } + + public void handleMarkAllPresent(String phoneNumber) { + String token = sessionService.getImsToken(phoneNumber); + Map activeParams = sessionService.getActiveAttendanceParams(phoneNumber); + if (activeParams == null) { + sendResponseWithMenu(phoneNumber, "āŒ Session expired or invalid class. Please try again."); + return; + } + + messageService.sendMessage(phoneNumber, "Retrieving student list... ā³"); + + String rawList = imsApiService.getStudentListForAttendance(token, mapToApiParams(activeParams)); + if (rawList == null) { + sendResponseWithMenu(phoneNumber, "āŒ Failed to retrieve student list from IMS."); + return; + } + + try { + JsonNode root = objectMapper.readTree(rawList); + if (root.has("success") && !root.get("success").asBoolean()) { + String errorMsg = root.has("message") ? root.get("message").asText() : "Unknown IMS error"; + sendResponseWithMenu(phoneNumber, "āŒ IMS Error: " + errorMsg); + return; + } + } catch (Exception e) { + // Proceed to parse HTML if not JSON error + } + + List students = MessageFormatter.parseStudents(rawList); + if (students.isEmpty()) { + sendResponseWithMenu(phoneNumber, "āŒ No students found in this class."); + return; + } + + Map postData = new HashMap<>(); + postData.put("date", activeParams.get("date")); + postData.put("period", activeParams.get("periods")); + postData.put("subject", activeParams.get("subjectId")); + + List> studentStatusList = new ArrayList<>(); + for (MessageFormatter.StudentAttendanceInfo s : students) { + Map sStatus = new HashMap<>(); + sStatus.put("id", s.id); + sStatus.put("status", "present"); + studentStatusList.add(sStatus); + } + postData.put("students", studentStatusList); + + messageService.sendMessage(phoneNumber, "Submitting attendance to IMS... ā³"); + String submitRes = imsApiService.submitAttendance(token, postData); + boolean isSuccess = false; + String errorMsg = "Unknown error"; + try { + if (submitRes != null) { + JsonNode root = objectMapper.readTree(submitRes); + if (root.has("success") && root.get("success").asBoolean()) { + isSuccess = true; + } else if (root.has("message")) { + errorMsg = root.get("message").asText(); + } + } + } catch (Exception e) { + // fallback + } + + if (isSuccess) { + sendResponseWithMenu(phoneNumber, "āœ… Attendance marked successfully! (100% Present)"); + } else { + sendResponseWithMenu(phoneNumber, "āŒ Failed to submit attendance: " + errorMsg); + } + sessionService.clearActiveAttendanceParams(phoneNumber); + } + + public void handleStartAbsenteesFlow(String phoneNumber) { + String token = sessionService.getImsToken(phoneNumber); + Map activeParams = sessionService.getActiveAttendanceParams(phoneNumber); + if (activeParams == null) { + sendResponseWithMenu(phoneNumber, "āŒ Session expired or invalid class. Please try again."); + return; + } + + messageService.sendMessage(phoneNumber, "Retrieving student list... ā³"); + + String rawList = imsApiService.getStudentListForAttendance(token, mapToApiParams(activeParams)); + if (rawList == null) { + sendResponseWithMenu(phoneNumber, "āŒ Failed to retrieve student list from IMS."); + return; + } + + try { + JsonNode root = objectMapper.readTree(rawList); + if (root.has("success") && !root.get("success").asBoolean()) { + String errorMsg = root.has("message") ? root.get("message").asText() : "Unknown IMS error"; + sendResponseWithMenu(phoneNumber, "āŒ IMS Error: " + errorMsg); + return; + } + } catch (Exception e) { + // Proceed to parse HTML if not JSON error + } + + List students = MessageFormatter.parseStudents(rawList); + if (students.isEmpty()) { + sendResponseWithMenu(phoneNumber, "āŒ No students found in this class."); + return; + } + + sessionService.storeActiveStudentList(phoneNumber, students); + sessionService.setUserState(phoneNumber, UserState.AWAITING_ABSENTEES); + + StringBuilder sb = new StringBuilder(); + sb.append("šŸ“‹ *Student List*\n"); + sb.append("Please reply with the Serial Numbers of the ABSENT students separated by commas (e.g., 2, 5).\n"); + sb.append("Type *'none'* if everyone is present.\n━━━━━━━━━━━━━━\n\n"); + + for (int i = 0; i < students.size(); i++) { + MessageFormatter.StudentAttendanceInfo s = students.get(i); + sb.append(i + 1).append(". `").append(s.regNo).append("` - ").append(s.name).append("\n"); + } + + messageService.sendMessage(phoneNumber, sb.toString()); + } + + public void handleAbsenteesInput(String phoneNumber, String messageBody) { + String token = sessionService.getImsToken(phoneNumber); + Map activeParams = sessionService.getActiveAttendanceParams(phoneNumber); + List students = sessionService.getActiveStudentList(phoneNumber); + + if (activeParams == null || students == null) { + sessionService.resetState(phoneNumber); + sendResponseWithMenu(phoneNumber, "āŒ Session expired. Please restart marking attendance."); + return; + } + + Set absenteeIndices = new HashSet<>(); + String input = messageBody.trim().toLowerCase(); + + if (!input.equals("none") && !input.equals("nil") && !input.equals("no one")) { + String[] parts = input.split(","); + for (String p : parts) { + try { + int val = Integer.parseInt(p.trim()); + if (val >= 1 && val <= students.size()) { + absenteeIndices.add(val - 1); + } + } catch (NumberFormatException e) { + // Ignore non-numeric inputs + } + } + } + + Map postData = new HashMap<>(); + postData.put("date", activeParams.get("date")); + postData.put("period", activeParams.get("periods")); + postData.put("subject", activeParams.get("subjectId")); + + List> studentStatusList = new ArrayList<>(); + int absentCount = 0; + for (int i = 0; i < students.size(); i++) { + MessageFormatter.StudentAttendanceInfo s = students.get(i); + Map sStatus = new HashMap<>(); + sStatus.put("id", s.id); + if (absenteeIndices.contains(i)) { + sStatus.put("status", "absent"); + absentCount++; + } else { + sStatus.put("status", "present"); + } + studentStatusList.add(sStatus); + } + postData.put("students", studentStatusList); + + messageService.sendMessage(phoneNumber, "Submitting attendance to IMS... ā³"); + String submitRes = imsApiService.submitAttendance(token, postData); + boolean isSuccess = false; + String errorMsg = "Unknown error"; + try { + if (submitRes != null) { + JsonNode root = objectMapper.readTree(submitRes); + if (root.has("success") && root.get("success").asBoolean()) { + isSuccess = true; + } else if (root.has("message")) { + errorMsg = root.get("message").asText(); + } + } + } catch (Exception e) { + // fallback + } + + sessionService.resetState(phoneNumber); + sessionService.clearActiveAttendanceParams(phoneNumber); + + if (isSuccess) { + sendResponseWithMenu(phoneNumber, "āœ… Attendance marked successfully! (" + (students.size() - absentCount) + " Present, " + absentCount + " Absent)"); + } else { + sendResponseWithMenu(phoneNumber, "āŒ Failed to submit attendance: " + errorMsg); + } + } + + public void sendResponseWithMenu(String phoneNumber, String bodyText) { + if (bodyText.length() > 1024) { + bodyText = bodyText.substring(0, 1021) + "..."; + } + + java.util.Map interactiveList = WhatsAppMessageBuilder.buildListMessage( + null, + bodyText, + "Faculty Menu", + getFacultyMenuRows() + ); + messageService.sendInteractiveMessage(phoneNumber, interactiveList); + } + + private java.util.List> getFacultyMenuRows() { + java.util.List> rows = new java.util.ArrayList<>(); + rows.add(createRow("faculty_timetable", "šŸ“… View Timetable", "Check your daily schedule")); + rows.add(createRow("faculty_subjects", "šŸ“š View Subjects", "Subjects you are teaching")); + rows.add(createRow("faculty_attendance", "šŸ“ Mark Attendance", "Mark student attendance")); + rows.add(createRow("faculty_logout", "🚪 Logout", "Sign out of your account")); + return rows; + } + + private java.util.Map createRow(String id, String title, String description) { + java.util.Map row = new java.util.HashMap<>(); + row.put("id", id); + row.put("title", title); + row.put("description", description); + return row; + } +} diff --git a/src/main/java/com/rit/whatsapp/handler/LoginHandler.java b/src/main/java/com/rit/whatsapp/handler/LoginHandler.java index 45b5ff3..ff4c6d8 100644 --- a/src/main/java/com/rit/whatsapp/handler/LoginHandler.java +++ b/src/main/java/com/rit/whatsapp/handler/LoginHandler.java @@ -15,15 +15,19 @@ public class LoginHandler { private final ImsApiService imsApiService; private final StudentMenuHandler studentMenuHandler; + private final FacultyMenuHandler facultyMenuHandler; + @Autowired public LoginHandler(WhatsAppMessageService messageService, SessionService sessionService, ImsApiService imsApiService, - StudentMenuHandler studentMenuHandler) { + StudentMenuHandler studentMenuHandler, + FacultyMenuHandler facultyMenuHandler) { this.messageService = messageService; this.sessionService = sessionService; this.imsApiService = imsApiService; this.studentMenuHandler = studentMenuHandler; + this.facultyMenuHandler = facultyMenuHandler; } public void startLoginFlow(String phoneNumber) { @@ -42,18 +46,22 @@ public class LoginHandler { messageService.sendMessage(phoneNumber, "Authenticating with IMS... ā³"); - String imsSessionToken = imsApiService.authenticate(imsId, passcode); + ImsApiService.AuthResult result = imsApiService.authenticate(imsId, passcode); - if (imsSessionToken != null) { + if (result != null && result.token != null) { // Authentication successful - sessionService.authenticateUser(phoneNumber, imsId, passcode, imsSessionToken); + sessionService.authenticateUser(phoneNumber, imsId, passcode, result); sessionService.clearTempLoginId(phoneNumber); sessionService.resetState(phoneNumber); // Reset the flow state messageService.sendMessage(phoneNumber, "āœ… Login Successful!"); // Show the main menu - studentMenuHandler.sendMainMenu(phoneNumber); + if (result.isFaculty) { + facultyMenuHandler.sendMainMenu(phoneNumber); + } else { + studentMenuHandler.sendMainMenu(phoneNumber); + } } else { // Authentication failed sessionService.clearTempLoginId(phoneNumber); diff --git a/src/main/java/com/rit/whatsapp/model/UserEntity.java b/src/main/java/com/rit/whatsapp/model/UserEntity.java index fd1659a..ee17afa 100644 --- a/src/main/java/com/rit/whatsapp/model/UserEntity.java +++ b/src/main/java/com/rit/whatsapp/model/UserEntity.java @@ -1,24 +1,38 @@ package com.rit.whatsapp.model; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.Table; @Entity -@Table(name = "users") +@Table(name = "students") public class UserEntity { @Id - private String phoneNumber; - + @Column(name = "roll_no") private String imsId; + @Column(name = "mobile_no") + private String phoneNumber; + + @Column(name = "encrypted_passcode") private String encryptedPasscode; + @Column(name = "is_faculty", columnDefinition = "boolean default false") + private Boolean isFaculty = false; + public UserEntity() { } - public UserEntity(String phoneNumber, String imsId, String encryptedPasscode) { + public UserEntity(String imsId, String phoneNumber, String encryptedPasscode) { + this.imsId = imsId; + this.phoneNumber = phoneNumber; + this.encryptedPasscode = encryptedPasscode; + } + + // Keep secondary constructor for backward compatibility + public UserEntity(String phoneNumber, String imsId, String encryptedPasscode, boolean legacyOrder) { this.phoneNumber = phoneNumber; this.imsId = imsId; this.encryptedPasscode = encryptedPasscode; @@ -47,4 +61,12 @@ public class UserEntity { public void setEncryptedPasscode(String encryptedPasscode) { this.encryptedPasscode = encryptedPasscode; } + + public boolean isFaculty() { + return isFaculty != null && isFaculty; + } + + public void setFaculty(Boolean faculty) { + isFaculty = faculty; + } } diff --git a/src/main/java/com/rit/whatsapp/model/UserState.java b/src/main/java/com/rit/whatsapp/model/UserState.java index 8fba579..dc1797a 100644 --- a/src/main/java/com/rit/whatsapp/model/UserState.java +++ b/src/main/java/com/rit/whatsapp/model/UserState.java @@ -12,5 +12,6 @@ public enum UserState { AWAITING_GRIEVANCE_DESC, AWAITING_GRIEVANCE_IMAGE, AWAITING_GRIEVANCE_STATUS_SELECTION, - AWAITING_GRIEVANCE_DELETE_SELECTION + AWAITING_GRIEVANCE_DELETE_SELECTION, + AWAITING_ABSENTEES } diff --git a/src/main/java/com/rit/whatsapp/repository/UserRepository.java b/src/main/java/com/rit/whatsapp/repository/UserRepository.java index eb5eeaa..dd09f16 100644 --- a/src/main/java/com/rit/whatsapp/repository/UserRepository.java +++ b/src/main/java/com/rit/whatsapp/repository/UserRepository.java @@ -3,7 +3,10 @@ package com.rit.whatsapp.repository; import com.rit.whatsapp.model.UserEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; @Repository public interface UserRepository extends JpaRepository { + Optional findByPhoneNumber(String phoneNumber); + boolean existsByPhoneNumber(String phoneNumber); } diff --git a/src/main/java/com/rit/whatsapp/service/ImsApiService.java b/src/main/java/com/rit/whatsapp/service/ImsApiService.java index 7dd9ce9..68eb319 100644 --- a/src/main/java/com/rit/whatsapp/service/ImsApiService.java +++ b/src/main/java/com/rit/whatsapp/service/ImsApiService.java @@ -24,13 +24,22 @@ public class ImsApiService { this.objectMapper = new ObjectMapper(); } + public static class AuthResult { + public final String token; + public final boolean isFaculty; + public AuthResult(String token, boolean isFaculty) { + this.token = token; + this.isFaculty = isFaculty; + } + } + /** * Authenticates the user against the real IMS backend. * @param imsId The user ID * @param passcode The passcode - * @return The API session token if successful, null otherwise. + * @return AuthResult if successful, null otherwise. */ - public String authenticate(String imsId, String passcode) { + public AuthResult authenticate(String imsId, String passcode) { String url = BASE_URL + "/api/auth/login"; HttpHeaders headers = new HttpHeaders(); @@ -48,7 +57,12 @@ public class ImsApiService { JsonNode rootNode = objectMapper.readTree(response.getBody()); if (rootNode.has("success") && rootNode.get("success").asBoolean()) { if (rootNode.has("session")) { - return rootNode.get("session").asText(); + String token = rootNode.get("session").asText(); + boolean isFaculty = false; + if (rootNode.has("role")) { + isFaculty = "staff".equals(rootNode.get("role").asText()); + } + return new AuthResult(token, isFaculty); } } } @@ -59,7 +73,7 @@ public class ImsApiService { return null; } - private String fetchFromIms(String endpoint, String token) { + public String fetchFromIms(String endpoint, String token) { String url = BASE_URL + endpoint; HttpHeaders headers = new HttpHeaders(); headers.setBearerAuth(token); @@ -250,4 +264,65 @@ public class ImsApiService { } return null; } + + public String getFacultyProfile(String token) { + return fetchFromIms("/api/faculty/profile", token); + } + + public String getFacultyAttendanceSubjects(String token) { + return fetchFromIms("/api/faculty/attendance-subjects", token); + } + + public String getFacultyName(String token) { + String rawJson = getFacultyProfile(token); + if (rawJson == null) return "Faculty"; + try { + JsonNode root = objectMapper.readTree(rawJson); + if (root.has("data")) { + JsonNode data = root.get("data"); + if (data.has("name")) return data.get("name").asText(); + } + } catch (Exception e) { + System.err.println("Failed to parse faculty profile JSON: " + e.getMessage()); + } + return "Faculty"; + } + + public String getStudentListForAttendance(String token, Map params) { + String url = BASE_URL + "/api/faculty/attendance/list"; + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(token); + headers.setContentType(MediaType.APPLICATION_JSON); + + HttpEntity> request = new HttpEntity<>(params, headers); + try { + ResponseEntity response = restTemplate.postForEntity(url, request, String.class); + return response.getBody(); + } catch (org.springframework.web.client.HttpStatusCodeException e) { + System.err.println("Failed to fetch student list (HTTP error): " + e.getResponseBodyAsString()); + return e.getResponseBodyAsString(); + } catch (Exception e) { + System.err.println("Failed to fetch student list: " + e.getMessage()); + return "{\"success\":false,\"message\":\"" + e.getMessage() + "\"}"; + } + } + + public String submitAttendance(String token, Map postData) { + String url = BASE_URL + "/api/faculty/attendance/take_periods"; + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(token); + headers.setContentType(MediaType.APPLICATION_JSON); + + HttpEntity> request = new HttpEntity<>(postData, headers); + try { + ResponseEntity response = restTemplate.postForEntity(url, request, String.class); + return response.getBody(); + } catch (org.springframework.web.client.HttpStatusCodeException e) { + System.err.println("Failed to submit attendance (HTTP error): " + e.getResponseBodyAsString()); + return e.getResponseBodyAsString(); + } catch (Exception e) { + System.err.println("Failed to submit attendance: " + e.getMessage()); + return "{\"success\":false,\"message\":\"" + e.getMessage() + "\"}"; + } + } } diff --git a/src/main/java/com/rit/whatsapp/service/SessionService.java b/src/main/java/com/rit/whatsapp/service/SessionService.java index b297004..1697247 100644 --- a/src/main/java/com/rit/whatsapp/service/SessionService.java +++ b/src/main/java/com/rit/whatsapp/service/SessionService.java @@ -8,6 +8,7 @@ import com.rit.whatsapp.util.EncryptionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; @@ -48,7 +49,7 @@ public class SessionService { } public boolean hasSavedCredentials(String phoneNumber) { - return userRepository.existsById(phoneNumber); + return userRepository.existsByPhoneNumber(phoneNumber); } public void resetState(String phoneNumber) { @@ -61,15 +62,22 @@ public class SessionService { } // Check database for persistent session - Optional userOpt = userRepository.findById(phoneNumber); + Optional userOpt = userRepository.findByPhoneNumber(phoneNumber); if (userOpt.isPresent()) { UserEntity user = userOpt.get(); + if (user.getEncryptedPasscode() == null) { + return false; + } String decryptedPasscode = encryptionUtils.decrypt(user.getEncryptedPasscode()); // Silent background login - String token = imsApiService.authenticate(user.getImsId(), decryptedPasscode); - if (token != null) { - sessionTokens.put(phoneNumber, token); + ImsApiService.AuthResult result = imsApiService.authenticate(user.getImsId(), decryptedPasscode); + if (result != null) { + sessionTokens.put(phoneNumber, result.token); + if (user.isFaculty() != result.isFaculty) { + user.setFaculty(result.isFaculty); + userRepository.save(user); + } return true; } } @@ -77,12 +85,21 @@ public class SessionService { return false; } - public void authenticateUser(String phoneNumber, String imsId, String passcode, String token) { - sessionTokens.put(phoneNumber, token); + public void authenticateUser(String phoneNumber, String imsId, String passcode, ImsApiService.AuthResult result) { + sessionTokens.put(phoneNumber, result.token); - // Persist to database - String encryptedPasscode = encryptionUtils.encrypt(passcode); - UserEntity user = new UserEntity(phoneNumber, imsId, encryptedPasscode); + // Persist to database (students table) + Optional userOpt = userRepository.findById(imsId); + UserEntity user; + if (userOpt.isPresent()) { + user = userOpt.get(); + user.setPhoneNumber(phoneNumber); + user.setEncryptedPasscode(encryptionUtils.encrypt(passcode)); + } else { + String encryptedPasscode = encryptionUtils.encrypt(passcode); + user = new UserEntity(imsId, phoneNumber, encryptedPasscode); + } + user.setFaculty(result.isFaculty); userRepository.save(user); } @@ -99,7 +116,12 @@ public class SessionService { if (token != null) { imsApiService.logout(token); } - userRepository.deleteById(phoneNumber); + Optional userOpt = userRepository.findByPhoneNumber(phoneNumber); + if (userOpt.isPresent()) { + UserEntity user = userOpt.get(); + user.setEncryptedPasscode(null); + userRepository.save(user); + } studentNames.remove(phoneNumber); resetState(phoneNumber); } @@ -108,11 +130,53 @@ public class SessionService { if (studentNames.containsKey(phoneNumber)) { return studentNames.get(phoneNumber); } - String name = imsApiService.getStudentName(token); + String name; + if (isFaculty(phoneNumber)) { + name = imsApiService.getFacultyName(token); + } else { + name = imsApiService.getStudentName(token); + } studentNames.put(phoneNumber, name); return name; } + private final Map>> attendanceSubjectParams = new ConcurrentHashMap<>(); + private final Map> activeAttendanceParams = new ConcurrentHashMap<>(); + private final Map> activeStudentLists = new ConcurrentHashMap<>(); + + public void storeAttendanceSubjectParams(String phoneNumber, List> paramsList) { + attendanceSubjectParams.put(phoneNumber, paramsList); + } + + public Map getAttendanceSubjectParams(String phoneNumber, int index) { + List> paramsList = attendanceSubjectParams.get(phoneNumber); + if (paramsList != null && index >= 0 && index < paramsList.size()) { + return paramsList.get(index); + } + return null; + } + + public void setActiveAttendanceParams(String phoneNumber, Map params) { + activeAttendanceParams.put(phoneNumber, params); + } + + public Map getActiveAttendanceParams(String phoneNumber) { + return activeAttendanceParams.get(phoneNumber); + } + + public void clearActiveAttendanceParams(String phoneNumber) { + activeAttendanceParams.remove(phoneNumber); + activeStudentLists.remove(phoneNumber); + } + + public void storeActiveStudentList(String phoneNumber, List list) { + activeStudentLists.put(phoneNumber, list); + } + + public List getActiveStudentList(String phoneNumber) { + return activeStudentLists.get(phoneNumber); + } + private final Map> receiptIdMappings = new ConcurrentHashMap<>(); public void storeReceiptIdMappings(String phoneNumber, Map mappings) { @@ -158,10 +222,26 @@ public class SessionService { } public String getImsId(String phoneNumber) { - Optional userOpt = userRepository.findById(phoneNumber); + Optional userOpt = userRepository.findByPhoneNumber(phoneNumber); if (userOpt.isPresent()) { return userOpt.get().getImsId(); } return null; } + + public boolean isFaculty(String phoneNumber) { + Optional userOpt = userRepository.findByPhoneNumber(phoneNumber); + if (userOpt.isPresent()) { + return userOpt.get().isFaculty(); + } + return false; + } + + public void deleteUser(String phoneNumber) { + logoutUser(phoneNumber); + Optional userOpt = userRepository.findByPhoneNumber(phoneNumber); + if (userOpt.isPresent()) { + userRepository.delete(userOpt.get()); + } + } } diff --git a/src/main/java/com/rit/whatsapp/util/MessageFormatter.java b/src/main/java/com/rit/whatsapp/util/MessageFormatter.java index d77318d..43f67b4 100644 --- a/src/main/java/com/rit/whatsapp/util/MessageFormatter.java +++ b/src/main/java/com/rit/whatsapp/util/MessageFormatter.java @@ -1244,4 +1244,310 @@ public class MessageFormatter { if (text.length() <= length) return text; return text.substring(0, length); } + + public static String formatFacultyTimetable(String rawJson) { + if (rawJson == null) return "āŒ Failed to retrieve timetable data."; + + try { + JsonNode root = mapper.readTree(rawJson); + if (!root.has("data") || !root.get("data").isObject() || !root.get("data").has("schedule")) { + return "āŒ Timetable details not available in this format."; + } + + JsonNode schedule = root.get("data").get("schedule"); + String day = java.time.LocalDate.now().getDayOfWeek().name().toLowerCase(); + + StringBuilder sb = new StringBuilder(); + sb.append("šŸ“… *Today's Classes (Faculty)*\n"); + sb.append("━━━━━━━━━━━━━━\n\n"); + + if (schedule.has(day)) { + JsonNode daySchedule = schedule.get(day); + + int maxPeriod = 7; + java.util.Iterator fieldNames = daySchedule.fieldNames(); + while (fieldNames.hasNext()) { + String fName = fieldNames.next(); + try { + int p = Integer.parseInt(fName); + if (p > maxPeriod) maxPeriod = p; + } catch (NumberFormatException e) {} + } + + for (int period = 1; period <= maxPeriod; period++) { + String periodKey = String.valueOf(period); + sb.append(getNumberEmoji(period)).append(" "); + + if (daySchedule.has(periodKey)) { + JsonNode periodNode = daySchedule.get(periodKey); + java.util.List entries = new java.util.ArrayList<>(); + + if (periodNode.isArray()) { + for (JsonNode node : periodNode) { + String sName = node.has("subjectName") ? node.get("subjectName").asText() : ""; + String sCode = node.has("subjectCode") ? node.get("subjectCode").asText() : ""; + String cName = node.has("className") ? node.get("className").asText() : ""; + entries.add("*" + sName + "*\n `" + sCode + "` - " + cName); + } + } + + if (!entries.isEmpty()) { + sb.append(String.join(" / ", entries)).append("\n\n"); + } else { + sb.append("*Free Period*\n\n"); + } + } else { + sb.append("*Free Period*\n\n"); + } + } + } else { + sb.append("No classes scheduled for today.\n\n"); + } + + sb.append("━━━━━━━━━━━━━━"); + return sb.toString(); + } catch (Exception e) { + System.err.println("Error formatting faculty timetable: " + e.getMessage()); + return "āš ļø Error formatting timetable data."; + } + } + + public static String formatFacultySubjects(String rawJson) { + if (rawJson == null) return "āŒ Failed to retrieve subjects data."; + + try { + JsonNode root = mapper.readTree(rawJson); + if (!root.has("data") || !root.get("data").isObject() || !root.get("data").has("subjects")) { + return "āŒ Subjects details not available in this format."; + } + + JsonNode subjects = root.get("data").get("subjects"); + + StringBuilder sb = new StringBuilder(); + sb.append("šŸ“š *Your Subjects*\n"); + sb.append("━━━━━━━━━━━━━━\n\n"); + + if (subjects.isArray() && subjects.size() > 0) { + for (JsonNode node : subjects) { + String sName = node.has("subjectName") ? node.get("subjectName").asText() : ""; + String sCode = node.has("subjectCode") ? node.get("subjectCode").asText() : ""; + String cName = node.has("className") ? node.get("className").asText() : ""; + + sb.append("šŸ”¹ *").append(sName).append("*\n"); + sb.append(" Code: `").append(sCode).append("`\n"); + sb.append(" Class: ").append(cName).append("\n\n"); + } + } else { + sb.append("No subjects assigned currently.\n\n"); + } + + sb.append("━━━━━━━━━━━━━━"); + return sb.toString(); + } catch (Exception e) { + System.err.println("Error formatting faculty subjects: " + e.getMessage()); + return "āš ļø Error formatting subjects data."; + } + } + + public static String formatFacultyAttendanceSubjects(String rawJson) { + if (rawJson == null) return "āŒ Failed to retrieve attendance subjects data."; + + try { + JsonNode root = mapper.readTree(rawJson); + if (!root.has("data") || !root.get("data").isObject()) { + return "āŒ Attendance details not available in this format."; + } + + JsonNode data = root.get("data"); + + StringBuilder sb = new StringBuilder(); + sb.append("šŸ“ *Attendance Pending*\n"); + sb.append("━━━━━━━━━━━━━━\n\n"); + + boolean found = false; + + if (data.has("regularSubjects")) { + JsonNode regular = data.get("regularSubjects"); + if (regular.isArray() && regular.size() > 0) { + sb.append("*Regular Subjects:*\n"); + for (JsonNode node : regular) { + String sName = node.has("subject") ? node.get("subject").asText() : ""; + String pName = node.has("allotedPeriod") ? node.get("allotedPeriod").asText() : ""; + String cName = node.has("className") ? node.get("className").asText() : ""; + + sb.append("šŸ”¹ Period ").append(pName).append(": *").append(sName).append("*\n"); + sb.append(" Class: ").append(cName).append("\n\n"); + found = true; + } + } + } + + if (!found) { + sb.append("No attendance pending to mark today.\n\n"); + } + + sb.append("━━━━━━━━━━━━━━"); + return sb.toString(); + } catch (Exception e) { + System.err.println("Error formatting faculty attendance subjects: " + e.getMessage()); + return "āš ļø Error formatting attendance subjects data."; + } + } + + public static class StudentAttendanceInfo { + public String id; + public String regNo; + public String name; + public StudentAttendanceInfo(String id, String regNo, String name) { + this.id = id; + this.regNo = regNo; + this.name = name; + } + } + + public static java.util.List parseStudentList(String html) { + java.util.List list = new java.util.ArrayList<>(); + String cleanHtml = html.replaceAll("\\s+", " "); + java.util.regex.Matcher rowMatcher = java.util.regex.Pattern.compile("]*>(.*?)").matcher(cleanHtml); + int serial = 1; + while (rowMatcher.find()) { + String rowContent = rowMatcher.group(1); + java.util.regex.Matcher idMatcher = java.util.regex.Pattern.compile("]+value=\"(\\d+)\"[^>]*name=\"student_id\\[\\]\"").matcher(rowContent); + if (!idMatcher.find()) { + idMatcher = java.util.regex.Pattern.compile("]+name=\"student_id\\[\\]\"[^>]+value=\"(\\d+)\"").matcher(rowContent); + } + if (idMatcher.find()) { + String id = idMatcher.group(1); + java.util.List cells = new java.util.ArrayList<>(); + java.util.regex.Matcher cellMatcher = java.util.regex.Pattern.compile("]*>(.*?)").matcher(rowContent); + while (cellMatcher.find()) { + cells.add(cellMatcher.group(1).replaceAll("<[^>]*>", "").trim()); + } + String regNo = cells.size() > 1 ? cells.get(1) : ""; + String name = cells.size() > 2 ? cells.get(2) : ""; + if (regNo.isEmpty()) { + regNo = "Roll " + serial; + } + if (name.isEmpty()) { + name = "Student " + serial; + } + list.add(new StudentAttendanceInfo(id, regNo, name)); + serial++; + } + } + return list; + } + + public static java.util.List parseStudents(String rawJsonOrHtml) { + java.util.List list = new java.util.ArrayList<>(); + if (rawJsonOrHtml == null) return list; + try { + JsonNode root = mapper.readTree(rawJsonOrHtml); + JsonNode dataNode = root.has("data") ? root.get("data") : root; + + if (dataNode.isArray()) { + for (JsonNode student : dataNode) { + String id = student.has("id") ? student.get("id").asText() : ""; + String name = student.has("name") ? student.get("name").asText() : + (student.has("student_name") ? student.get("student_name").asText() : ""); + String regNo = student.has("roll_no") ? student.get("roll_no").asText() : + (student.has("register_number") ? student.get("register_number").asText() : + (student.has("reg_no") ? student.get("reg_no").asText() : "")); + if (!id.isEmpty()) { + list.add(new StudentAttendanceInfo(id, regNo, name)); + } + } + return list; + } else if (dataNode.isTextual()) { + return parseStudentList(dataNode.asText()); + } + } catch (Exception e) { + return parseStudentList(rawJsonOrHtml); + } + return list; + } + + public static String formatFacultyWeeklyTimetable(String rawJson) { + if (rawJson == null) return "āŒ Failed to retrieve weekly timetable data."; + + try { + JsonNode root = mapper.readTree(rawJson); + if (!root.has("data") || !root.get("data").isObject() || !root.get("data").has("schedule")) { + return "āŒ Weekly timetable details not available in the response."; + } + + JsonNode schedule = root.get("data").get("schedule"); + StringBuilder sb = new StringBuilder(); + sb.append("šŸ“… *Weekly Timetable (Faculty)*\n\n"); + + String[] days = {"monday", "tuesday", "wednesday", "thursday", "friday", "saturday"}; + String[] dayLabels = {"MON", "TUE", "WED", "THU", "FRI", "SAT"}; + boolean hasAnyClasses = false; + + for (int i = 0; i < days.length; i++) { + String day = days[i]; + String label = dayLabels[i]; + + if (schedule.has(day)) { + JsonNode daySchedule = schedule.get(day); + + StringBuilder daySb = new StringBuilder(); + daySb.append("šŸ“… *").append(label).append("*\n"); + boolean dayHasClasses = false; + + int maxPeriod = 7; + java.util.Iterator fieldNames = daySchedule.fieldNames(); + while (fieldNames.hasNext()) { + try { + int p = Integer.parseInt(fieldNames.next()); + if (p > maxPeriod) { + maxPeriod = p; + } + } catch (NumberFormatException e) {} + } + + for (int period = 1; period <= maxPeriod; period++) { + String periodKey = String.valueOf(period); + + if (daySchedule.has(periodKey)) { + JsonNode details = daySchedule.get(periodKey); + java.util.Set subjects = new java.util.LinkedHashSet<>(); + + if (details.isArray()) { + for (JsonNode entry : details) { + String name = entry.has("subjectName") ? entry.get("subjectName").asText() : ""; + String code = entry.has("subjectCode") ? entry.get("subjectCode").asText() : ""; + String cName = entry.has("className") ? entry.get("className").asText() : ""; + if (!name.trim().isEmpty()) { + String entryStr = name.trim() + " (" + code.trim() + ")"; + if (!cName.isEmpty()) { + entryStr += " - " + cName.trim(); + } + subjects.add(entryStr); + } + } + } + + if (!subjects.isEmpty()) { + daySb.append(" ").append(getNumberEmoji(period)).append(" ").append(String.join(" / ", subjects)).append("\n"); + dayHasClasses = true; + hasAnyClasses = true; + } + } + } + if (dayHasClasses) { + sb.append(daySb).append("\n"); + } + } + } + + if (!hasAnyClasses) { + return "šŸ“… *Weekly Timetable*\n\nNo classes scheduled for this week."; + } + + return sb.toString().trim(); + } catch (Exception e) { + return "āš ļø Error formatting weekly timetable data."; + } + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 4bb00b7..1f8f2ac 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -6,7 +6,8 @@ whatsapp.verify-token=rit_bot_secret_token_123 # WhatsApp Graph API Token (You get this from the Meta developer console) # WhatsApp Phone Number ID (You get this from the Meta developer console) -whatsapp.phone-number-id=1236658909534007 +whatsapp.phone-number-id=1239246362613929 + # PostgreSQL Database Configuration spring.datasource.url=${DB_URL} @@ -18,4 +19,4 @@ spring.jpa.hibernate.ddl-auto=update # Encryption Secret for Passcodes (Must be 16, 24, or 32 bytes) bot.encryption.secret=12345678901234567890123456789012 -whatsapp.api-token=EAAc3Wjql00oBSdZAyOjO81it8Tnpiqa1mmJq9c3WFzwXmYvjZC1RaXLlJDBe14zrCQ7DA2l5pzAV5y8sinZCBmTzlp9SUXvrcMMzmMDJ7rN58KHBqP6MAIkxcyQZCpewbFqB3mp4xwUHJ89GRZB88rsLpvJFMZABWNbfsaTZAQ0ZCabRcAvL0iJ528UGJJnv98y9F9EMerZCi3KZCGYfvIZBuYBM8tL2XWmesQir8Dlj2ZCjBmtuDLGOEQ59lHZBq1XrEbhbzlWOzH6EdUZBOajYDHn8rcZBdGZC +whatsapp.api-token=EAAc3Wjql00oBSTlrhFqV7uer16ZAEy5B0c4GXZAForXZCTGp8rUcpyL5yAbQvOFjC2T0ZAgUmTFSMDT00SGbaaQVQLlYqZBOmkd91tqVCSSTGJZCCzBB2pPEHr8ckxTZCSe0srBZAGl7IHX8ucODcwuzQm8TZBy2RrnGG4gAUZB9a0HHO4uFYZAZCyUZAv3i65GVRfainyAZDZD