From 020c0132c9d6ed8f0eeff6371c228aa81e04f68c Mon Sep 17 00:00:00 2001 From: Dev102030-pixel Date: Mon, 17 Aug 2026 11:09:35 +0530 Subject: [PATCH] initial commit by dev prasad --- .gitignore | 4 + .vscode/settings.json | 3 + README.md | 310 +++++ fix_args.py | 63 + fix_tamil.py | 38 + length_check.py | 15 + pom.xml | 56 + replace.py | 54 + .../WhatsAppAutomationApplication.java | 12 + .../controller/WhatsAppWebhookController.java | 99 ++ .../entity/Appointment.java | 44 + .../entity/ServiceBooking.java | 44 + .../entity/TestBooking.java | 47 + .../whatsappautomation/entity/UserState.java | 144 +++ .../repository/AppointmentRepository.java | 14 + .../repository/ServiceBookingRepository.java | 9 + .../repository/TestBookingRepository.java | 9 + .../repository/UserStateRepository.java | 9 + .../service/ChatbotService.java | 1129 +++++++++++++++++ src/main/resources/application.properties | 17 + src/main/resources/messages.properties | 8 + src/main/resources/messages_en.properties | 209 +++ src/main/resources/messages_ta.properties | 209 +++ .../WhatsAppAutomationApplicationTests.java | 12 + 24 files changed, 2558 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 README.md create mode 100644 fix_args.py create mode 100644 fix_tamil.py create mode 100644 length_check.py create mode 100644 pom.xml create mode 100644 replace.py create mode 100644 src/main/java/com/example/whatsappautomation/WhatsAppAutomationApplication.java create mode 100644 src/main/java/com/example/whatsappautomation/controller/WhatsAppWebhookController.java create mode 100644 src/main/java/com/example/whatsappautomation/entity/Appointment.java create mode 100644 src/main/java/com/example/whatsappautomation/entity/ServiceBooking.java create mode 100644 src/main/java/com/example/whatsappautomation/entity/TestBooking.java create mode 100644 src/main/java/com/example/whatsappautomation/entity/UserState.java create mode 100644 src/main/java/com/example/whatsappautomation/repository/AppointmentRepository.java create mode 100644 src/main/java/com/example/whatsappautomation/repository/ServiceBookingRepository.java create mode 100644 src/main/java/com/example/whatsappautomation/repository/TestBookingRepository.java create mode 100644 src/main/java/com/example/whatsappautomation/repository/UserStateRepository.java create mode 100644 src/main/java/com/example/whatsappautomation/service/ChatbotService.java create mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/messages.properties create mode 100644 src/main/resources/messages_en.properties create mode 100644 src/main/resources/messages_ta.properties create mode 100644 src/test/java/com/example/whatsappautomation/WhatsAppAutomationApplicationTests.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a154754 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target/ +/.idea/ +*.iml +.DS_Store diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7b016a8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.compile.nullAnalysis.mode": "automatic" +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..04c8f24 --- /dev/null +++ b/README.md @@ -0,0 +1,310 @@ +# WhatsApp Automation + +WhatsApp Automation is a Spring Boot backend for WhatsApp Business messaging workflows. It receives webhook events from Meta, runs bot-driven conversation logic, and persists booking/state data in PostgreSQL. + +## About The Project + +This project provides automation features around WhatsApp messaging: + +- Webhook verification and message handling (`/webhook`) +- Chatbot conversation flow +- PostgreSQL persistence for appointments, service bookings, test bookings, and user state + +The application is a **Java 21** Maven service built with Spring Boot. + +## Tech Stack + +- Spring Boot 3.3.2 +- Java 21 +- Spring Web +- Spring Data JPA +- PostgreSQL +- ngrok (for local webhook tunneling to Meta) + +## Requirements + +| Tool | Purpose | +|------|---------| +| **Java 21** | Runtime for the Spring Boot app | +| **Maven 3.9+** | Build and run the project | +| **PostgreSQL** | Application database | +| **ngrok** | Expose local backend to the public internet so Meta can call your webhook | +| **Meta / WhatsApp Business API access** | Access token, webhook configuration, and test messaging | + +--- + +## How to Run the Backend (Full Setup) + +Follow these steps in order. + +### 1. Clone the repository + +```bash +git clone +cd "WhatsApp Automation" +``` + +Replace `` with the actual Git remote URL for this project. + +--- + +### 2. Install ngrok + +ngrok creates a public HTTPS URL that tunnels to your local Spring Boot server so Meta can deliver WhatsApp webhooks. + +1. Go to [https://ngrok.com](https://ngrok.com) +2. Sign up or sign in +3. Install ngrok for your OS (macOS example with Homebrew): + +```bash +brew install ngrok +``` + +Or download the binary from the ngrok dashboard and follow their install instructions. + +--- + +### 3. Authenticate ngrok with your access token + +After signing in to ngrok, copy your **authtoken** from the ngrok dashboard, then run: + +```bash +ngrok config add-authtoken +``` + +Without this step, ngrok will not start a usable tunnel. + +--- + +### 4. Log in to the WhatsApp Business / Meta developer dashboard + +1. Open the [WhatsApp Business API / Meta for Developers](https://developers.facebook.com/) dashboard +2. Log in with the account that has access to this app +3. Use the project’s WhatsApp / Meta credentials (app ID, business ID, phone number ID, etc.) as configured for your team + +Relevant app links used by this project: + +- **API testing / access token:** + [Generate WhatsApp access token](https://developers.facebook.com/apps/1054675710401577/use_cases/customize/api-testing-v2/?product_route=whatsapp-business&business_id=1051413880774512&use_case_enum=WHATSAPP_BUSINESS_MESSAGING&selected_tab=api-testing-v2) + +- **Webhooks configuration:** + [Configure webhooks](https://developers.facebook.com/apps/1054675710401577/use_cases/customize/webhooks/?product_route=webhooks&business_id=1051413880774512&use_case_enum=WHATSAPP_BUSINESS_MESSAGING&selected_tab=webhooks) + +--- + +### 5. Generate a WhatsApp access token and configure the app + +1. Open the API testing page: + [https://developers.facebook.com/apps/1054675710401577/use_cases/customize/api-testing-v2/?product_route=whatsapp-business&business_id=1051413880774512&use_case_enum=WHATSAPP_BUSINESS_MESSAGING&selected_tab=api-testing-v2](https://developers.facebook.com/apps/1054675710401577/use_cases/customize/api-testing-v2/?product_route=whatsapp-business&business_id=1051413880774512&use_case_enum=WHATSAPP_BUSINESS_MESSAGING&selected_tab=api-testing-v2) +2. Generate a **new Access Token** +3. Open the project config file: + +```text +src/main/resources/application.properties +``` + +4. Paste the token into the access-token property: + +```properties +whatsapp.access-token= +``` + +> **Note:** Temporary tokens from Meta expire. If outbound WhatsApp API calls start failing with auth errors, generate a new token and update this property. + +--- + +### 6. Create the PostgreSQL database and set credentials + +1. Ensure PostgreSQL is installed and running locally +2. Create the database used by this project (default name: `rmc_bot`): + +```bash +createdb rmc_bot +``` + +Or via `psql`: + +```sql +CREATE DATABASE rmc_bot; +``` + +3. Update database settings in `src/main/resources/application.properties`: + +```properties +spring.datasource.url=jdbc:postgresql://localhost:5432/rmc_bot +spring.datasource.username= +spring.datasource.password= +``` + +Default values currently in the file (change these for your machine): + +| Property | Default / example | +|----------|-------------------| +| `spring.datasource.url` | `jdbc:postgresql://localhost:5432/rmc_bot` | +| `spring.datasource.username` | `postgres` | +| `spring.datasource.password` | *(your local password)* | + +The app uses: + +```properties +spring.jpa.hibernate.ddl-auto=update +``` + +so tables are created/updated automatically on startup. + +--- + +### 7. Start the Spring Boot backend + +The server listens on **port 8085** (`server.port=8085`). + +From the project root: + +```bash +mvn spring-boot:run +``` + +Confirm the app started successfully (no datasource or port-binding errors). Leave this process running. + +You can also run tests with: + +```bash +mvn test +``` + +--- + +### 8. Start ngrok and point it at the backend + +**Important:** Start the Spring Boot backend *before* configuring Meta webhooks, and keep both the backend and ngrok running while testing. + +In a **new terminal**: + +```bash +ngrok http 8085 +``` + +ngrok will print a public HTTPS URL, for example: + +```text +https://abcd-1234.ngrok-free.app +``` + +Copy that base URL (the `https://...` forwarding address). + +> Free ngrok URLs change every time you restart ngrok. When the URL changes, you must update the Callback URL in Meta again. + +--- + +### 9. Configure the Meta webhook + +1. Open the webhooks page: + [https://developers.facebook.com/apps/1054675710401577/use_cases/customize/webhooks/?product_route=webhooks&business_id=1051413880774512&use_case_enum=WHATSAPP_BUSINESS_MESSAGING&selected_tab=webhooks](https://developers.facebook.com/apps/1054675710401577/use_cases/customize/webhooks/?product_route=webhooks&business_id=1051413880774512&use_case_enum=WHATSAPP_BUSINESS_MESSAGING&selected_tab=webhooks) + +2. Set **Callback URL** to: + +```text +https:///webhook +``` + +Example: + +```text +https://abcd-1234.ngrok-free.app/webhook +``` + +3. Set **Verify Token** to exactly: + +```text +my_whatsapp_verify_token_123 +``` + +This must match `whatsapp.verify-token` in `application.properties`: + +```properties +whatsapp.verify-token=my_whatsapp_verify_token_123 +``` + +4. Click **Verify and Save** (or equivalent). Meta will send a `GET` request to `/webhook` with `hub.mode`, `hub.verify_token`, and `hub.challenge`. The backend validates the token and returns the challenge. + +5. Subscribe to the webhook fields you need (typically **messages** for inbound WhatsApp traffic). + +--- + +## Configuration reference + +All runtime secrets and connection settings live in: + +```text +src/main/resources/application.properties +``` + +| Property | Description | +|----------|-------------| +| `server.port` | Backend HTTP port (default `8085`) | +| `whatsapp.access-token` | Meta WhatsApp Cloud API access token | +| `whatsapp.verify-token` | Token Meta uses to verify your webhook (`my_whatsapp_verify_token_123`) | +| `spring.datasource.url` | JDBC URL for PostgreSQL | +| `spring.datasource.username` | PostgreSQL username | +| `spring.datasource.password` | PostgreSQL password | +| `spring.jpa.hibernate.ddl-auto` | Schema mode (`update`) | +| `spring.jpa.properties.hibernate.dialect` | PostgreSQL dialect | +| `spring.jpa.show-sql` | Log SQL statements | + +Webhook endpoint exposed by the app: + +| Method | Path | Purpose | +|--------|------|---------| +| `GET` | `/webhook` | Meta webhook verification | +| `POST` | `/webhook` | Inbound WhatsApp events / messages | + +--- + +## Quick checklist + +Use this when bringing the stack up on a new machine: + +1. [ ] Clone the repo +2. [ ] Install and authenticate ngrok +3. [ ] Log in to Meta / WhatsApp Business dashboard +4. [ ] Generate access token → set `whatsapp.access-token` +5. [ ] Create PostgreSQL DB → set datasource username/password +6. [ ] `mvn spring-boot:run` (port **8085**) +7. [ ] `ngrok http 8085` +8. [ ] Meta Callback URL = `https:///webhook` +9. [ ] Verify token = `my_whatsapp_verify_token_123` +10. [ ] Verify webhook + subscribe to message events + +--- + +## Troubleshooting + +| Problem | What to check | +|---------|----------------| +| Webhook verification fails | Verify token must be exactly `my_whatsapp_verify_token_123`. Backend must be running. Callback URL must end with `/webhook`. | +| Meta cannot reach the server | ngrok must be running (`ngrok http 8085`). Use the current ngrok HTTPS URL, not an old one. | +| Database connection errors | PostgreSQL is running; database `rmc_bot` exists; username/password in `application.properties` are correct. | +| Cannot send WhatsApp messages | Access token may have expired — generate a new one and update `whatsapp.access-token`. | +| Port already in use | Something else is bound to `8085`, or change `server.port` and point ngrok at the new port. | +| Not receiving messages on webhook | Your WhatsApp Business Account (WABA) might not be subscribed to your developer App. Link them via API: `curl -X POST "https://graph.facebook.com/v20.0//subscribed_apps" -H "Authorization: Bearer "` | + +--- + +## Project structure (high level) + +```text +src/main/java/com/example/whatsappautomation/ + WhatsAppAutomationApplication.java + controller/WhatsAppWebhookController.java + service/ChatbotService.java + entity/ # Appointment, ServiceBooking, TestBooking, UserState + repository/ # Spring Data JPA repositories +src/main/resources/ + application.properties +``` + +--- + +## License + +Internal / project use unless otherwise specified. diff --git a/fix_args.py b/fix_args.py new file mode 100644 index 0000000..a675111 --- /dev/null +++ b/fix_args.py @@ -0,0 +1,63 @@ +import re + +with open('src/main/java/com/example/whatsappautomation/service/ChatbotService.java', 'r') as f: + content = f.read() + +# We want to match: sendPaginatedButtons(state, arg2, arg3, new String[]{...}, new String[]{...}) +# and replace it with: sendPaginatedButtons(state, arg2, new String[]{...}, new String[]{...}) +# Note: arg2 and arg3 might be getMessage(...) or "string". +# It's safest to match: sendPaginatedButtons(state, (whatever), (whatever), new String[]{ + +def replacer(match): + # match.group(1) is the part from `state, ` up to `, new String[]{` + # this part contains `arg2, arg3`. We need to split by comma from the right, but arg2 or arg3 might have commas! + # Wait, getting the last argument before `new String[]{` is the best way. + # Actually, the button text (arg3) is almost always `getMessage("something.btn", state)` or `"View Options"` or similar. + # Let's just use regex to match the third argument which is always followed by `, new String\[\]\{` + pass + +# Let's write a simple python parser for the arguments of sendPaginatedButtons +new_content = "" +idx = 0 +while True: + start_idx = content.find('sendPaginatedButtons(state, ', idx) + if start_idx == -1: + new_content += content[idx:] + break + + new_content += content[idx:start_idx] + + # find the start of `new String[]{` + arrays_start = content.find('new String[]{', start_idx) + + # The text between `start_idx + len('sendPaginatedButtons(state, ')` and `arrays_start` is `arg2, arg3, ` + # We want to remove `arg3, `. + # `arg3` is either a string literal `"..."` or a `getMessage(...)` call. + args_str = content[start_idx + len('sendPaginatedButtons(state, '):arrays_start].strip() + # args_str ends with `,`. Let's remove the trailing comma. + args_str = args_str[:-1].strip() + + # Now we need to split args_str into arg2 and arg3. + # If it ends with `state)`, arg3 is `getMessage(..., state)`. + # If it ends with `"`, arg3 is a string literal. + + # Actually, we can just find the LAST comma that is NOT inside parentheses! + # A simple comma split works if we respect parens. + def rsplit_args(s): + depth = 0 + for i in range(len(s)-1, -1, -1): + if s[i] == ')': depth += 1 + elif s[i] == '(': depth -= 1 + elif s[i] == ',' and depth == 0: + return s[:i].strip(), s[i+1:].strip() + return s, "" + + arg2, arg3 = rsplit_args(args_str) + + new_content += f"sendPaginatedButtons(state, {arg2}, " + idx = arrays_start + +with open('src/main/java/com/example/whatsappautomation/service/ChatbotService.java', 'w') as f: + f.write(new_content) + +print("Fixed arguments") diff --git a/fix_tamil.py b/fix_tamil.py new file mode 100644 index 0000000..c85164c --- /dev/null +++ b/fix_tamil.py @@ -0,0 +1,38 @@ +replacements = { + "காய்ச்சல் பரிசோதனைகள்": "காய்ச்சல் பரிசோதனை", + "மூட்டு மற்றும் எலும்பு": "மூட்டு & எலும்பு", + "முழுமையான உடல் பரிசோதனை": "முழு உடல் பரிசோதனை", + "காய்ச்சல் அல்ட்ரா பேனல்": "காய்ச்சல் அல்ட்ரா", + "மலம் பரிசோதனை (Stool Occult Blood)": "மலம் பரிசோதனை", + "அமயா இன்ட் கார்டியாக்": "அமயா கார்டியாக்", + "கார்டியாக் பேக்கேஜ் - அமயா": "அமயா பேக்கேஜ்", + "உயர் இரத்த அழுத்தம் தொகுப்பு": "உயர் ரத்த அழுத்தம்", + "மாஸ்டர் ஹெல்த் பேசிக்": "மாஸ்டர் பேசிக்", + "மாஸ்டர் ஹெல்த் எக்ஸிகியூட்டிவ்": "மாஸ்டர் எக்ஸிகியூட்", + "மாஸ்டர் ஹெல்த் பிரீமியம்": "மாஸ்டர் பிரீமியம்", + "கர்ப்ப காலம் அட்வான்ஸ்டு": "கர்ப்ப அட்வான்ஸ்டு", + "சர்க்கரை கட்டுப்பாடு பேசிக்": "சர்க்கரை பேசிக்", + "சர்க்கரை கட்டுப்பாடு பிளஸ்": "சர்க்கரை பிளஸ்", + "பெண்கள் நல்வாழ்வு பேசிக்": "பெண்கள் பேசிக்", + "பெண்கள் நல்வாழ்வு அட்வான்ஸ்டு": "பெண்கள் அட்வான்ஸ்டு", + "புதிய சந்திப்பை முன்பதிவு செய்ய": "புதிய முன்பதிவு", + "சந்திப்புகளைப் பார்க்க": "சந்திப்புகளை பார்", + "சந்திப்பை மாற்றியமைக்க": "மாற்றியமைக்க", + "சந்திப்பை ரத்து செய்ய": "ரத்து செய்ய", + "முன்பதிவை உறுதிப்படுத்தவும்": "உறுதிப்படுத்துக", + "சந்திப்பை ரத்து செய்யவும்": "ரத்து செய்க", + "கட்டு போடுதல் (Dressing)": "கட்டு போடுதல்", + "வடிகுழாய் (Catheterization)": "வடிகுழாய்", + "வீட்டு பராமரிப்பு தொகுப்புகள்": "வீட்டு பராமரிப்பு" +} + +with open('src/main/resources/messages_ta.properties', 'r', encoding='utf-8') as f: + content = f.read() + +for old, new in replacements.items(): + content = content.replace(f"={old}", f"={new}") + +with open('src/main/resources/messages_ta.properties', 'w', encoding='utf-8') as f: + f.write(content) + +print("Replaced lengths.") diff --git a/length_check.py b/length_check.py new file mode 100644 index 0000000..57dbfda --- /dev/null +++ b/length_check.py @@ -0,0 +1,15 @@ +import re + +# Keys that are used for buttons (options) +button_keys = ['opt', 'btn', 'yes', 'no', 'confirm', 'change', 'cancel'] + +with open('src/main/resources/messages_ta.properties', 'r', encoding='utf-8') as f: + for line in f: + line = line.strip() + if not line or line.startswith('#'): + continue + if '=' in line: + key, val = line.split('=', 1) + is_button = any(bk in key for bk in button_keys) + if is_button and len(val) > 20: + print(f"{key}={val} ({len(val)} chars)") diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..64ef23d --- /dev/null +++ b/pom.xml @@ -0,0 +1,56 @@ + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.3.2 + + + + com.example + whatsapp-automation + 0.0.1-SNAPSHOT + WhatsApp Automation + Spring Boot application for WhatsApp automation + + + 21 + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.postgresql + postgresql + runtime + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/replace.py b/replace.py new file mode 100644 index 0000000..6441269 --- /dev/null +++ b/replace.py @@ -0,0 +1,54 @@ +import re + +with open('src/main/java/com/example/whatsappautomation/service/ChatbotService.java', 'r') as f: + content = f.read() + +# Pattern to match sendInteractiveList(...) up to the null); +# We need to capture bodyText (arg 1), ids (arg 4), titles (arg 5). +# Using a python script to parse the balanced parentheses might be better. + +def replace_calls(text): + start = 0 + while True: + idx = text.find('sendInteractiveList(', start) + if idx == -1: + break + + # find the end of this statement + end_idx = text.find(');', idx) + if end_idx == -1: + break + + call_str = text[idx:end_idx+2] + + # We need to extract the parts. It's safer to just split by "new String[]{" + # because we know args 4 and 5 are array allocations. + + parts = call_str.split('new String[]{') + if len(parts) == 3: + # parts[0] has 'sendInteractiveList(bodyText, buttonText, sectionTitle, \n' + # parts[1] has 'ids...}, \n' + # parts[2] has 'titles...}, null);' + + # Extract bodyText from parts[0] + # sendInteractiveList(arg1, arg2, arg3, + # We want just arg1! + p0 = parts[0][len('sendInteractiveList('):].strip() + # It's tricky to parse commas. + pass + +print("Doing regex...") + +# Actually, let's just use a simple regex if they are formatted consistently. +# Almost all of them are: +# sendInteractiveList( BODY, BTN, TITLE, +# new String[]{IDS}, +# new String[]{TITLES}, null); + +pattern = r'sendInteractiveList\((.*?),\s*[^,]+,\s*[^,]+,\s*(new String\[\]\{.*?\})\s*,\s*(new String\[\]\{.*?\})\s*,\s*null\);' +replaced = re.sub(pattern, r'sendPaginatedButtons(state, \1, \2, \3);', content, flags=re.DOTALL) + +with open('src/main/java/com/example/whatsappautomation/service/ChatbotService.java', 'w') as f: + f.write(replaced) + +print("Done") diff --git a/src/main/java/com/example/whatsappautomation/WhatsAppAutomationApplication.java b/src/main/java/com/example/whatsappautomation/WhatsAppAutomationApplication.java new file mode 100644 index 0000000..3f9a9d5 --- /dev/null +++ b/src/main/java/com/example/whatsappautomation/WhatsAppAutomationApplication.java @@ -0,0 +1,12 @@ +package com.example.whatsappautomation; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class WhatsAppAutomationApplication { + + public static void main(String[] args) { + SpringApplication.run(WhatsAppAutomationApplication.class, args); + } +} diff --git a/src/main/java/com/example/whatsappautomation/controller/WhatsAppWebhookController.java b/src/main/java/com/example/whatsappautomation/controller/WhatsAppWebhookController.java new file mode 100644 index 0000000..717298c --- /dev/null +++ b/src/main/java/com/example/whatsappautomation/controller/WhatsAppWebhookController.java @@ -0,0 +1,99 @@ +package com.example.whatsappautomation.controller; + +import com.example.whatsappautomation.service.ChatbotService; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/webhook") +public class WhatsAppWebhookController { + + private static final Logger logger = LoggerFactory.getLogger(WhatsAppWebhookController.class); + + @Value("${whatsapp.verify-token}") + private String verifyToken; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + private final ChatbotService chatbotService; + + public WhatsAppWebhookController(ChatbotService chatbotService) { + this.chatbotService = chatbotService; + } + + /** + * Webhook verification endpoint for Meta/WhatsApp. + */ + @GetMapping + public ResponseEntity verifyWebhook( + @RequestParam("hub.mode") String mode, + @RequestParam("hub.challenge") String challenge, + @RequestParam("hub.verify_token") String token) { + + logger.info("Received verification request: mode={}, token={}", mode, token); + + if ("subscribe".equals(mode) && verifyToken.equals(token)) { + logger.info("Webhook verified successfully!"); + return ResponseEntity.ok(challenge); + } else { + logger.warn("Webhook verification failed. Provided token: {}", token); + return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Verification failed"); + } + } + + /** + * Webhook event handler endpoint. + */ + @PostMapping + public ResponseEntity receive(@RequestBody String body) { + System.out.println("Received:"); + System.out.println(body); + + try { + JsonNode payload = objectMapper.readTree(body); + if (payload.has("entry")) { + JsonNode entryArray = payload.get("entry"); + if (entryArray.isArray() && entryArray.size() > 0) { + JsonNode changesArray = entryArray.get(0).get("changes"); + if (changesArray != null && changesArray.isArray() && changesArray.size() > 0) { + JsonNode value = changesArray.get(0).get("value"); + if (value != null && value.has("messages")) { + JsonNode messagesArray = value.get("messages"); + if (messagesArray.isArray() && messagesArray.size() > 0) { + JsonNode message = messagesArray.get(0); + String from = message.get("from").asText(); + + // Avoid processing status changes/read receipts as incoming messages + String textBody = null; + if (message.has("text")) { + textBody = message.get("text").get("body").asText(); + } else if (message.has("interactive")) { + JsonNode interactive = message.get("interactive"); + if (interactive.has("button_reply")) { + textBody = interactive.get("button_reply").get("id").asText(); + } else if (interactive.has("list_reply")) { + textBody = interactive.get("list_reply").get("id").asText(); + } + } + if (textBody != null) { + String phoneNumberId = value.get("metadata").get("phone_number_id").asText(); + // Delegate to Chatbot Service + chatbotService.processMessage(from, textBody, phoneNumberId); + } + } + } + } + } + } + } catch (Exception e) { + logger.error("Error processing webhook payload", e); + } + + return ResponseEntity.ok("EVENT_RECEIVED"); + } +} diff --git a/src/main/java/com/example/whatsappautomation/entity/Appointment.java b/src/main/java/com/example/whatsappautomation/entity/Appointment.java new file mode 100644 index 0000000..02d782e --- /dev/null +++ b/src/main/java/com/example/whatsappautomation/entity/Appointment.java @@ -0,0 +1,44 @@ +package com.example.whatsappautomation.entity; + +import jakarta.persistence.*; + +@Entity +@Table(name = "appointment") +public class Appointment { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String phoneNumber; + private String patientId; + private String appointmentId; + private String department; + private String doctor; + private String appointmentDate; + private String appointmentTime; + private String fee; + private String status; + + // Getters and Setters + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + public String getPhoneNumber() { return phoneNumber; } + public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; } + public String getPatientId() { return patientId; } + public void setPatientId(String patientId) { this.patientId = patientId; } + public String getAppointmentId() { return appointmentId; } + public void setAppointmentId(String appointmentId) { this.appointmentId = appointmentId; } + public String getDepartment() { return department; } + public void setDepartment(String department) { this.department = department; } + public String getDoctor() { return doctor; } + public void setDoctor(String doctor) { this.doctor = doctor; } + public String getAppointmentDate() { return appointmentDate; } + public void setAppointmentDate(String appointmentDate) { this.appointmentDate = appointmentDate; } + public String getAppointmentTime() { return appointmentTime; } + public void setAppointmentTime(String appointmentTime) { this.appointmentTime = appointmentTime; } + public String getFee() { return fee; } + public void setFee(String fee) { this.fee = fee; } + public String getStatus() { return status; } + public void setStatus(String status) { this.status = status; } +} diff --git a/src/main/java/com/example/whatsappautomation/entity/ServiceBooking.java b/src/main/java/com/example/whatsappautomation/entity/ServiceBooking.java new file mode 100644 index 0000000..44b5d5f --- /dev/null +++ b/src/main/java/com/example/whatsappautomation/entity/ServiceBooking.java @@ -0,0 +1,44 @@ +package com.example.whatsappautomation.entity; + +import jakarta.persistence.*; + +@Entity +@Table(name = "service_booking") +public class ServiceBooking { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String phoneNumber; + private String patientId; + private String bookingId; + private String serviceName; + private String price; + private String address; + private String bookingDate; + private String bookingTime; + private String status; + + // Getters and Setters + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + public String getPhoneNumber() { return phoneNumber; } + public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; } + public String getPatientId() { return patientId; } + public void setPatientId(String patientId) { this.patientId = patientId; } + public String getBookingId() { return bookingId; } + public void setBookingId(String bookingId) { this.bookingId = bookingId; } + public String getServiceName() { return serviceName; } + public void setServiceName(String serviceName) { this.serviceName = serviceName; } + public String getPrice() { return price; } + public void setPrice(String price) { this.price = price; } + public String getAddress() { return address; } + public void setAddress(String address) { this.address = address; } + public String getBookingDate() { return bookingDate; } + public void setBookingDate(String bookingDate) { this.bookingDate = bookingDate; } + public String getBookingTime() { return bookingTime; } + public void setBookingTime(String bookingTime) { this.bookingTime = bookingTime; } + public String getStatus() { return status; } + public void setStatus(String status) { this.status = status; } +} diff --git a/src/main/java/com/example/whatsappautomation/entity/TestBooking.java b/src/main/java/com/example/whatsappautomation/entity/TestBooking.java new file mode 100644 index 0000000..4a470ec --- /dev/null +++ b/src/main/java/com/example/whatsappautomation/entity/TestBooking.java @@ -0,0 +1,47 @@ +package com.example.whatsappautomation.entity; + +import jakarta.persistence.*; + +@Entity +@Table(name = "test_booking") +public class TestBooking { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String phoneNumber; + private String bookingId; + private String packageName; + private String price; + private String collectionType; + private String address; + private String bookingDate; + private String bookingTime; + private String patientId; + private String status; + + // Getters and Setters + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + public String getPhoneNumber() { return phoneNumber; } + public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; } + public String getBookingId() { return bookingId; } + public void setBookingId(String bookingId) { this.bookingId = bookingId; } + public String getPackageName() { return packageName; } + public void setPackageName(String packageName) { this.packageName = packageName; } + public String getPrice() { return price; } + public void setPrice(String price) { this.price = price; } + public String getCollectionType() { return collectionType; } + public void setCollectionType(String collectionType) { this.collectionType = collectionType; } + public String getAddress() { return address; } + public void setAddress(String address) { this.address = address; } + public String getBookingDate() { return bookingDate; } + public void setBookingDate(String bookingDate) { this.bookingDate = bookingDate; } + public String getBookingTime() { return bookingTime; } + public void setBookingTime(String bookingTime) { this.bookingTime = bookingTime; } + public String getPatientId() { return patientId; } + public void setPatientId(String patientId) { this.patientId = patientId; } + public String getStatus() { return status; } + public void setStatus(String status) { this.status = status; } +} diff --git a/src/main/java/com/example/whatsappautomation/entity/UserState.java b/src/main/java/com/example/whatsappautomation/entity/UserState.java new file mode 100644 index 0000000..8532990 --- /dev/null +++ b/src/main/java/com/example/whatsappautomation/entity/UserState.java @@ -0,0 +1,144 @@ +package com.example.whatsappautomation.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +@Entity +@Table(name = "user_state") +public class UserState { + + @Id + private String phoneNumber; + + private String patientId; + private String state; + private String category; + private String subCategory; + private String selectedItem; + private String price; + private String address; + private String bookingDate; + private String bookingTime; + private String languagePreference = "en"; + private int menuPage = 0; + + public UserState() {} + + public UserState(String phoneNumber) { + this.phoneNumber = phoneNumber; + this.state = "LANGUAGE_SELECTION"; + this.patientId = "PAT-" + (100000 + new java.util.Random().nextInt(900000)); + } + + public void reset() { + this.state = "LANGUAGE_SELECTION"; + this.category = null; + this.subCategory = null; + this.selectedItem = null; + this.price = null; + this.address = null; + this.bookingDate = null; + this.bookingTime = null; + this.menuPage = 0; + } + + public String getPhoneNumber() { + return phoneNumber; + } + + public void setPhoneNumber(String phoneNumber) { + this.phoneNumber = phoneNumber; + } + + public String getState() { + return state; + } + + public void setState(String state) { + if (!state.equals(this.state)) { + this.menuPage = 0; + } + this.state = state; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public String getSubCategory() { + return subCategory; + } + + public void setSubCategory(String subCategory) { + this.subCategory = subCategory; + } + + public String getSelectedItem() { + return selectedItem; + } + + public void setSelectedItem(String selectedItem) { + this.selectedItem = selectedItem; + } + + public String getPrice() { + return price; + } + + public void setPrice(String price) { + this.price = price; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getBookingDate() { + return bookingDate; + } + + public void setBookingDate(String bookingDate) { + this.bookingDate = bookingDate; + } + + public String getBookingTime() { + return bookingTime; + } + + public void setBookingTime(String bookingTime) { + this.bookingTime = bookingTime; + } + + public String getPatientId() { + return patientId; + } + + public void setPatientId(String patientId) { + this.patientId = patientId; + } + + public String getLanguagePreference() { + return languagePreference; + } + + public void setLanguagePreference(String languagePreference) { + this.languagePreference = languagePreference; + } + + public int getMenuPage() { + return menuPage; + } + + public void setMenuPage(int menuPage) { + this.menuPage = menuPage; + } +} diff --git a/src/main/java/com/example/whatsappautomation/repository/AppointmentRepository.java b/src/main/java/com/example/whatsappautomation/repository/AppointmentRepository.java new file mode 100644 index 0000000..f8df56f --- /dev/null +++ b/src/main/java/com/example/whatsappautomation/repository/AppointmentRepository.java @@ -0,0 +1,14 @@ +package com.example.whatsappautomation.repository; + +import com.example.whatsappautomation.entity.Appointment; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface AppointmentRepository extends JpaRepository { + List findByPhoneNumber(String phoneNumber); + Optional findByAppointmentId(String appointmentId); +} diff --git a/src/main/java/com/example/whatsappautomation/repository/ServiceBookingRepository.java b/src/main/java/com/example/whatsappautomation/repository/ServiceBookingRepository.java new file mode 100644 index 0000000..d31cdae --- /dev/null +++ b/src/main/java/com/example/whatsappautomation/repository/ServiceBookingRepository.java @@ -0,0 +1,9 @@ +package com.example.whatsappautomation.repository; + +import com.example.whatsappautomation.entity.ServiceBooking; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ServiceBookingRepository extends JpaRepository { +} diff --git a/src/main/java/com/example/whatsappautomation/repository/TestBookingRepository.java b/src/main/java/com/example/whatsappautomation/repository/TestBookingRepository.java new file mode 100644 index 0000000..7faeb96 --- /dev/null +++ b/src/main/java/com/example/whatsappautomation/repository/TestBookingRepository.java @@ -0,0 +1,9 @@ +package com.example.whatsappautomation.repository; + +import com.example.whatsappautomation.entity.TestBooking; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface TestBookingRepository extends JpaRepository { +} diff --git a/src/main/java/com/example/whatsappautomation/repository/UserStateRepository.java b/src/main/java/com/example/whatsappautomation/repository/UserStateRepository.java new file mode 100644 index 0000000..77ce3d4 --- /dev/null +++ b/src/main/java/com/example/whatsappautomation/repository/UserStateRepository.java @@ -0,0 +1,9 @@ +package com.example.whatsappautomation.repository; + +import com.example.whatsappautomation.entity.UserState; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface UserStateRepository extends JpaRepository { +} diff --git a/src/main/java/com/example/whatsappautomation/service/ChatbotService.java b/src/main/java/com/example/whatsappautomation/service/ChatbotService.java new file mode 100644 index 0000000..d5a4158 --- /dev/null +++ b/src/main/java/com/example/whatsappautomation/service/ChatbotService.java @@ -0,0 +1,1129 @@ +package com.example.whatsappautomation.service; + +import com.example.whatsappautomation.entity.*; +import com.example.whatsappautomation.repository.*; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.context.MessageSource; + +import java.util.List; +import java.util.Optional; +import java.util.Random; + +@Service +public class ChatbotService { + private static final Logger logger = LoggerFactory.getLogger(ChatbotService.class); + + private final ThreadLocal currentPhoneId = new ThreadLocal<>(); + private final ThreadLocal currentFrom = new ThreadLocal<>(); + + private final UserStateRepository userStateRepository; + private final ServiceBookingRepository serviceBookingRepository; + private final TestBookingRepository testBookingRepository; + private final AppointmentRepository appointmentRepository; + private final MessageSource messageSource; + + @Value("${whatsapp.access-token}") + private String accessToken; + + private final RestTemplate restTemplate = new RestTemplate(); + private final ObjectMapper objectMapper = new ObjectMapper(); + + public ChatbotService(UserStateRepository userStateRepository, + ServiceBookingRepository serviceBookingRepository, + TestBookingRepository testBookingRepository, + AppointmentRepository appointmentRepository, + MessageSource messageSource) { + this.userStateRepository = userStateRepository; + this.serviceBookingRepository = serviceBookingRepository; + this.testBookingRepository = testBookingRepository; + this.appointmentRepository = appointmentRepository; + this.messageSource = messageSource; + } + + public void processMessage(String from, String messageText, String phoneNumberId) { + currentPhoneId.set(phoneNumberId); + currentFrom.set(from); + try { + UserState userState = userStateRepository.findById(from).orElse(new UserState(from)); + + // Global Reset: Handling basic greetings + String lowerInput = messageText.trim().toLowerCase(); + if (lowerInput.equals("hi") || lowerInput.equals("hello") || lowerInput.equals("hey") || lowerInput.equals("0")) { + userState.reset(); + } + + String response = handleState(userState, messageText.trim()); + + // If not in MAIN_MENU or LANGUAGE_SELECTION, append restart hint + if (response != null && !response.isEmpty() && !response.startsWith("INTERACTIVE:") && !userState.getState().equals("MAIN_MENU") && !userState.getState().equals("LANGUAGE_SELECTION")) { + response += "\n\n(Send 0 to go back to the main menu / Restart the conversation)"; + } + + userStateRepository.save(userState); + + if (response != null && !response.isEmpty()) { + if (response.startsWith("INTERACTIVE:LANG")) { + sendLanguageSelectionMessage(phoneNumberId, from); + } else if (response.startsWith("INTERACTIVE:MAIN_MENU")) { + sendMainMenuInteractiveMessage(phoneNumberId, from, userState.getLanguagePreference()); + } else { + sendMessage(phoneNumberId, from, response); + } + } + } finally { + currentPhoneId.remove(); + currentFrom.remove(); + } + } + + private String getMessage(String key, UserState state, Object... args) { + java.util.Locale locale = new java.util.Locale(state.getLanguagePreference()); + try { + return messageSource.getMessage(key, args, locale); + } catch (Exception e) { + return key; + } + } + + private String handleState(UserState state, String input) { + if ("NEXT_PAGE".equals(input)) { + state.setMenuPage(state.getMenuPage() + 1); + } + + switch (state.getState()) { + case "LANGUAGE_SELECTION": + return handleLanguageSelection(state, input); + case "MAIN_MENU": + return handleMainMenu(state, input); + case "SERVICES_MENU": + return handleServicesMenu(state, input); + case "NURSING_SERVICES_MENU": + return handleNursingServices(state, input); + case "MALE_NURSE_MENU": + return handleMaleNurse(state, input); + case "FEMALE_NURSE_MENU": + return handleFemaleNurse(state, input); + case "ATTENDER_MENU": + return handleAttender(state, input); + case "FEMALE_ATTENDER_MENU": + return handleFemaleAttender(state, input); + case "PHYSIOTHERAPY_MENU": + return handlePhysiotherapy(state, input); + case "MEDICAL_PROCEDURES_MENU": + return handleMedicalProcedures(state, input); + case "DIGITAL_CONSULTATION_MENU": + return handleDigitalConsultation(state, input); + case "HOME_CARE_PACKAGES_MENU": + return handleHomeCare(state, input); + case "SERVICE_CONFIRM_BOOKING": + return handleServiceConfirmBooking(state, input); + case "SERVICE_ASK_ADDRESS": + return handleServiceAskAddress(state, input); + case "SERVICE_ASK_DATE": + return handleServiceAskDate(state, input); + case "SERVICE_ASK_TIME": + return handleServiceAskTime(state, input); + + case "TESTS_MENU": + return handleTestsMenu(state, input); + case "FEVER_PANEL": + return handleFeverPanel(state, input); + case "HEART_HEALTH": + return handleHeartHealth(state, input); + case "JOINT_BONE": + return handleJointBone(state, input); + case "MASTER_HEALTH": + return handleMasterHealth(state, input); + case "PREGNANCY": + return handlePregnancy(state, input); + case "SUGAR_CONTROL": + return handleSugarControl(state, input); + case "WOMENS_WELLNESS": + return handleWomensWellness(state, input); + case "TEST_CONFIRM_BOOKING": + return handleTestConfirmBooking(state, input); + case "TEST_ASK_COLLECTION_TYPE": + return handleTestAskCollectionType(state, input); + case "TEST_ASK_ADDRESS": + return handleTestAskAddress(state, input); + case "TEST_ASK_DATE": + return handleTestAskDate(state, input); + case "TEST_ASK_TIME": + return handleTestAskTime(state, input); + + case "DOCTOR_MENU": + return handleDoctorMenu(state, input); + case "BOOK_NEW_APPOINTMENT_DEPT": + return handleBookAppointmentDept(state, input); + case "DOCTOR_SELECTION": + return handleDoctorSelection(state, input); + case "DOCTOR_DATE_SELECTION": + return handleDoctorDateSelection(state, input); + case "DOCTOR_TIME_SELECTION": + return handleDoctorTimeSelection(state, input); + case "APPOINTMENT_SUMMARY": + return handleAppointmentSummary(state, input); + case "VIEW_APPOINTMENTS": + return handleViewAppointments(state, input); + case "RESCHEDULE_ASK_ID": + return handleRescheduleAskId(state, input); + case "RESCHEDULE_ASK_TIME": + return handleRescheduleAskTime(state, input); + case "CANCEL_ASK_ID": + return handleCancelAskId(state, input); + case "CANCEL_CONFIRM": + return handleCancelConfirm(state, input); + + case "FINAL_MAIN_MENU": + return handleFinalMainMenu(state, input); + + default: + state.reset(); + return getMainMenuText(state); + } + } + + // ========================================== + // LANGUAGE SELECTION + // ========================================== + private String handleLanguageSelection(UserState state, String input) { + if ("LANG_EN".equals(input) || "1".equals(input) || input.equalsIgnoreCase("English")) { + state.setLanguagePreference("en"); + state.setState("MAIN_MENU"); + return getMainMenuText(state); + } else if ("LANG_TA".equals(input) || "2".equals(input) || input.equals("தமிழ்")) { + state.setLanguagePreference("ta"); + state.setState("MAIN_MENU"); + return getMainMenuText(state); + } + return "INTERACTIVE:LANG"; + } + + // ========================================== + // MAIN MENU + // ========================================== + private String handleMainMenu(UserState state, String input) { + switch (input) { + case "1": + case "MENU_SERVICES": + state.setState("SERVICES_MENU"); + return handleServicesMenu(state, ""); // Empty input to trigger menu display + case "2": + case "MENU_TESTS": + state.setState("TESTS_MENU"); + return handleTestsMenu(state, ""); + case "3": + case "MENU_BOOKING": + state.setState("DOCTOR_MENU"); + return handleDoctorMenu(state, ""); + default: + return getMainMenuText(state); + } + } + + private String getMainMenuText(UserState state) { + return "INTERACTIVE:MAIN_MENU"; + } + + // ========================================== + // SERVICES + // ========================================== + private String handleServicesMenu(UserState state, String input) { + switch (input) { + case "1": case "SRV_NURSING": + state.setState("NURSING_SERVICES_MENU"); + sendInteractiveButtons(getMessage("services.nursing.title", state), new String[]{"NSG_MALE", "NSG_FEMALE"}, new String[]{getMessage("services.nursing.opt1", state), getMessage("services.nursing.opt2", state)}); + return null; + case "2": case "SRV_ATTENDER": + state.setState("ATTENDER_MENU"); + sendInteractiveButtons(getMessage("services.attender.title", state), new String[]{"ATT_MALE", "ATT_FEMALE"}, new String[]{getMessage("services.attender.opt1", state), getMessage("services.attender.opt2", state)}); + return null; + case "3": case "SRV_PHYSIO": + state.setState("PHYSIOTHERAPY_MENU"); + state.setSelectedItem("Physiotherapy"); + state.setPrice("₹700"); + sendInteractiveButtons(getMessage("services.physio.title", state), new String[]{"CONFIRM_YES", "CONFIRM_NO"}, new String[]{getMessage("services.physio.yes", state), getMessage("services.physio.no", state)}); + return null; + case "4": case "SRV_MEDICAL_PROC": + state.setState("MEDICAL_PROCEDURES_MENU"); + sendPaginatedButtons(state, getMessage("services.medproc.title", state), new String[]{"PROC_IM", "PROC_IV", "PROC_NEB", "PROC_DRESS", "PROC_CATH"}, new String[]{getMessage("services.medproc.opt1", state), getMessage("services.medproc.opt2", state), getMessage("services.medproc.opt3", state), getMessage("services.medproc.opt4", state), getMessage("services.medproc.opt5", state)}); + return null; + case "5": case "SRV_DIGITAL_CONSULT": + state.setState("DIGITAL_CONSULTATION_MENU"); + state.setSelectedItem("Digital Consultation"); + state.setPrice("₹750"); + sendInteractiveButtons(getMessage("services.digital.title", state), new String[]{"CONFIRM_YES", "CONFIRM_BACK"}, new String[]{getMessage("services.digital.yes", state), getMessage("services.digital.back", state)}); + return null; + case "6": case "SRV_HOME_CARE": + state.setState("HOME_CARE_PACKAGES_MENU"); + sendPaginatedButtons(state, getMessage("services.homecare.title", state), + new String[]{"HC_NURSING", "HC_ATTENDER", "HC_PHYSIO", "HC_MEDICAL_PROC"}, + new String[]{getMessage("services.homecare.opt1", state), getMessage("services.homecare.opt2", state), getMessage("services.homecare.opt3", state), getMessage("services.homecare.opt4", state)}); + return null; + default: + sendPaginatedButtons(state, getMessage("services.main.title", state), new String[]{"SRV_NURSING", "SRV_ATTENDER", "SRV_PHYSIO", "SRV_MEDICAL_PROC", "SRV_DIGITAL_CONSULT", "SRV_HOME_CARE"}, new String[]{getMessage("services.main.opt1", state), getMessage("services.main.opt2", state), getMessage("services.main.opt3", state), getMessage("services.main.opt4", state), getMessage("services.main.opt5", state), getMessage("services.main.opt6", state)}); + return null; + } + } + + private String handleNursingServices(UserState state, String input) { + if ("1".equals(input) || "NSG_MALE".equals(input)) { + state.setState("MALE_NURSE_MENU"); + sendPaginatedButtons(state, "👨‍⚕️ Male Nurse Services", new String[]{"MALE_12H_D", "MALE_24H_D", "MALE_12H_M", "MALE_24H_M"}, new String[]{"12 Hours (Per Day)", "24 Hours (Per Day)", "12 Hours (Month)", "24 Hours (Month)"}); + return null; + } else if ("2".equals(input) || "NSG_FEMALE".equals(input)) { + state.setState("FEMALE_NURSE_MENU"); + sendPaginatedButtons(state, "👩‍⚕️ Female Nurse Services", + new String[]{"FEM_12H_D", "FEM_24H_D", "FEM_12H_M", "FEM_24H_M"}, + new String[]{"12 Hours (Per Day)", "24 Hours (Per Day)", "12 Hours (Month)", "24 Hours (Month)"}); + return null; + } + return "Invalid option. Please use the menu buttons."; + } + + private String handleMaleNurse(UserState state, String input) { + String serviceName = "Male Nurse"; + String price = ""; + switch (input) { + case "1": case "MALE_12H_D": serviceName += " (12 Hours/Day)"; price = "₹1,800"; break; + case "2": case "MALE_24H_D": serviceName += " (24 Hours/Day)"; price = "₹3,300"; break; + case "3": case "MALE_12H_M": serviceName += " (12 Hours/Month)"; price = "₹54,000"; break; + case "4": case "MALE_24H_M": serviceName += " (24 Hours/Month)"; price = "₹99,000"; break; + default: return "Invalid option."; + } + state.setSelectedItem(serviceName); + state.setPrice(price); + state.setState("SERVICE_CONFIRM_BOOKING"); + sendInteractiveButtons(serviceName + "\nPrice:\n" + price + "\nWould you like to book this service?", new String[]{"BOOK_CONFIRM", "BOOK_BACK"}, new String[]{"Confirm Booking", "Back"}); + return null; + } + + private String handleFemaleNurse(UserState state, String input) { + String serviceName = "Female Nurse"; + String price = ""; + switch (input) { + case "1": case "FEM_12H_D": serviceName += " (12 Hours/Day)"; price = "₹1,500"; break; + case "2": case "FEM_24H_D": serviceName += " (24 Hours/Day)"; price = "₹3,000"; break; + case "3": case "FEM_12H_M": serviceName += " (12 Hours/Month)"; price = "₹45,000"; break; + case "4": case "FEM_24H_M": serviceName += " (24 Hours/Month)"; price = "₹90,000"; break; + default: return "Invalid option."; + } + state.setSelectedItem(serviceName); + state.setPrice(price); + state.setState("SERVICE_CONFIRM_BOOKING"); + sendInteractiveButtons(serviceName + "\nPrice:\n" + price + "\nWould you like to book this service?", new String[]{"BOOK_CONFIRM", "BOOK_BACK"}, new String[]{"Confirm Booking", "Back"}); + return null; + } + + private String handleAttender(UserState state, String input) { + if ("1".equals(input) || "ATT_MALE".equals(input)) { + state.setSelectedItem("Male Attender"); + state.setPrice("₹1,200"); + state.setState("SERVICE_CONFIRM_BOOKING"); + sendInteractiveButtons("Male Attender\nPrice: ₹1,200\nWould you like to book this service?", new String[]{"BOOK_CONFIRM", "BOOK_BACK"}, new String[]{"Confirm Booking", "Back"}); + return null; + } else if ("2".equals(input) || "ATT_FEMALE".equals(input)) { + state.setState("FEMALE_ATTENDER_MENU"); + sendPaginatedButtons(state, "👩‍⚕️ Female Attender", + new String[]{"F_ATT_12H_D", "F_ATT_24H_D", "F_ATT_12H_M", "F_ATT_24H_M"}, + new String[]{"12 Hours (Per Day)", "24 Hours (Per Day)", "12 Hours (Month)", "24 Hours (Month)"}); + return null; + } + return "Invalid option. Please use the menu buttons."; + } + + private String handleFemaleAttender(UserState state, String input) { + String serviceName = "Female Attender"; + String price = ""; + switch (input) { + case "1": case "F_ATT_12H_D": serviceName += " (12 Hours)"; price = "₹1,200"; break; + case "2": case "F_ATT_24H_D": serviceName += " (24 Hours)"; price = "₹1,700"; break; + case "3": case "F_ATT_12H_M": serviceName += " (12 Hours/Month)"; price = "₹36,000"; break; + case "4": case "F_ATT_24H_M": serviceName += " (24 Hours/Month)"; price = "₹51,000"; break; + default: return "Invalid option."; + } + state.setSelectedItem(serviceName); + state.setPrice(price); + state.setState("SERVICE_CONFIRM_BOOKING"); + sendInteractiveButtons(serviceName + "\nPrice:\n" + price + "\nWould you like to book this service?", new String[]{"BOOK_CONFIRM", "BOOK_BACK"}, new String[]{"Confirm Booking", "Back"}); + return null; + } + + private String handlePhysiotherapy(UserState state, String input) { + if ("1".equals(input) || input.equalsIgnoreCase("Yes") || "CONFIRM_YES".equals(input)) { + state.setState("SERVICE_ASK_ADDRESS"); + return "✅ Booking Confirmed\nService: " + state.getSelectedItem() + "\nAmount: " + state.getPrice() + "\n\nPlease share:\n📍 Service Address\nor\n📍 Live Location"; + } else { + state.reset(); + return getMainMenuText(state); + } + } + + private String handleMedicalProcedures(UserState state, String input) { + String serviceName = ""; + String price = ""; + switch (input) { + case "1": case "PROC_IM": serviceName = "IM Injection"; price = "₹400"; break; + case "2": case "PROC_IV": serviceName = "IV Injection"; price = "₹500"; break; + case "3": case "PROC_NEB": serviceName = "Nebulization"; price = "₹1,000"; break; + case "4": case "PROC_DRESS": serviceName = "Dressing Charges"; price = "₹750"; break; + case "5": case "PROC_CATH": serviceName = "Catheterization"; price = "₹1,500"; break; + default: return "Invalid option."; + } + state.setSelectedItem(serviceName); + state.setPrice(price); + state.setState("SERVICE_CONFIRM_BOOKING"); + sendInteractiveButtons(serviceName + "\n\nPrice:\n" + price + "\n\nWould you like to continue?", new String[]{"BOOK_CONFIRM", "BOOK_CANCEL"}, new String[]{"Confirm", "Cancel"}); + return null; + } + + private String handleDigitalConsultation(UserState state, String input) { + if ("1".equals(input) || input.equalsIgnoreCase("Yes") || "CONFIRM_YES".equals(input)) { + state.setState("SERVICE_ASK_ADDRESS"); + return getMessage("booking.service.confirm", state, state.getSelectedItem(), state.getPrice()); + } else { + state.reset(); + return getMainMenuText(state); + } + } + + private String handleHomeCare(UserState state, String input) { + String service = input; + switch(input) { + case "HC_NURSING": service = "Nursing Care"; break; + case "HC_ATTENDER": service = "Attender Care"; break; + case "HC_PHYSIO": service = "Physiotherapy"; break; + case "HC_MEDICAL_PROC": service = "Medical Procedures"; break; + } + state.setSelectedItem("Home Care: " + service); + state.setPrice("Varies"); + state.setState("SERVICE_CONFIRM_BOOKING"); + sendInteractiveButtons("Home Care Package selected.\nWould you like to continue?", new String[]{"BOOK_CONFIRM", "BOOK_CANCEL"}, new String[]{"Confirm", "Cancel"}); + return null; + } + + private String handleServiceConfirmBooking(UserState state, String input) { + if ("1".equals(input) || "BOOK_CONFIRM".equals(input) || input.equalsIgnoreCase("Confirm Booking") || input.equalsIgnoreCase("Confirm")) { + state.setState("SERVICE_ASK_ADDRESS"); + return getMessage("booking.service.confirm", state, state.getSelectedItem(), state.getPrice()); + } else { + state.reset(); + return getMainMenuText(state); + } + } + + private String handleServiceAskAddress(UserState state, String input) { + state.setAddress(input); + state.setState("SERVICE_ASK_DATE"); + return getMessage("booking.ask_date", state); + } + + private String handleServiceAskDate(UserState state, String input) { + state.setBookingDate(input); + state.setState("SERVICE_ASK_TIME"); + sendPaginatedButtons(state, getMessage("booking.ask_time.title", state), + new String[]{"TIME_9_1", "TIME_1_5", "TIME_5_9"}, + new String[]{getMessage("booking.ask_time.opt1", state), getMessage("booking.ask_time.opt2", state), getMessage("booking.ask_time.opt3", state)}); + return null; + } + + private String handleServiceAskTime(UserState state, String input) { + String time = input; + switch(input) { + case "TIME_9_1": time = "9 AM - 1 PM"; break; + case "TIME_1_5": time = "1 PM - 5 PM"; break; + case "TIME_5_9": time = "5 PM - 9 PM"; break; + } + state.setBookingTime(time); + + // Save to DB + ServiceBooking booking = new ServiceBooking(); + booking.setPhoneNumber(state.getPhoneNumber()); + String bookingId = "RMC-HC-" + (10000 + new Random().nextInt(90000)); + booking.setBookingId(bookingId); + booking.setServiceName(state.getSelectedItem()); + booking.setPrice(state.getPrice()); + booking.setAddress(state.getAddress()); + booking.setBookingDate(state.getBookingDate()); + booking.setBookingTime(state.getBookingTime()); + booking.setPatientId(state.getPatientId()); + booking.setStatus("CONFIRMED"); + serviceBookingRepository.save(booking); + + + state.setState("FINAL_MAIN_MENU"); + return getMessage("booking.service.success", state, bookingId, state.getPatientId()) + "\n\n" + getFinalMainMenuText(state); + } + + + // ========================================== + // TESTS MENU + // ========================================== + private String handleTestsMenu(UserState state, String input) { + switch(input) { + case "1": case "TEST_FEVER": + state.setState("FEVER_PANEL"); + sendPaginatedButtons(state, getMessage("tests.fever.title", state), new String[]{"FEVER_ULTRA", "FEVER_CBC", "FEVER_IGE", "FEVER_LFT", "FEVER_STOOL"}, new String[]{getMessage("tests.fever.opt1", state), getMessage("tests.fever.opt2", state), getMessage("tests.fever.opt3", state), getMessage("tests.fever.opt4", state), getMessage("tests.fever.opt5", state)}); + return null; + case "2": case "TEST_HEART": + state.setState("HEART_HEALTH"); + sendPaginatedButtons(state, getMessage("tests.heart.title", state), + new String[]{"HRT_PLUS", "HRT_AMAYA_INT", "HRT_AMAYA", "HRT_HEALTHY", "HRT_BASIC", "HRT_MASTER", "HRT_HYPER"}, + new String[]{getMessage("tests.heart.opt1", state), getMessage("tests.heart.opt2", state), getMessage("tests.heart.opt3", state), getMessage("tests.heart.opt4", state), getMessage("tests.heart.opt5", state), getMessage("tests.heart.opt6", state), getMessage("tests.heart.opt7", state)}); + return null; + case "3": case "TEST_JOINT": + state.setState("JOINT_BONE"); + sendInteractiveButtons(getMessage("tests.joint.title", state), new String[]{"JOINT_PLUS", "JOINT_ARTH"}, new String[]{getMessage("tests.joint.opt1", state), getMessage("tests.joint.opt2", state)}); + return null; + case "4": case "TEST_MASTER": + state.setState("MASTER_HEALTH"); + sendPaginatedButtons(state, getMessage("tests.master.title", state), + new String[]{"MH_BASIC", "MH_EXEC", "MH_PREMIUM"}, + new String[]{getMessage("tests.master.opt1", state), getMessage("tests.master.opt2", state), getMessage("tests.master.opt3", state)}); + return null; + case "5": case "TEST_PREGNANCY": + state.setState("PREGNANCY"); + sendInteractiveButtons(getMessage("tests.preg.title", state), new String[]{"PREG_BASIC", "PREG_ADV"}, new String[]{getMessage("tests.preg.opt1", state), getMessage("tests.preg.opt2", state)}); + return null; + case "6": case "TEST_SUGAR": + state.setState("SUGAR_CONTROL"); + sendPaginatedButtons(state, getMessage("tests.sugar.title", state), new String[]{"SUGAR_BASIC", "SUGAR_PLUS", "SUGAR_MONITOR", "SUGAR_HBA1C"}, new String[]{getMessage("tests.sugar.opt1", state), getMessage("tests.sugar.opt2", state), getMessage("tests.sugar.opt3", state), getMessage("tests.sugar.opt4", state)}); + return null; + case "7": case "TEST_WOMEN": + state.setState("WOMENS_WELLNESS"); + sendInteractiveButtons(getMessage("tests.women.title", state), new String[]{"WOM_BASIC", "WOM_ADV"}, new String[]{getMessage("tests.women.opt1", state), getMessage("tests.women.opt2", state)}); + return null; + default: + sendPaginatedButtons(state, getMessage("tests.main.title", state), new String[]{"TEST_FEVER", "TEST_HEART", "TEST_JOINT", "TEST_MASTER", "TEST_PREGNANCY", "TEST_SUGAR", "TEST_WOMEN"}, new String[]{getMessage("tests.main.opt1", state), getMessage("tests.main.opt2", state), getMessage("tests.main.opt3", state), getMessage("tests.main.opt4", state), getMessage("tests.main.opt5", state), getMessage("tests.main.opt6", state), getMessage("tests.main.opt7", state)}); + return null; + } + } + + private String handleFeverPanel(UserState state, String input) { + String test = "Fever Ultra Panel"; + String price = "₹1,999"; + switch(input) { + case "1": case "FEVER_CBC": test = "CBC"; price = "₹300"; break; + case "2": case "FEVER_IGE": test = "IgE Total Serum"; price = "₹400"; break; + case "3": case "FEVER_LFT": test = "Liver Function Test (LFT)"; price = "₹500"; break; + case "4": case "FEVER_STOOL": test = "Stool for Occult Blood"; price = "₹250"; break; + case "FEVER_ULTRA": break; + default: return "Invalid option."; + } + state.setSelectedItem(test); + state.setPrice(price); + state.setState("TEST_CONFIRM_BOOKING"); + sendInteractiveButtons("Selected Package\n\n" + test + "\nPrice:\n" + price + "\nWould you like to continue?", new String[]{"TEST_BOOK", "TEST_BACK"}, new String[]{"Book Now", "Back"}); + return null; + } + + private String handleHeartHealth(UserState state, String input) { + String test = ""; String price = ""; + switch(input) { + case "1": case "HRT_PLUS": test = "Heart Health Plus"; price = "₹3,300"; break; + case "2": case "HRT_AMAYA_INT": test = "Amaya International Cardiac"; price = "₹2,121"; break; + case "3": case "HRT_AMAYA": test = "Cardiac Package - Amaya"; price = "₹3,360"; break; + case "4": case "HRT_HEALTHY": test = "Healthy Heart Basic"; price = "₹799"; break; + case "5": case "HRT_BASIC": test = "Heart Basic"; price = "₹599"; break; + case "6": case "HRT_MASTER": test = "Heart Master"; price = "₹1,399"; break; + case "7": case "HRT_HYPER": test = "Hypertension Comprehensive Package"; price = "₹2,999"; break; + default: return "Invalid option."; + } + state.setSelectedItem(test); + state.setPrice(price); + state.setState("TEST_CONFIRM_BOOKING"); + sendInteractiveButtons("Selected Package\n\n" + test + "\nPrice:\n" + price + "\nWould you like to continue?", new String[]{"TEST_BOOK", "TEST_BACK"}, new String[]{"Book Now", "Back"}); + return null; + } + + private String handleJointBone(UserState state, String input) { + String test = ""; String price = ""; + switch(input) { + case "1": case "JOINT_PLUS": test = "Joint Care Plus"; price = "₹1,599"; break; + case "2": case "JOINT_ARTH": test = "Arthritis Package"; price = "₹799"; break; + default: return "Invalid option."; + } + state.setSelectedItem(test); + state.setPrice(price); + state.setState("TEST_CONFIRM_BOOKING"); + sendInteractiveButtons("Selected Package\n\n" + test + "\nPrice:\n" + price + "\nWould you like to continue?", new String[]{"TEST_BOOK", "TEST_BACK"}, new String[]{"Book Now", "Back"}); + return null; + } + + private String handleMasterHealth(UserState state, String input) { + String test = ""; String price = "₹2,500"; + switch(input) { + case "1": case "MH_BASIC": test = "Master Health Basic"; break; + case "2": case "MH_EXEC": test = "Master Health Executive"; break; + case "3": case "MH_PREMIUM": test = "Master Health Premium"; break; + default: return "Invalid option."; + } + state.setSelectedItem(test); + state.setPrice(price); + state.setState("TEST_CONFIRM_BOOKING"); + sendInteractiveButtons("Selected Package\n\n" + test + "\nPrice:\n" + price + "\nWould you like to continue?", new String[]{"TEST_BOOK", "TEST_BACK"}, new String[]{"Book Now", "Back"}); + return null; + } + + private String handlePregnancy(UserState state, String input) { + String test = ""; String price = "₹1,500"; + switch(input) { + case "1": case "PREG_BASIC": test = "Pregnancy Basic"; break; + case "2": case "PREG_ADV": test = "Pregnancy Advanced"; break; + default: return "Invalid option."; + } + state.setSelectedItem(test); + state.setPrice(price); + state.setState("TEST_CONFIRM_BOOKING"); + sendInteractiveButtons("Selected Package\n\n" + test + "\nPrice:\n" + price + "\nWould you like to continue?", new String[]{"TEST_BOOK", "TEST_BACK"}, new String[]{"Book Now", "Back"}); + return null; + } + + private String handleSugarControl(UserState state, String input) { + String test = ""; String price = "₹999"; + switch(input) { + case "1": case "SUGAR_BASIC": test = "Sugar Control Basic"; break; + case "2": case "SUGAR_PLUS": test = "Sugar Control Plus"; break; + case "3": case "SUGAR_MONITOR": test = "Diabetes Monitoring"; break; + case "4": case "SUGAR_HBA1C": test = "HbA1c Comprehensive"; break; + default: return "Invalid option."; + } + state.setSelectedItem(test); + state.setPrice(price); + state.setState("TEST_CONFIRM_BOOKING"); + sendInteractiveButtons(getMessage("booking.test.confirm_pkg", state, test, price), new String[]{"TEST_BOOK", "TEST_BACK"}, new String[]{getMessage("booking.test.btn_book", state), getMessage("booking.test.btn_back", state)}); + return null; + } + + private String handleWomensWellness(UserState state, String input) { + String test = ""; String price = "₹1,200"; + switch(input) { + case "1": case "WOM_BASIC": test = "Women's Wellness Basic"; break; + case "2": case "WOM_ADV": test = "Women's Wellness Advanced"; break; + default: return "Invalid option."; + } + state.setSelectedItem(test); + state.setPrice(price); + state.setState("TEST_CONFIRM_BOOKING"); + sendInteractiveButtons(getMessage("booking.test.confirm_pkg", state, test, price), new String[]{"TEST_BOOK", "TEST_BACK"}, new String[]{getMessage("booking.test.btn_book", state), getMessage("booking.test.btn_back", state)}); + return null; + } + + private String handleTestConfirmBooking(UserState state, String input) { + if ("1".equals(input) || "TEST_BOOK".equals(input) || input.equalsIgnoreCase("Book Now")) { + state.setState("TEST_ASK_COLLECTION_TYPE"); + sendInteractiveButtons(getMessage("booking.test.ask_coll", state), new String[]{"COLL_HOME", "COLL_VISIT"}, new String[]{getMessage("booking.test.coll_home", state), getMessage("booking.test.coll_visit", state)}); + return null; + } else { + state.reset(); + return getMainMenuText(state); + } + } + + private String handleTestAskCollectionType(UserState state, String input) { + if ("1".equals(input) || "COLL_HOME".equals(input)) { + state.setSubCategory("Home Sample Collection"); + state.setState("TEST_ASK_ADDRESS"); + return getMessage("booking.ask_address", state); + } else if ("2".equals(input) || "COLL_VISIT".equals(input)) { + state.setSubCategory("Visit Laboratory"); + state.setAddress("Rajalakshmi Medical College Laboratory"); + state.setState("TEST_ASK_DATE"); + return getMessage("booking.test.ask_date", state); + } + return getMessage("invalid.option", state); + } + + private String handleTestAskAddress(UserState state, String input) { + state.setAddress(input); + state.setState("TEST_ASK_DATE"); + return getMessage("booking.test.ask_date", state); + } + + private String handleTestAskDate(UserState state, String input) { + state.setBookingDate(input); + state.setState("TEST_ASK_TIME"); + sendPaginatedButtons(state, getMessage("booking.test.ask_time.title", state), new String[]{"SLOT_7AM", "SLOT_9AM", "SLOT_11AM"}, new String[]{getMessage("booking.test.opt1", state), getMessage("booking.test.opt2", state), getMessage("booking.test.opt3", state)}); + return null; + } + + private String handleTestAskTime(UserState state, String input) { + String time = input; + switch(input) { + case "1": case "SLOT_7AM": time = "7 AM – 9 AM"; break; + case "2": case "SLOT_9AM": time = "9 AM – 11 AM"; break; + case "3": case "SLOT_11AM": time = "11 AM – 1 PM"; break; + } + state.setBookingTime(time); + + // Save DB + TestBooking booking = new TestBooking(); + booking.setPhoneNumber(state.getPhoneNumber()); + String bookingId = "LAB" + (1000000 + new Random().nextInt(9000000)); + booking.setBookingId(bookingId); + booking.setPatientId(state.getPatientId()); + booking.setPackageName(state.getSelectedItem()); + booking.setPrice(state.getPrice()); + booking.setCollectionType(state.getSubCategory()); + booking.setAddress(state.getAddress()); + booking.setBookingDate(state.getBookingDate()); + booking.setBookingTime(state.getBookingTime()); + booking.setStatus("CONFIRMED"); + testBookingRepository.save(booking); + + state.setState("FINAL_MAIN_MENU"); + return getMessage("booking.test.success", state, bookingId, state.getPatientId(), state.getSelectedItem(), state.getPrice(), state.getSubCategory(), state.getBookingDate(), state.getBookingTime()) + "\n\n" + getFinalMainMenuText(state); + } + + // ========================================== + private String handleDoctorMenu(UserState state, String input) { + switch(input) { + case "1": case "DOC_NEW_APP": + state.setState("BOOK_NEW_APPOINTMENT_DEPT"); + sendPaginatedButtons(state, getMessage("doc.dept.title", state), new String[]{"DEPT_GEN_MED", "DEPT_CARDIO", "DEPT_ORTHO", "DEPT_NEURO", "DEPT_PEDS", "DEPT_GYNO", "DEPT_DERM", "DEPT_ENT", "DEPT_OPHTH"}, new String[]{getMessage("doc.dept.opt1", state), getMessage("doc.dept.opt2", state), getMessage("doc.dept.opt3", state), getMessage("doc.dept.opt4", state), getMessage("doc.dept.opt5", state), getMessage("doc.dept.opt6", state), getMessage("doc.dept.opt7", state), getMessage("doc.dept.opt8", state), getMessage("doc.dept.opt9", state)}); + return null; + case "2": case "DOC_VIEW_APP": + return listAppointments(state); + case "3": case "DOC_RESCHEDULE": + state.setState("RESCHEDULE_ASK_ID"); + return getMessage("doc.reschedule.ask", state); + case "4": case "DOC_CANCEL": + state.setState("CANCEL_ASK_ID"); + return getMessage("doc.cancel.ask", state); + default: + sendPaginatedButtons(state, getMessage("doc.main.title", state), new String[]{"DOC_NEW_APP", "DOC_VIEW_APP", "DOC_RESCHEDULE", "DOC_CANCEL"}, new String[]{getMessage("doc.main.opt1", state), getMessage("doc.main.opt2", state), getMessage("doc.main.opt3", state), getMessage("doc.main.opt4", state)}); + return null; + } + } + + private String handleBookAppointmentDept(UserState state, String input) { + String dept = ""; + switch(input) { + case "1": case "DEPT_GEN_MED": dept = "General Medicine"; break; + case "2": case "DEPT_CARDIO": dept = "Cardiology"; break; + case "3": case "DEPT_ORTHO": dept = "Orthopedics"; break; + case "4": case "DEPT_NEURO": dept = "Neurology"; break; + case "5": case "DEPT_PEDS": dept = "Pediatrics"; break; + case "6": case "DEPT_GYNO": dept = "Gynecology"; break; + case "7": case "DEPT_DERM": dept = "Dermatology"; break; + case "8": case "DEPT_ENT": dept = "ENT"; break; + case "9": case "DEPT_OPHTH": dept = "Ophthalmology"; break; + default: return "Invalid option."; + } + state.setCategory(dept); + state.setState("DOCTOR_SELECTION"); + sendPaginatedButtons(state, dept + "\n\n" + getMessage("doc.sel.title", state), new String[]{"DOC_RAJESH", "DOC_PRIYA", "DOC_ARAVIND"}, new String[]{"Dr. Rajesh Kumar", "Dr. Priya Sharma", "Dr. Aravind"}); + return null; + } + + private String handleDoctorSelection(UserState state, String input) { + String doc = ""; + switch(input) { + case "1": case "DOC_RAJESH": doc = "Dr. Rajesh Kumar"; break; + case "2": case "DOC_PRIYA": doc = "Dr. Priya Sharma"; break; + case "3": case "DOC_ARAVIND": doc = "Dr. Aravind"; break; + default: return "Invalid option."; + } + state.setSelectedItem(doc); + state.setState("DOCTOR_DATE_SELECTION"); + sendPaginatedButtons(state, doc + "\n" + getMessage("doc.date.title", state), new String[]{"DATE_25_JUL", "DATE_26_JUL", "DATE_27_JUL"}, new String[]{"25 July 2026", "26 July 2026", "27 July 2026"}); + return null; + } + + private String handleDoctorDateSelection(UserState state, String input) { + String date = ""; + switch(input) { + case "1": case "DATE_25_JUL": date = "25 July 2026"; break; + case "2": case "DATE_26_JUL": date = "26 July 2026"; break; + case "3": case "DATE_27_JUL": date = "27 July 2026"; break; + default: date = input; break; + } + state.setBookingDate(date); + state.setState("DOCTOR_TIME_SELECTION"); + sendPaginatedButtons(state, getMessage("doc.time.title", state), new String[]{"TIME_9AM", "TIME_10AM", "TIME_11_30AM", "TIME_2PM", "TIME_4PM"}, new String[]{"09:00 AM", "10:00 AM", "11:30 AM", "02:00 PM", "04:00 PM"}); + return null; + } + + private String handleDoctorTimeSelection(UserState state, String input) { + String time = ""; + switch(input) { + case "1": case "TIME_9AM": time = "09:00 AM"; break; + case "2": case "TIME_10AM": time = "10:00 AM"; break; + case "3": case "TIME_11_30AM": time = "11:30 AM"; break; + case "4": case "TIME_2PM": time = "02:00 PM"; break; + case "5": case "TIME_4PM": time = "04:00 PM"; break; + default: time = input; break; + } + state.setBookingTime(time); + state.setPrice("₹600"); + state.setState("APPOINTMENT_SUMMARY"); + sendPaginatedButtons(state, getMessage("doc.summary.title", state) + "\n\nDoctor:\n" + state.getSelectedItem() + "\n\n🏥 Department:\n" + state.getCategory() + "\n\n📅 Date:\n" + state.getBookingDate() + "\n\n🕑 Time:\n" + state.getBookingTime() + "\n\nConsultation Fee:\n" + state.getPrice(), new String[]{"APT_CONFIRM", "APT_CHANGE_TIME", "APT_CANCEL"}, new String[]{getMessage("doc.summary.confirm", state), getMessage("doc.summary.change", state), getMessage("doc.summary.cancel", state)}); + return null; + } + + private String handleAppointmentSummary(UserState state, String input) { + if ("1".equals(input) || "APT_CONFIRM".equals(input)) { + // Save DB + Appointment app = new Appointment(); + app.setPhoneNumber(state.getPhoneNumber()); + String appId = "APT" + (100000 + new Random().nextInt(900000)); + app.setAppointmentId(appId); + app.setDepartment(state.getCategory()); + app.setDoctor(state.getSelectedItem()); + app.setAppointmentDate(state.getBookingDate()); + app.setAppointmentTime(state.getBookingTime()); + app.setFee(state.getPrice()); + app.setPatientId(state.getPatientId()); + app.setStatus("Confirmed"); + appointmentRepository.save(app); + + state.setState("FINAL_MAIN_MENU"); + return getMessage("doc.booking.success", state, appId, state.getPatientId(), state.getSelectedItem(), state.getCategory(), state.getBookingDate(), state.getBookingTime()) + "\n\n" + getFinalMainMenuText(state); + } else if ("2".equals(input) || "APT_CHANGE_TIME".equals(input)) { + state.setState("DOCTOR_TIME_SELECTION"); + sendPaginatedButtons(state, getMessage("doc.time.title", state), new String[]{"TIME_9AM", "TIME_10AM", "TIME_11_30AM", "TIME_2PM", "TIME_4PM"}, new String[]{"09:00 AM", "10:00 AM", "11:30 AM", "02:00 PM", "04:00 PM"}); + return null; + } else { + state.reset(); + return getMainMenuText(state); + } + } + + private String listAppointments(UserState state) { + List list = appointmentRepository.findByPhoneNumber(state.getPhoneNumber()); + if (list.isEmpty()) { + state.reset(); + return getMessage("doc.no_apps", state) + "\n\n" + getMainMenuText(state); + } + StringBuilder sb = new StringBuilder(getMessage("doc.view_apps.title", state) + "\n\n"); + for (Appointment app : list) { + sb.append(getMessage("doc.view_apps.item", state, app.getAppointmentId(), app.getDoctor(), app.getDepartment(), app.getAppointmentDate(), app.getAppointmentTime(), app.getStatus())).append("\n\n"); + } + state.setState("VIEW_APPOINTMENTS"); + sendPaginatedButtons(state, sb.toString(), new String[]{"VIEW_CANCEL", "VIEW_RESCHEDULE", "VIEW_MAIN_MENU"}, new String[]{getMessage("doc.view_apps.opt1", state), getMessage("doc.view_apps.opt2", state), getMessage("doc.view_apps.opt3", state)}); + return null; + } + + private String handleViewAppointments(UserState state, String input) { + if ("1".equals(input) || "VIEW_CANCEL".equals(input)) { + state.setState("CANCEL_ASK_ID"); + return getMessage("doc.cancel.ask", state); + } else if ("2".equals(input) || "VIEW_RESCHEDULE".equals(input)) { + state.setState("RESCHEDULE_ASK_ID"); + return getMessage("doc.reschedule.ask", state); + } else { + state.setState("FINAL_MAIN_MENU"); + return getFinalMainMenuText(state); + } + } + + private String handleRescheduleAskId(UserState state, String input) { + Optional optApp = appointmentRepository.findByAppointmentId(input); + if (optApp.isPresent()) { + state.setSubCategory(input); // store ID + state.setState("RESCHEDULE_ASK_TIME"); + sendPaginatedButtons(state, getMessage("doc.reschedule.time.title", state), new String[]{"RESCHED_9AM", "RESCHED_11AM", "RESCHED_3PM"}, new String[]{"09:00 AM", "11:00 AM", "03:00 PM"}); + return null; + } + return getMessage("doc.invalid_id", state); + } + + private String handleRescheduleAskTime(UserState state, String input) { + String time = ""; + switch(input) { + case "1": case "RESCHED_9AM": time = "09:00 AM"; break; + case "2": case "RESCHED_11AM": time = "11:00 AM"; break; + case "3": case "RESCHED_3PM": time = "03:00 PM"; break; + default: time = input; break; + } + Optional optApp = appointmentRepository.findByAppointmentId(state.getSubCategory()); + if (optApp.isPresent()) { + Appointment app = optApp.get(); + app.setAppointmentTime(time); + appointmentRepository.save(app); + state.setState("FINAL_MAIN_MENU"); + return getMessage("doc.reschedule.success", state, app.getDoctor(), time, app.getAppointmentDate()) + "\n\n" + getFinalMainMenuText(state); + } + return getMessage("error.generic", state); + } + + private String handleCancelAskId(UserState state, String input) { + Optional optApp = appointmentRepository.findByAppointmentId(input); + if (optApp.isPresent()) { + state.setSubCategory(input); // store ID + state.setState("CANCEL_CONFIRM"); + sendInteractiveButtons(getMessage("doc.cancel.confirm_msg", state), new String[]{"CANCEL_YES", "CANCEL_NO"}, new String[]{getMessage("doc.cancel.yes", state), getMessage("doc.cancel.no", state)}); + return null; + } + return getMessage("doc.invalid_id", state); + } + + private String handleCancelConfirm(UserState state, String input) { + if ("1".equals(input) || input.equalsIgnoreCase("Yes") || "CANCEL_YES".equals(input)) { + Optional optApp = appointmentRepository.findByAppointmentId(state.getSubCategory()); + if (optApp.isPresent()) { + Appointment app = optApp.get(); + app.setStatus("Cancelled"); + appointmentRepository.save(app); + state.setState("FINAL_MAIN_MENU"); + return "✅ Appointment Cancelled Successfully.\nIf applicable, your refund will be processed within 5–7 working days.\nThank you.\n\n" + getFinalMainMenuText(state); + } + } + state.setState("FINAL_MAIN_MENU"); + return "Cancellation aborted.\n\n" + getFinalMainMenuText(state); + } + + // ========================================== + // FINAL MAIN MENU + // ========================================== + private String getFinalMainMenuText(UserState state) { + return "Rajalakshmi Medical College\n\nWhat would you like to do next?\nReply 0 to restart the conversation."; + } + + private String handleFinalMainMenu(UserState state, String input) { + return handleMainMenu(state, input); + } + + + // ========================================== + // SENDER FUNCTION + // ========================================== + private void sendLanguageSelectionMessage(String phoneNumberId, String recipientPhoneNumber) { + String url = "https://graph.facebook.com/v20.0/" + phoneNumberId + "/messages"; + + try { + ObjectNode requestBody = objectMapper.createObjectNode(); + requestBody.put("messaging_product", "whatsapp"); + requestBody.put("recipient_type", "individual"); + requestBody.put("to", recipientPhoneNumber); + requestBody.put("type", "interactive"); + + ObjectNode interactiveNode = objectMapper.createObjectNode(); + interactiveNode.put("type", "button"); + + ObjectNode bodyNode = objectMapper.createObjectNode(); + bodyNode.put("text", "Welcome to RMC Hospital! Please select your preferred language. / RMC மருத்துவமனைக்கு உங்களை வரவேற்கிறோம்! உங்கள் மொழியைத் தேர்ந்தெடுக்கவும்."); + interactiveNode.set("body", bodyNode); + + ObjectNode actionNode = objectMapper.createObjectNode(); + com.fasterxml.jackson.databind.node.ArrayNode buttonsArray = actionNode.putArray("buttons"); + + ObjectNode btn1 = objectMapper.createObjectNode(); + btn1.put("type", "reply"); + ObjectNode reply1 = objectMapper.createObjectNode(); + reply1.put("id", "LANG_EN"); + reply1.put("title", "English"); + btn1.set("reply", reply1); + buttonsArray.add(btn1); + + ObjectNode btn2 = objectMapper.createObjectNode(); + btn2.put("type", "reply"); + ObjectNode reply2 = objectMapper.createObjectNode(); + reply2.put("id", "LANG_TA"); + reply2.put("title", "தமிழ்"); + btn2.set("reply", reply2); + buttonsArray.add(btn2); + + interactiveNode.set("action", actionNode); + requestBody.set("interactive", interactiveNode); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(accessToken); + + HttpEntity entity = new HttpEntity<>(requestBody.toString(), headers); + logger.info("Sending interactive message to {}", recipientPhoneNumber); + restTemplate.postForEntity(url, entity, String.class); + } catch (Exception e) { + logger.error("Error sending interactive message", e); + } + } + + private void sendMainMenuInteractiveMessage(String phoneNumberId, String recipientPhoneNumber, String languagePref) { + String url = "https://graph.facebook.com/v20.0/" + phoneNumberId + "/messages"; + + try { + java.util.Locale locale = new java.util.Locale(languagePref != null ? languagePref : "en"); + String bodyText = messageSource.getMessage("welcome.body", null, locale); + String btn1Title = messageSource.getMessage("menu.btn.services", null, locale); + String btn2Title = messageSource.getMessage("menu.btn.tests", null, locale); + String btn3Title = messageSource.getMessage("menu.btn.booking", null, locale); + + ObjectNode requestBody = objectMapper.createObjectNode(); + requestBody.put("messaging_product", "whatsapp"); + requestBody.put("recipient_type", "individual"); + requestBody.put("to", recipientPhoneNumber); + requestBody.put("type", "interactive"); + + ObjectNode interactiveNode = objectMapper.createObjectNode(); + interactiveNode.put("type", "button"); + + ObjectNode bodyNode = objectMapper.createObjectNode(); + bodyNode.put("text", bodyText); + interactiveNode.set("body", bodyNode); + + ObjectNode actionNode = objectMapper.createObjectNode(); + com.fasterxml.jackson.databind.node.ArrayNode buttonsArray = actionNode.putArray("buttons"); + + ObjectNode btn1 = objectMapper.createObjectNode(); + btn1.put("type", "reply"); + ObjectNode reply1 = objectMapper.createObjectNode(); + reply1.put("id", "MENU_SERVICES"); + reply1.put("title", btn1Title); + btn1.set("reply", reply1); + buttonsArray.add(btn1); + + ObjectNode btn2 = objectMapper.createObjectNode(); + btn2.put("type", "reply"); + ObjectNode reply2 = objectMapper.createObjectNode(); + reply2.put("id", "MENU_TESTS"); + reply2.put("title", btn2Title); + btn2.set("reply", reply2); + buttonsArray.add(btn2); + + ObjectNode btn3 = objectMapper.createObjectNode(); + btn3.put("type", "reply"); + ObjectNode reply3 = objectMapper.createObjectNode(); + reply3.put("id", "MENU_BOOKING"); + reply3.put("title", btn3Title); + btn3.set("reply", reply3); + buttonsArray.add(btn3); + + interactiveNode.set("action", actionNode); + requestBody.set("interactive", interactiveNode); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(accessToken); + + HttpEntity entity = new HttpEntity<>(requestBody.toString(), headers); + logger.info("Sending Main Menu interactive message to {}", recipientPhoneNumber); + restTemplate.postForEntity(url, entity, String.class); + } catch (Exception e) { + logger.error("Error sending main menu interactive message", e); + } + } + + private void sendMessage(String phoneNumberId, String recipientPhoneNumber, String text) { + String url = "https://graph.facebook.com/v20.0/" + phoneNumberId + "/messages"; + + try { + ObjectNode requestBody = objectMapper.createObjectNode(); + requestBody.put("messaging_product", "whatsapp"); + requestBody.put("recipient_type", "individual"); + requestBody.put("to", recipientPhoneNumber); + requestBody.put("type", "text"); + + ObjectNode textNode = objectMapper.createObjectNode(); + textNode.put("preview_url", false); + textNode.put("body", text); + requestBody.set("text", textNode); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(accessToken); + + HttpEntity entity = new HttpEntity<>(requestBody.toString(), headers); + + logger.info("Sending reply to {}: {}", recipientPhoneNumber, text); + ResponseEntity response = restTemplate.postForEntity(url, entity, String.class); + logger.info("Response from WhatsApp API: Status={}", response.getStatusCode()); + } catch (Exception e) { + logger.error("Failed to send WhatsApp message to " + recipientPhoneNumber, e); + } + } + + private void sendInteractiveButtons(String bodyText, String[] ids, String[] titles) { + String phoneNumberId = currentPhoneId.get(); + String recipientPhoneNumber = currentFrom.get(); + if (phoneNumberId == null || recipientPhoneNumber == null) return; + String url = "https://graph.facebook.com/v20.0/" + phoneNumberId + "/messages"; + try { + ObjectNode requestBody = objectMapper.createObjectNode(); + requestBody.put("messaging_product", "whatsapp"); + requestBody.put("recipient_type", "individual"); + requestBody.put("to", recipientPhoneNumber); + requestBody.put("type", "interactive"); + + ObjectNode interactiveNode = objectMapper.createObjectNode(); + interactiveNode.put("type", "button"); + + ObjectNode bodyNode = objectMapper.createObjectNode(); + bodyNode.put("text", bodyText); + interactiveNode.set("body", bodyNode); + + ObjectNode actionNode = objectMapper.createObjectNode(); + com.fasterxml.jackson.databind.node.ArrayNode buttonsArray = actionNode.putArray("buttons"); + + for (int i = 0; i < ids.length; i++) { + ObjectNode btn = objectMapper.createObjectNode(); + btn.put("type", "reply"); + ObjectNode reply = objectMapper.createObjectNode(); + reply.put("id", ids[i]); + reply.put("title", titles[i]); + btn.set("reply", reply); + buttonsArray.add(btn); + } + + interactiveNode.set("action", actionNode); + requestBody.set("interactive", interactiveNode); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(accessToken); + + HttpEntity entity = new HttpEntity<>(requestBody.toString(), headers); + restTemplate.postForEntity(url, entity, String.class); + } catch (Exception e) { + logger.error("Error sending interactive buttons", e); + } + } + + private void sendPaginatedButtons(UserState state, String bodyText, String[] ids, String[] titles) { + int totalOptions = ids.length; + if (totalOptions <= 3) { + sendInteractiveButtons(bodyText, ids, titles); + return; + } + + int page = state.getMenuPage(); + int maxItemsPerPage = 2; + int startIndex = page * maxItemsPerPage; + + if (startIndex >= totalOptions) { + startIndex = 0; + state.setMenuPage(0); + } + + int endIndex = Math.min(startIndex + maxItemsPerPage, totalOptions); + boolean hasMore = (endIndex < totalOptions) || (totalOptions > 3); + + int buttonCount = endIndex - startIndex; + if (hasMore) buttonCount++; + + String[] pageIds = new String[buttonCount]; + String[] pageTitles = new String[buttonCount]; + + for (int i = 0; i < endIndex - startIndex; i++) { + pageIds[i] = ids[startIndex + i]; + pageTitles[i] = titles[startIndex + i]; + } + + if (hasMore) { + pageIds[buttonCount - 1] = "NEXT_PAGE"; + pageTitles[buttonCount - 1] = getMessage("pagination.next", state); + } + + sendInteractiveButtons(bodyText, pageIds, pageTitles); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..b8606b8 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,17 @@ +spring.application.name=whatsapp-automation +server.port=8085 + +whatsapp.access-token=EAAOZCONMSXCkBSIUAa6NJVDZAgN6yC6DyrsBefMhijoVWZA7613gQecnXtvEYFZASHR933rDPh3fjnLzUqbS8Qgbcy0bfZCGTDyq3ceB2zA4c3ZBn4LzYZC622K1PRQGWGyuuESnTk3cwGeJE6IsesTAcJZB7wtwbkvHGLJAVqtqo41HCLWjrCHjZCI12NrFmmgPQiZCEx8mz3fBMJLEOovhVZAvGZCwTOj0agXfaeUXu3sFGHlLN3ZCO3lIEhUkNMu0bcgv02nhUCWYqbJJCZBKJNmGpygDcZD +whatsapp.verify-token=my_whatsapp_verify_token_123 + +spring.datasource.url=jdbc:postgresql://localhost:5432/rmc_bot +spring.datasource.username=postgres +spring.datasource.password=postgres +spring.jpa.hibernate.ddl-auto=update +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect +spring.jpa.show-sql=true + + + + +spring.messages.encoding=UTF-8 diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties new file mode 100644 index 0000000..935bcae --- /dev/null +++ b/src/main/resources/messages.properties @@ -0,0 +1,8 @@ +welcome.body=👋 Welcome to Rajalakshmi Medical College! I'm your virtual healthcare assistant. Please choose one of the following options: +menu.btn.services=My Services +menu.btn.tests=My Tests +menu.btn.booking=Book Appointment +menu.services=🩺 My Services\n\nPlease choose a category:\n1️⃣ Nursing Services\n2️⃣ Attender Services\n3️⃣ Physiotherapy\n4️⃣ Medical Procedures\n5️⃣ Digital Consultation\n6️⃣ Home Care Packages\n\nReply with the option number. +menu.tests=🏥 Rajalakshmi Medical College\nWelcome to Laboratory Services.\n\nPlease choose a Health Package Category.\n1️⃣ Fever Panel Tests\n2️⃣ Heart Health\n3️⃣ Joint & Bone\n4️⃣ Master Health Check Plans\n5️⃣ Pregnancy\n6️⃣ Sugar Control Series\n7️⃣ Women's Wellness Plans\n8️⃣ Search Test\n\nReply with the option number. +menu.doctor=👨‍⚕️ Doctor Appointment\nPlease choose an option\n1️⃣ Book New Appointment\n2️⃣ View My Appointments\n3️⃣ Reschedule Appointment\n4️⃣ Cancel Appointment\n\nReply with the option number. +invalid.option=Invalid option. Please try again. diff --git a/src/main/resources/messages_en.properties b/src/main/resources/messages_en.properties new file mode 100644 index 0000000..66ec0b3 --- /dev/null +++ b/src/main/resources/messages_en.properties @@ -0,0 +1,209 @@ +welcome.body=👋 Welcome to Rajalakshmi Medical College! I'm your virtual healthcare assistant. Please choose one of the following options: +menu.btn.services=My Services +menu.btn.tests=My Tests +menu.btn.booking=Book Appointment +menu.services=🩺 My Services\n\nPlease choose a category:\n1️⃣ Nursing Services\n2️⃣ Attender Services\n3️⃣ Physiotherapy\n4️⃣ Medical Procedures\n5️⃣ Digital Consultation\n6️⃣ Home Care Packages\n\nReply with the option number. +menu.tests=🏥 Rajalakshmi Medical College\nWelcome to Laboratory Services.\n\nPlease choose a Health Package Category.\n1️⃣ Fever Panel Tests\n2️⃣ Heart Health\n3️⃣ Joint & Bone\n4️⃣ Master Health Check Plans\n5️⃣ Pregnancy\n6️⃣ Sugar Control Series\n7️⃣ Women's Wellness Plans\n8️⃣ Search Test\n\nReply with the option number. +menu.doctor=👨‍⚕️ Doctor Appointment\nPlease choose an option\n1️⃣ Book New Appointment\n2️⃣ View My Appointments\n3️⃣ Reschedule Appointment\n4️⃣ Cancel Appointment\n\nReply with the option number. +invalid.option=Invalid option. Please try again. + +# Tests Menu +tests.main.title=🩺 My Tests\n\nPlease choose a category: +tests.main.btn=View Tests +tests.main.list=Test Categories +tests.main.opt1=Fever Panel +tests.main.opt2=Heart Health +tests.main.opt3=Joint & Bone +tests.main.opt4=Master Health +tests.main.opt5=Pregnancy +tests.main.opt6=Sugar Control +tests.main.opt7=Women's Wellness + +tests.fever.title=🌡️ Fever Panel Tests +tests.fever.btn=View Tests +tests.fever.list=Tests +tests.fever.opt1=Fever Ultra Panel +tests.fever.opt2=CBC +tests.fever.opt3=IgE Total Serum +tests.fever.opt4=LFT +tests.fever.opt5=Stool Occult Blood +tests.fever.desc1=₹1,999 (11 Tests) +tests.fever.desc2=₹300 +tests.fever.desc3=₹400 +tests.fever.desc4=₹500 +tests.fever.desc5=₹250 + +tests.heart.title=❤️ Heart Health Packages +tests.heart.btn=View Packages +tests.heart.list=Packages +tests.heart.opt1=Heart Health Plus +tests.heart.opt2=Amaya Int Cardiac +tests.heart.opt3=Cardiac Pkg - Amaya +tests.heart.opt4=Healthy Heart Basic +tests.heart.opt5=Heart Basic +tests.heart.opt6=Heart Master +tests.heart.opt7=Hypertension Comp + +tests.joint.title=🦴 Joint & Bone Packages +tests.joint.opt1=Joint Care Plus +tests.joint.opt2=Arthritis Package + +tests.master.title=🩺 Master Health Check Plans +tests.master.btn=View Plans +tests.master.list=Plans +tests.master.opt1=Master Health Basic +tests.master.opt2=Master Health Exec +tests.master.opt3=Master Health Prem + +tests.preg.title=🤰 Pregnancy Packages +tests.preg.opt1=Pregnancy Basic +tests.preg.opt2=Pregnancy Advanced + +tests.sugar.title=🩸 Sugar Control Series +tests.sugar.btn=View Packages +tests.sugar.list=Packages +tests.sugar.opt1=Sugar Control Basic +tests.sugar.opt2=Sugar Control Plus +tests.sugar.opt3=Diabetes Monitoring +tests.sugar.opt4=HbA1c Comprehensive + +tests.women.title=👩 Women's Wellness Plans +tests.women.opt1=Wellness Basic +tests.women.opt2=Wellness Advanced + +# Doctor Menu +doc.main.title=🩺 Doctor Appointments +doc.main.btn=View Options +doc.main.list=Options +doc.main.opt1=Book New Appointment +doc.main.opt2=View Appointments +doc.main.opt3=Reschedule Appointment +doc.main.opt4=Cancel Appointment + +doc.dept.title=Please select a department. +doc.dept.btn=View Departments +doc.dept.list=Departments +doc.dept.opt1=General Medicine +doc.dept.opt2=Cardiology +doc.dept.opt3=Orthopedics +doc.dept.opt4=Neurology +doc.dept.opt5=Pediatrics +doc.dept.opt6=Gynecology +doc.dept.opt7=Dermatology +doc.dept.opt8=ENT +doc.dept.opt9=Ophthalmology + +doc.sel.title=Available Doctors +doc.sel.btn=Select Doctor +doc.sel.list=Doctors + +doc.date.title=Available Dates +doc.date.btn=Select Date +doc.date.list=Dates + +doc.time.title=Available Time Slots +doc.time.btn=Select Time +doc.time.list=Time Slots + +doc.summary.title=Appointment Summary +doc.summary.btn=Select Option +doc.summary.list=Options +doc.summary.confirm=Confirm Booking +doc.summary.change=Change Time +doc.summary.cancel=Cancel + +doc.reschedule.ask=Please enter your Appointment ID. +doc.cancel.ask=Please enter your Appointment ID. +doc.no_apps=You have no upcoming appointments. + +# Confirmations +booking.service.confirm=✅ Booking Confirmed\nService: {0}\nAmount: {1}\n\nPlease share:\n📍 Service Address\nor\n📍 Live Location +booking.ask_address=Please enter your address. +booking.ask_date=Thank you.\nPlease reply with your preferred date.\n\n📅 DD/MM/YYYY +booking.ask_time.title=Please choose the preferred start time. +booking.ask_time.btn=Select Time +booking.ask_time.list=Available Slots +booking.ask_time.opt1=9 AM - 1 PM +booking.ask_time.opt2=1 PM - 5 PM +booking.ask_time.opt3=5 PM - 9 PM + +booking.service.success=✅ Service Booked Successfully\nBooking ID:\n{0}\nPatient ID:\n{1}\n\nPlease provide your Patient ID at the hospital billing desk for payment.\n\nOur nursing team will contact you shortly.\nThank you for choosing Rajalakshmi Medical College. + +booking.test.confirm_pkg=Selected Package\n\n{0}\nPrice:\n{1}\nWould you like to continue? +booking.test.btn_book=Book Now +booking.test.btn_back=Back +booking.test.ask_coll=How would you like to provide your sample? +booking.test.coll_home=Home Collection +booking.test.coll_visit=Visit Laboratory +booking.test.ask_date=Please reply with your preferred date.\n📅 DD/MM/YYYY +booking.test.ask_time.title=Choose your preferred time slot. +booking.test.ask_time.btn=Select Slot +booking.test.ask_time.list=Time Slots +booking.test.opt1=7 AM - 9 AM +booking.test.opt2=9 AM - 11 AM +booking.test.opt3=11 AM - 1 PM + +booking.test.success=✅ Booking Successful\nBooking ID:\n{0}\nPatient ID:\n{1}\n\nPackage:\n{2}\nAmount:\n{3}\n\nPlease provide your Patient ID at the hospital billing desk for payment.\n\nCollection:\n{4}\nTechnician Visit\n{5}\n{6}\n\nThank you for choosing Rajalakshmi Medical College. + +# Final Main Menu +menu.final.title=Would you like to go back to the Main Menu? +menu.final.yes=Main Menu +menu.final.no=Exit + +doc.booking.success=✅ Appointment Booked Successfully\n\nAppointment ID:\n{0}\n\nPatient ID:\n{1}\n\nPlease provide your Patient ID at the hospital billing desk for payment.\n\nDoctor:\n{2}\n\nDepartment:\n{3}\n\nDate:\n{4}\n\nTime:\n{5}\n\nPlease arrive 15 minutes before your appointment.\nThank you for choosing Rajalakshmi Medical College +doc.view_apps.title=Your Upcoming Appointments +doc.view_apps.item=Appointment ID:\n{0}\nDoctor:\n{1}\nDepartment:\n{2}\nDate:\n{3}\nTime:\n{4}\nStatus:\n✅ {5} +doc.view_apps.btn=Select Option +doc.view_apps.list=Options +doc.view_apps.opt1=Cancel Appointment +doc.view_apps.opt2=Reschedule +doc.view_apps.opt3=Main Menu +doc.invalid_id=Invalid Appointment ID. Please try again. +doc.reschedule.time.title=Available New Time Slots +doc.reschedule.time.btn=Select Time +doc.reschedule.time.list=Time Slots +doc.reschedule.success=✅ Appointment Rescheduled\nDoctor:\n{0}\nNew Time:\n{1}\nDate:\n{2} +error.generic=Error. Please try again. +doc.cancel.confirm_msg=Are you sure you want to cancel? +doc.cancel.yes=Yes +doc.cancel.no=No +doc.cancel.success=✅ Appointment Cancelled Successfully. + +pagination.next=Next ➡️ +# Services Menu +services.nursing.title=💉 Nursing Services\n\nChoose: +services.nursing.opt1=Male Nurse +services.nursing.opt2=Female Nurse +services.attender.title=🤝 Attender Services +services.attender.opt1=Male Attender +services.attender.opt2=Female Attender +services.physio.title=🏃 Physiotherapy\n\nPrice: ₹700\n\nWould you like to book? +services.physio.yes=Yes +services.physio.no=No +services.medproc.title=🩺 Medical Procedures +services.medproc.btn=View Options +services.medproc.list=Procedures +services.medproc.opt1=IM Injection +services.medproc.opt2=IV Injection +services.medproc.opt3=Nebulization +services.medproc.opt4=Dressing Charges +services.medproc.opt5=Catheterization +services.digital.title=💻 Digital Consultation\n\nConsultation Fee: ₹750\n\nWould you like to schedule? +services.digital.yes=Yes +services.digital.back=Back +services.homecare.title=🏡 Home Care Packages +services.homecare.btn=View Services +services.homecare.list=Services +services.homecare.opt1=Nursing Care +services.homecare.opt2=Attender Care +services.homecare.opt3=Physiotherapy +services.homecare.opt4=Medical Procedures +services.main.title=🩺 My Services\n\nPlease choose a category: +services.main.btn=View Services +services.main.list=Service Categories +services.main.opt1=Nursing Services +services.main.opt2=Attender Services +services.main.opt3=Physiotherapy +services.main.opt4=Medical Procedures +services.main.opt5=Digital Consult +services.main.opt6=Home Care Packages diff --git a/src/main/resources/messages_ta.properties b/src/main/resources/messages_ta.properties new file mode 100644 index 0000000..86fdcd1 --- /dev/null +++ b/src/main/resources/messages_ta.properties @@ -0,0 +1,209 @@ +welcome.body=👋 ராஜலட்சுமி மருத்துவக் கல்லூரிக்கு வரவேற்கிறோம்! நான் உங்கள் மெய்நிகர் சுகாதார உதவியாளர். தயவுசெய்து கீழ்க்கண்ட விருப்பங்களில் ஒன்றைத் தேர்ந்தெடுக்கவும்: +menu.btn.services=என் சேவைகள் +menu.btn.tests=பரிசோதனைகள் +menu.btn.booking=முன்பதிவு செய்க +menu.services=🩺 எனது சேவைகள்\n\nதயவுசெய்து ஒரு வகையைத் தேர்ந்தெடுக்கவும்:\n1️⃣ செவிலியர் சேவைகள்\n2️⃣ உதவியாளர் சேவைகள்\n3️⃣ பிசியோதெரபி\n4️⃣ மருத்துவ நடைமுறைகள்\n5️⃣ டிஜிட்டல் ஆலோசனை\n6️⃣ வீட்டு பராமரிப்பு தொகுப்புகள்\n\nவிருப்ப எண்ணுடன் பதிலளிக்கவும். +menu.tests=🏥 ராஜலட்சுமி மருத்துவக் கல்லூரி\nஆய்வக சேவைகளுக்கு வரவேற்கிறோம்.\n\nசுகாதாரத் தொகுப்பு வகையைத் தேர்ந்தெடுக்கவும்.\n1️⃣ காய்ச்சல் பரிசோதனைகள்\n2️⃣ இதய ஆரோக்கியம்\n3️⃣ மூட்டு மற்றும் எலும்பு\n4️⃣ முழுமையான உடல் பரிசோதனை\n5️⃣ கர்ப்ப காலம்\n6️⃣ சர்க்கரை கட்டுப்பாடு\n7️⃣ பெண்கள் நல்வாழ்வு\n8️⃣ பரிசோதனையைத் தேடுங்கள்\n\nவிருப்ப எண்ணுடன் பதிலளிக்கவும். +menu.doctor=👨‍⚕️ மருத்துவர் சந்திப்பு\nதயவுசெய்து ஒரு விருப்பத்தைத் தேர்ந்தெடுக்கவும்\n1️⃣ புதிய சந்திப்பை முன்பதிவு செய்ய\n2️⃣ எனது சந்திப்புகளைப் பார்க்க\n3️⃣ சந்திப்பை மாற்றியமைக்க\n4️⃣ சந்திப்பை ரத்து செய்ய\n\nவிருப்ப எண்ணுடன் பதிலளிக்கவும். +invalid.option=தவறான விருப்பம். தயவுசெய்து மீண்டும் முயற்சிக்கவும். + +# Tests Menu +tests.main.title=🩺 எனது பரிசோதனைகள்\n\nதயவுசெய்து ஒரு வகையைத் தேர்ந்தெடுக்கவும்: +tests.main.btn=பரிசோதனைகளை காண்க +tests.main.list=பரிசோதனை பிரிவுகள் +tests.main.opt1=காய்ச்சல் பரிசோதனை +tests.main.opt2=இதய ஆரோக்கியம் +tests.main.opt3=மூட்டு & எலும்பு +tests.main.opt4=முழு உடல் பரிசோதனை +tests.main.opt5=கர்ப்ப காலம் +tests.main.opt6=சர்க்கரை கட்டுப்பாடு +tests.main.opt7=பெண்கள் நல்வாழ்வு + +tests.fever.title=🌡️ காய்ச்சல் பரிசோதனைகள் +tests.fever.btn=பரிசோதனைகளை காண்க +tests.fever.list=பரிசோதனைகள் +tests.fever.opt1=காய்ச்சல் அல்ட்ரா +tests.fever.opt2=CBC +tests.fever.opt3=IgE Total Serum +tests.fever.opt4=LFT +tests.fever.opt5=மலம் பரிசோதனை +tests.fever.desc1=₹1,999 (11 பரிசோதனைகள்) +tests.fever.desc2=₹300 +tests.fever.desc3=₹400 +tests.fever.desc4=₹500 +tests.fever.desc5=₹250 + +tests.heart.title=❤️ இதய ஆரோக்கிய தொகுப்புகள் +tests.heart.btn=தொகுப்புகளை காண்க +tests.heart.list=தொகுப்புகள் +tests.heart.opt1=ஹார்ட் ஹெல்த் பிளஸ் +tests.heart.opt2=அமயா கார்டியாக் +tests.heart.opt3=அமயா பேக்கேஜ் +tests.heart.opt4=ஹெல்தி ஹார்ட் பேசிக் +tests.heart.opt5=ஹார்ட் பேசிக் +tests.heart.opt6=ஹார்ட் மாஸ்டர் +tests.heart.opt7=உயர் ரத்த அழுத்தம் + +tests.joint.title=🦴 மூட்டு மற்றும் எலும்பு தொகுப்புகள் +tests.joint.opt1=ஜாயின்ட் கேர் பிளஸ் +tests.joint.opt2=ஆர்த்ரிடிஸ் பேக்கேஜ் + +tests.master.title=🩺 முழுமையான உடல் பரிசோதனை திட்டங்கள் +tests.master.btn=திட்டங்களை காண்க +tests.master.list=திட்டங்கள் +tests.master.opt1=மாஸ்டர் பேசிக் +tests.master.opt2=மாஸ்டர் எக்ஸிகியூட் +tests.master.opt3=மாஸ்டர் பிரீமியம் + +tests.preg.title=🤰 கர்ப்ப கால தொகுப்புகள் +tests.preg.opt1=கர்ப்ப காலம் பேசிக் +tests.preg.opt2=கர்ப்ப அட்வான்ஸ்டு + +tests.sugar.title=🩸 சர்க்கரை கட்டுப்பாடு தொடர் +tests.sugar.btn=தொகுப்புகளை காண்க +tests.sugar.list=தொகுப்புகள் +tests.sugar.opt1=சர்க்கரை பேசிக் +tests.sugar.opt2=சர்க்கரை பிளஸ் +tests.sugar.opt3=நீரிழிவு கண்காணிப்பு +tests.sugar.opt4=HbA1c விரிவானது + +tests.women.title=👩 பெண்கள் நல்வாழ்வு திட்டங்கள் +tests.women.opt1=பெண்கள் பேசிக் +tests.women.opt2=பெண்கள் அட்வான்ஸ்டு + +# Doctor Menu +doc.main.title=🩺 மருத்துவர் சந்திப்புகள் +doc.main.btn=விருப்பங்களை காண்க +doc.main.list=விருப்பங்கள் +doc.main.opt1=புதிய முன்பதிவு +doc.main.opt2=சந்திப்புகளை பார் +doc.main.opt3=மாற்றியமைக்க +doc.main.opt4=ரத்து செய்ய + +doc.dept.title=தயவுசெய்து ஒரு துறையை தேர்ந்தெடுக்கவும். +doc.dept.btn=துறைகளை காண்க +doc.dept.list=துறைகள் +doc.dept.opt1=பொது மருத்துவம் +doc.dept.opt2=இருதயவியல் +doc.dept.opt3=எலும்பியல் +doc.dept.opt4=நரம்பியல் +doc.dept.opt5=குழந்தை மருத்துவம் +doc.dept.opt6=மகப்பேறியல் +doc.dept.opt7=தோல் மருத்துவம் +doc.dept.opt8=காது மூக்கு தொண்டை +doc.dept.opt9=கண் மருத்துவம் + +doc.sel.title=கிடைக்கும் மருத்துவர்கள் +doc.sel.btn=மருத்துவரை தேர்ந்தெடுங்கள் +doc.sel.list=மருத்துவர்கள் + +doc.date.title=கிடைக்கும் தேதிகள் +doc.date.btn=தேதியை தேர்ந்தெடுங்கள் +doc.date.list=தேதிகள் + +doc.time.title=கிடைக்கும் நேரங்கள் +doc.time.btn=நேரத்தை தேர்ந்தெடுங்கள் +doc.time.list=நேரங்கள் + +doc.summary.title=சந்திப்பு சுருக்கம் +doc.summary.btn=விருப்பத்தை தேர்ந்தெடுங்கள் +doc.summary.list=விருப்பங்கள் +doc.summary.confirm=உறுதிப்படுத்துக +doc.summary.change=நேரத்தை மாற்றவும் +doc.summary.cancel=ரத்து செய்யவும் + +doc.reschedule.ask=தயவுசெய்து உங்கள் சந்திப்பு ஐடியை உள்ளிடவும். +doc.cancel.ask=தயவுசெய்து உங்கள் சந்திப்பு ஐடியை உள்ளிடவும். +doc.no_apps=உங்களுக்கு வரவிருக்கும் சந்திப்புகள் ஏதும் இல்லை. + +# Confirmations +booking.service.confirm=✅ முன்பதிவு உறுதி செய்யப்பட்டது\nசேவை: {0}\nகட்டணம்: {1}\n\nதயவுசெய்து பகிரவும்:\n📍 சேவை முகவரி\nஅல்லது\n📍 நேரலை இருப்பிடம் (Live Location) +booking.ask_address=தயவுசெய்து உங்கள் முகவரியை உள்ளிடவும். +booking.ask_date=நன்றி.\nஉங்கள் விருப்பமான தேதியை உள்ளிடவும்.\n\n📅 DD/MM/YYYY +booking.ask_time.title=விருப்பமான தொடக்க நேரத்தை தேர்வு செய்யவும். +booking.ask_time.btn=நேரத்தை தேர்ந்தெடுக்கவும் +booking.ask_time.list=கிடைக்கும் நேரங்கள் +booking.ask_time.opt1=காலை 9 - மதியம் 1 +booking.ask_time.opt2=மதியம் 1 - மாலை 5 +booking.ask_time.opt3=மாலை 5 - இரவு 9 + +booking.service.success=✅ சேவை வெற்றிகரமாக முன்பதிவு செய்யப்பட்டது\nமுன்பதிவு எண்:\n{0}\nநோயாளி எண்:\n{1}\n\nபணம் செலுத்த உங்கள் நோயாளி எண்ணை மருத்துவமனை பில்லிங் டெஸ்கில் வழங்கவும்.\n\nஎங்கள் செவிலியர் குழு உங்களை விரைவில் தொடர்புகொள்ளும்.\nராஜலட்சுமி மருத்துவக் கல்லூரியை தேர்ந்தெடுத்ததற்கு நன்றி. + +booking.test.confirm_pkg=தேர்ந்தெடுக்கப்பட்ட தொகுப்பு\n\n{0}\nவிலை:\n{1}\nநீங்கள் தொடர விரும்புகிறீர்களா? +booking.test.btn_book=முன்பதிவு செய் +booking.test.btn_back=பின்செல் +booking.test.ask_coll=மாதிரியை எவ்வாறு வழங்க விரும்புகிறீர்கள்? +booking.test.coll_home=வீட்டு மாதிரி சேகரிப்பு +booking.test.coll_visit=ஆய்வகத்திற்கு வருகை +booking.test.ask_date=உங்கள் விருப்பமான தேதியை உள்ளிடவும்.\n📅 DD/MM/YYYY +booking.test.ask_time.title=விருப்பமான நேரத்தை தேர்வு செய்யவும். +booking.test.ask_time.btn=நேரத்தை தேர்ந்தெடுக்கவும் +booking.test.ask_time.list=நேரங்கள் +booking.test.opt1=காலை 7 - காலை 9 +booking.test.opt2=காலை 9 - காலை 11 +booking.test.opt3=காலை 11 - மதியம் 1 + +booking.test.success=✅ முன்பதிவு வெற்றிகரமாக முடிந்தது\nமுன்பதிவு எண்:\n{0}\nநோயாளி எண்:\n{1}\n\nதொகுப்பு:\n{2}\nகட்டணம்:\n{3}\n\nபணம் செலுத்த உங்கள் நோயாளி எண்ணை மருத்துவமனை பில்லிங் டெஸ்கில் வழங்கவும்.\n\nசேகரிப்பு:\n{4}\nதொழில்நுட்ப வல்லுநர் வருகை\n{5}\n{6}\n\nராஜலட்சுமி மருத்துவக் கல்லூரியை தேர்ந்தெடுத்ததற்கு நன்றி. + +# Final Main Menu +menu.final.title=நீங்கள் பிரதான மெனுவுக்குச் செல்ல விரும்புகிறீர்களா? +menu.final.yes=பிரதான மெனு +menu.final.no=வெளியேறு + +doc.booking.success=✅ சந்திப்பு வெற்றிகரமாக முன்பதிவு செய்யப்பட்டது\n\nசந்திப்பு ஐடி:\n{0}\n\nநோயாளி ஐடி:\n{1}\n\nபணம் செலுத்த உங்கள் நோயாளி எண்ணை மருத்துவமனை பில்லிங் டெஸ்கில் வழங்கவும்.\n\nமருத்துவர்:\n{2}\n\nதுறை:\n{3}\n\nதேதி:\n{4}\n\nநேரம்:\n{5}\n\nஉங்கள் சந்திப்புக்கு 15 நிமிடங்களுக்கு முன்பு வரவும்.\nராஜலட்சுமி மருத்துவக் கல்லூரியை தேர்ந்தெடுத்ததற்கு நன்றி +doc.view_apps.title=உங்கள் வரவிருக்கும் சந்திப்புகள் +doc.view_apps.item=சந்திப்பு ஐடி:\n{0}\nமருத்துவர்:\n{1}\nதுறை:\n{2}\nதேதி:\n{3}\nநேரம்:\n{4}\nநிலை:\n✅ {5} +doc.view_apps.btn=விருப்பத்தை தேர்ந்தெடுக்கவும் +doc.view_apps.list=விருப்பங்கள் +doc.view_apps.opt1=ரத்து செய்யவும் +doc.view_apps.opt2=மாற்றியமைக்க +doc.view_apps.opt3=பிரதான மெனு +doc.invalid_id=தவறான சந்திப்பு ஐடி. தயவுசெய்து மீண்டும் முயற்சிக்கவும். +doc.reschedule.time.title=கிடைக்கும் புதிய நேரங்கள் +doc.reschedule.time.btn=நேரத்தை தேர்ந்தெடுக்கவும் +doc.reschedule.time.list=நேரங்கள் +doc.reschedule.success=✅ சந்திப்பு மாற்றியமைக்கப்பட்டது\nமருத்துவர்:\n{0}\nபுதிய நேரம்:\n{1}\nதேதி:\n{2} +error.generic=பிழை. தயவுசெய்து மீண்டும் முயற்சிக்கவும். +doc.cancel.confirm_msg=நிச்சயமாக ரத்து செய்ய விரும்புகிறீர்களா? +doc.cancel.yes=ஆம் +doc.cancel.no=இல்லை +doc.cancel.success=✅ சந்திப்பு வெற்றிகரமாக ரத்து செய்யப்பட்டது. + +pagination.next=அடுத்து ➡️ +# Services Menu +services.nursing.title=💉 செவிலியர் சேவைகள்\n\nதேர்வு செய்க: +services.nursing.opt1=ஆண் செவிலியர் +services.nursing.opt2=பெண் செவிலியர் +services.attender.title=🤝 உதவியாளர் சேவைகள் +services.attender.opt1=ஆண் உதவியாளர் +services.attender.opt2=பெண் உதவியாளர் +services.physio.title=🏃 பிசியோதெரபி\n\nவிலை: ₹700\n\nநீங்கள் முன்பதிவு செய்ய விரும்புகிறீர்களா? +services.physio.yes=ஆம் +services.physio.no=இல்லை +services.medproc.title=🩺 மருத்துவ நடைமுறைகள் +services.medproc.btn=விருப்பங்களை காண்க +services.medproc.list=நடைமுறைகள் +services.medproc.opt1=IM ஊசி +services.medproc.opt2=IV ஊசி +services.medproc.opt3=நெபுலைசேஷன் +services.medproc.opt4=கட்டு போடுதல் +services.medproc.opt5=வடிகுழாய் +services.digital.title=💻 டிஜிட்டல் ஆலோசனை\n\nஆலோசனை கட்டணம்: ₹750\n\nநீங்கள் முன்பதிவு செய்ய விரும்புகிறீர்களா? +services.digital.yes=ஆம் +services.digital.back=பின்செல் +services.homecare.title=🏡 வீட்டு பராமரிப்பு தொகுப்புகள் +services.homecare.btn=சேவைகளை காண்க +services.homecare.list=சேவைகள் +services.homecare.opt1=செவிலியர் பராமரிப்பு +services.homecare.opt2=உதவியாளர் பராமரிப்பு +services.homecare.opt3=பிசியோதெரபி +services.homecare.opt4=மருத்துவ நடைமுறைகள் +services.main.title=🩺 எனது சேவைகள்\n\nதயவுசெய்து ஒரு வகையைத் தேர்ந்தெடுக்கவும்: +services.main.btn=சேவைகளை காண்க +services.main.list=சேவை பிரிவுகள் +services.main.opt1=செவிலியர் சேவைகள் +services.main.opt2=உதவியாளர் சேவைகள் +services.main.opt3=பிசியோதெரபி +services.main.opt4=மருத்துவ நடைமுறைகள் +services.main.opt5=டிஜிட்டல் ஆலோசனை +services.main.opt6=வீட்டு பராமரிப்பு diff --git a/src/test/java/com/example/whatsappautomation/WhatsAppAutomationApplicationTests.java b/src/test/java/com/example/whatsappautomation/WhatsAppAutomationApplicationTests.java new file mode 100644 index 0000000..db1d25a --- /dev/null +++ b/src/test/java/com/example/whatsappautomation/WhatsAppAutomationApplicationTests.java @@ -0,0 +1,12 @@ +package com.example.whatsappautomation; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class WhatsAppAutomationApplicationTests { + + @Test + void contextLoads() { + } +}