Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@rakshabesafe
Copy link

No description provided.

google-labs-jules bot and others added 11 commits May 26, 2025 07:22
This commit introduces an auto-reply feature for chat messages.

New API Endpoints:
- POST /chat/autoreply: Allows you to set an auto-reply message for a specific phone number. The request body requires 'Phone' and 'Body'. It stores the rule per user.
- DELETE /chat/autoreply: Allows you to remove an auto-reply rule for a specific phone number. The request body requires 'Phone'.

Core Logic:
- Database: A new 'autoreplies' table is added via migrations to store user_id, phone_number, and reply_body.
- Message Handling: Incoming messages are checked against active auto-reply rules for the recipient user and the sender's phone number. If a rule matches, the predefined reply_body is sent back.
- Loop Prevention: Auto-replies are not sent to messages originating from the bot itself (IsFromMe check).

Documentation:
- API.md and the Swagger specification (static/api/spec.yml) have been updated to include the new endpoints, request/response formats, and error codes.
feat: Implement chat auto-reply feature
This commit introduces an autoreply feature for WhatsApp messages.

Key changes:

1.  **Database Schema:**
    *   Added `last_sent_at` (TIMESTAMP) column to the `autoreplies` table to track the last time an autoreply was sent.
    *   Updated `migrations.go` to include this new column.

2.  **API Endpoints & Handlers:**
    *   Added a new `GET /chat/autoreply` endpoint to list all configured autoreplies for you.
    *   Created `GetAutoReplies` handler in `handlers.go`.
    *   Modified `AddAutoReply` in `handlers.go` to initialize `last_sent_at` to NULL.
    *   Updated `routes.go` to map the new GET endpoint.

3.  **Autoreply Logic:**
    *   Implemented the core autoreply logic in `wmiau.go` within the `myEventHandler`.
    *   When a message is received, the system checks if an autoreply rule exists for the sender.
    *   An autoreply is sent if a rule exists and if no reply has been sent to that number in the last 5 minutes.
    *   The `last_sent_at` timestamp is updated after an autoreply is sent.

4.  **API Documentation:**
    *   Updated `static/api/spec.yml` to include the new `GET /chat/autoreply` endpoint and its response schema (`AutoReplyEntry`).

This feature allows you to configure automatic replies to incoming messages with a 5-minute cooldown period per recipient.
feat: Implement WhatsApp autoreply feature
add references
Utility class to add user
This commit introduces a new "modes" system for managing autoreplies. You can define different modes (e.g., "work", "home") with specific phone numbers and autoreply messages associated with each mode.

Key features include:

1.  **Database Changes:**
    *   Added `autoreply_modes` table to store mode configurations (user_id, mode_name, phone_number, message).
    *   Added `active_mode` table to track the currently active mode for each user.

2.  **New API Endpoints (under /mode/):**
    *   `POST /mode/autoreply`: Add or update a phone number and message for a specific mode.
    *   `DELETE /mode/autoreply`: Delete a phone number from a mode, or delete an entire mode.
    *   `GET /mode/autoreply`: Retrieve configured phone numbers and messages for a mode or all modes.
    *   `POST /mode/enablemode`: Activate a mode, clearing previous autoreplies and applying the selected mode's configuration.
    *   `POST /mode/disablemode`: Deactivate the currently active mode, clearing relevant autoreplies.
    *   `GET /mode/currentmode`: Get the name of the currently active mode.
    *   `POST /mode/clear`: Clear all active autoreplies and deactivate any current mode.

3.  **API Spec Update:**
    *   Updated `static/api/spec.yml` to include definitions for all new `/mode/...` endpoints.

4.  **Unit Tests:**
    *   Added a comprehensive test suite in `handlers_mode_test.go` to cover the new API handlers, ensuring functionality and user-specificity.

This system allows you to easily switch between different sets of autoreply configurations. Mode names are stored in lowercase and must be alphanumeric.
This commit introduces a comprehensive autoreply mode management system and integrates with Google Contacts for populating modes.

Key features:

1.  **Autoreply Modes Management (Phase 1):**
    *   New `autoreply_modes` table to store mode configurations (mode name, phone number, message) per user.
    *   New `active_mode` table to track the currently active mode per user.
    *   API Endpoints:
        *   `POST /mode/autoreply`: Add/update a phone number and message to a mode.
        *   `DELETE /mode/autoreply`: Delete a phone number from a mode or an entire mode.
        *   `GET /mode/autoreply`: List configured autoreplies for a mode or all modes.
        *   `POST /mode/enablemode`: Activate a mode, loading its numbers into the live autoreply list.
        *   `POST /mode/disablemode`: Deactivate the current mode.
        *   `GET /mode/currentmode`: View the currently active mode.
        *   `POST /mode/clear`: Clear all active autoreplies.
    *   Mode names are stored in lowercase and validated (alphanumeric).
    *   Comprehensive unit tests for all mode management handlers.
    *   OpenAPI specification updated for all new mode endpoints.

2.  **Google Contacts Integration (Phase 2):**
    *   `users` table updated with `google_contacts_auth_token` column to store user-provided OAuth tokens.
    *   API Endpoints:
        *   `POST /autoreply/contactgroupauth`: Store Google Contacts API auth token for you.
        *   `POST /autoreply/contactgroup`: Fetch contacts from a (mocked) Google Contacts group using the stored token, normalize phone numbers, and add them to a specified autoreply mode.
        *   `DELETE /autoreply/contactgroup`: Fetch contacts from a (mocked) Google Contacts group, normalize phone numbers, and remove them from a specified autoreply mode.
    *   Implemented robust phone number normalization logic (handles '+' prefix, default country code '91').
    *   Google Contacts API interaction is currently via a placeholder function (`fetchContactsFromGoogleGroup`) to allow testing of the surrounding logic.
    *   Unit tests for new handlers, including token management, phone normalization, and mocked API interaction.
    *   OpenAPI specification updated for Google Contacts integration endpoints.

All database schema changes are handled idempotently. All new endpoints are covered by authentication middleware.
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @rakshabesafe, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

Summary of Changes

Greetings! Gemini here, providing a summary of this pull request titled "Feature/autoreply modes".

This PR introduces a significant enhancement to the autoreply functionality by implementing the concept of 'modes'. Instead of just a single list of autoreplies, users can now define multiple named modes, each containing a specific set of phone numbers and their corresponding autoreply messages. Users can then activate one mode at a time. When a mode is enabled, the autoreply rules from that mode are loaded into the active autoreply list, replacing any previous entries. Disabling a mode or clearing all modes removes the entries from the active list.

The PR also includes initial steps towards integrating with Google Contacts, allowing users to store a Google API token and populate autoreply modes based on contact groups (currently using a placeholder for the actual contact fetching logic). Additionally, basic phone number normalization is introduced.

Several new API endpoints have been added to manage these modes, enable/disable them, retrieve the current active mode, and integrate with the Google Contacts feature. Database schema changes are included to support the new autoreply_modes and active_mode tables, as well as storing the Google Contacts auth token.

Highlights

  • Autoreply Modes: Introduces the concept of named autoreply modes, allowing users to define multiple sets of autoreply rules.
  • Mode Management API: Adds new API endpoints (/mode/autoreply, /mode/enablemode, /mode/disablemode, /mode/currentmode, /mode/clear) to manage mode configurations and control the active mode.
  • Google Contacts Integration (Placeholder): Adds API endpoints (/autoreply/contactgroupauth, /autoreply/contactgroup) and database support to store a Google API token and populate modes from Google Contact Groups (using simulated fetching logic for now).
  • Database Schema Updates: Adds autoreply_modes and active_mode tables and alters the users table to store the Google Contacts auth token.
  • Message Handling Logic: Updates the message event handler to check the active autoreply list (autoreplies table) and send replies based on the currently enabled mode's rules, including a 5-minute rate limit per contact.
  • Phone Number Normalization: Adds a helper function to normalize phone numbers by removing non-numeric characters and applying specific rules (like prefixing '91' for 10-digit numbers without a leading '+').
  • Testing: Adds a new test file (handlers_mode_test.go) with comprehensive unit tests for the new mode and contact group related API handlers.

Changelog

Click here to see the changelog
  • API.md
    • Added documentation for new API endpoints: /chat/autoreply (POST, DELETE, GET) for managing the active autoreply list.
    • Added documentation for new API endpoints: /mode/autoreply (POST, DELETE, GET) for managing mode-specific autoreply configurations.
    • Added documentation for new API endpoints: /mode/enablemode (POST), /mode/disablemode (POST), /mode/currentmode (GET), /mode/clear (POST) for controlling the active mode.
    • Added documentation for new API endpoints: /autoreply/contactgroupauth (POST) for storing Google token.
    • Added documentation for new API endpoints: /autoreply/contactgroup (POST, DELETE) for managing mode entries based on Google Contact Groups.
    • Added new definitions for request/response bodies related to modes and contact groups in the OpenAPI spec.
  • Dockerfile
    • Changed default timezone from America/Sao_Paulo to Asia/Kolkata.
    • Removed trailing newline from the ENTRYPOINT instruction.
  • adduser.sh
    • Added a new shell script to create a user via a curl command.
  • db.go
    • Modified InitializeDatabase to call a new createTables function after initializing the database connection.
    • Added createTables function to idempotently create autoreply_modes and active_mode tables.
    • Added logic in createTables to alter the users table to add the google_contacts_auth_token column, handling both PostgreSQL and SQLite syntax.
  • handlers.go
    • Added new Go structs for request/response payloads related to autoreply modes and contact groups (AutoReplyRequest, AutoReplyEntry, DeleteAutoReplyRequest, ModeAutoreplyRequest, ModeAutoreplyDeleteRequest, EnableModeRequest, DisableModeRequest, ModeAutoreplyEntry, AuthTokenRequest, ContactGroupRequest, ContactGroupDeleteRequest).
    • Implemented normalizePhoneNumber helper function for cleaning phone number strings.
    • Added a placeholder fetchContactsFromGoogleGroup function.
    • Implemented handler SetGoogleContactsAuthToken for /autoreply/contactgroupauth (POST).
    • Implemented handlers AddAutoReply, GetAutoReplies, DeleteAutoReply for /chat/autoreply (POST, GET, DELETE) - these seem to manage the active list, not modes.
    • Implemented isValidModeName helper function.
    • Implemented handlers AddModeAutoreply, DeleteModeAutoreply, GetModeAutoreplies for /mode/autoreply (POST, DELETE, GET).
    • Implemented handlers EnableMode, DisableMode, GetCurrentMode, ClearModes for /mode/enablemode, /mode/disablemode, /mode/currentmode, /mode/clear (POST/GET).
    • Implemented handlers AddContactGroupToMode and DeleteContactGroupFromMode for /autoreply/contactgroup (POST, DELETE).
  • handlers_mode_test.go
    • Added a new test file for mode and contact group related handlers.
    • Includes setup for an in-memory SQLite database for testing.
    • Adds comprehensive tests for AddModeAutoreply, DeleteModeAutoreply, GetModeAutoreplies, EnableMode, DisableMode, GetCurrentMode, ClearModes.
    • Adds tests for SetGoogleContactsAuthToken, AddContactGroupToMode, DeleteContactGroupFromMode.
    • Includes tests for helper functions isValidModeName and normalizePhoneNumber.
  • main.go
    • Commented out the line zerolog.SetGlobalLevel(zerolog.Disabled).
  • migrations.go
    • Added a new migration (ID 4) to create the autoreplies table if it does not exist, including user_id, phone_number, reply_body, last_sent_at, created_at, and a unique constraint on user_id and phone_number.
  • routes.go
    • Added new routes and associated handlers for /chat/autoreply (POST, DELETE, GET).
    • Added new routes and associated handlers for /mode/autoreply (POST, DELETE, GET), /mode/enablemode (POST), /mode/disablemode (POST), /mode/currentmode (GET), /mode/clear (POST).
    • Added new routes and associated handlers for /autoreply/contactgroupauth (POST) and /autoreply/contactgroup (POST, DELETE).
  • wmiau.go
    • Modified the myEventHandler function to implement autoreply logic.
    • Checks the autoreplies table for a rule matching the sender's phone number for the current user.
    • Implements a 5-minute rate limit per contact for sending autoreplies.
    • Sends an ExtendedTextMessage autoreply if a rule is found and the rate limit is not exceeded.
    • Updates the last_sent_at timestamp in the autoreplies table after sending a reply.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.


A mode is set, a message sent,
To contacts grouped, time wisely spent.
No manual reply,
Just bytes that fly,
A digital, helpful event.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a significant new feature: autoreply modes, including integration with Google Contacts (via a placeholder) and various API endpoints to manage these modes and autoreplies. The changes are extensive, touching API documentation, database schema, handlers, tests, and core application logic.

Overall, the implementation is quite comprehensive, especially the test coverage for the new handlers. The API documentation in API.md and spec.yml has also been updated, which is good.

I've identified a few areas for improvement, primarily around consistency between documentation and implementation, and a suggestion for enhancing database migration robustness for SQLite. Please see the detailed comments below.

Summary of Findings

  • API Documentation and Implementation Mismatch for AddAutoReply: The API.md describes POST /chat/autoreply as an Add/Update operation, but the handlers.go:AddAutoReply implementation is add-only, returning 409 Conflict if the entry exists. This needs to be reconciled. (Severity: High, commented)
  • SQLite Column Addition Logic in db.go: The logic for adding a column in SQLite could be made more robust by checking for column existence via PRAGMA before attempting ALTER TABLE, to avoid potential errors on older SQLite versions. (Severity: Medium, commented)
  • Duplicate Definition in OpenAPI Specification: The AutoReplyEntry definition is duplicated in static/api/spec.yml. One instance should be removed for clarity. (Severity: Medium, commented)
  • Phone Number Normalization Assumption: The normalizePhoneNumber function in handlers.go assumes 10-digit numbers without a '+' prefix are Indian and prefixes '91'. This regional assumption should be documented or made configurable if broader applicability is desired. (Severity: Low, not commented due to settings)
  • Hardcoded Autoreply Rate Limit: The autoreply logic in wmiau.go uses a hardcoded 5-minute rate limit. Consider making this configurable. (Severity: Low, not commented due to settings)
  • Test Isolation in handlers_mode_test.go: Some test cases in handlers_mode_test.go might have dependencies on the state left by previous test cases. Enhancing test isolation (e.g., with t.Cleanup or more specific per-test setup/teardown) could improve test robustness. (Severity: Low, not commented due to settings)
  • Commented-out Log Statement: A log.Warn() statement is commented out in db.go (line 113) regarding duplicate columns in SQLite. If this warning is relevant, it should be active or a comment should explain its absence. (Severity: Low, not commented due to settings)

Merge Readiness

This pull request introduces valuable autoreply functionality with extensive new code and tests. Before merging, I recommend addressing the identified high severity issues, particularly the discrepancy between the API documentation and the AddAutoReply handler's behavior, and the medium severity issues concerning database migration robustness and OpenAPI spec clarity. Once these are resolved, the PR should be in a much better state for merging. As an AI, I am not authorized to approve pull requests; please ensure further review and approval from team members.

Comment on lines +711 to +733
* **Description:** Sets up an auto-reply for a specific phone number. If an auto-reply for the given phone number already exists for the user, it will be updated. If not, a new one will be created.
* **Authentication:** Requires user token.
* **Request Body (JSON):**
```json
{
"Phone": "1234567890", // Target phone number (normalized, e.g., digits only or international format used by the system)
"Body": "Hello! I am currently unavailable and will get back to you soon."
}
```
* **Responses:**
* `201 Created`: If a new auto-reply was successfully created.
```json
{
"code": 201,
"data": {
"detail": "Auto-reply added successfully",
"id": "generated-unique-id-for-the-rule"
},
"success": true
}
```
* `400 Bad Request`: If `Phone` or `Body` is missing or invalid.
* `409 Conflict`: If an auto-reply for this phone number already exists for the user.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The description for Add/Update Auto-Reply (line 711) states, "If an auto-reply for the given phone number already exists for the user, it will be updated." However, the 409 Conflict response (line 733) is documented for the case "If an auto-reply for this phone number already exists for the user."

This seems contradictory. The current handler implementation (handlers.go:AddAutoReply) also reflects an "add-only" behavior, returning a 409 Conflict if the entry exists, rather than updating it.

Could you clarify the intended behavior?

  • If it's "add-only", the description on line 711 should be updated to reflect this, and the 409 Conflict is appropriate.
  • If it's meant to be an "add-or-update" (upsert) operation, the handler logic in handlers.go would need to be changed (e.g., using ON CONFLICT DO UPDATE for PostgreSQL and INSERT OR REPLACE or equivalent for SQLite), and the success status code might need adjustment (e.g., 200 OK for update, 201 Created for new).

handlers.go Outdated
Comment on lines 676 to 687
_, err = s.db.Exec("INSERT INTO autoreplies (id, user_id, phone_number, reply_body, last_sent_at) VALUES ($1, $2, $3, $4, $5)", newId, txtid, req.Phone, req.Body, nil)
if err != nil {
// Check for unique constraint violation (specific error code might depend on DB: PostgreSQL uses "23505")
// This is a simplified check; a more robust way involves checking pq.Error.Code or sqlite3.ErrConstraintUnique
if strings.Contains(err.Error(), "UNIQUE constraint failed") || strings.Contains(err.Error(), "duplicate key value violates unique constraint") {
s.Respond(w, r, http.StatusConflict, errors.New("Auto-reply for this phone number already exists for the user"))
return
}
log.Error().Err(err).Str("user_id", txtid).Str("phone", req.Phone).Msg("Failed to add auto-reply")
s.Respond(w, r, http.StatusInternalServerError, errors.New("Failed to add auto-reply"))
return
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The AddAutoReply handler (lines 676-687) implements an "add-only" behavior for autoreplies. It attempts an INSERT and then checks for a UNIQUE constraint failed error (line 680) to return http.StatusConflict. This means if an auto-reply for the given phone number already exists for the user, the operation fails with a 409 status.

This conflicts with the API.md documentation (line 711 for POST /chat/autoreply), which states: "If an auto-reply for the given phone number already exists for the user, it will be updated."

To align the implementation with the "Add/Update" (upsert) behavior described in the documentation, would you consider modifying the SQL query?
For PostgreSQL, you could use ON CONFLICT (user_id, phone_number) DO UPDATE SET reply_body = EXCLUDED.reply_body, last_sent_at = EXCLUDED.last_sent_at; (adjusting last_sent_at update as needed, perhaps only updating reply_body).
For SQLite (modern versions), a similar ON CONFLICT clause can be used, or INSERT OR REPLACE INTO autoreplies ... if appropriate (though INSERT OR REPLACE deletes and re-inserts, which might affect id or created_at if not handled).

Alternatively, if the intended behavior is indeed "add-only", the API.md description should be updated to reflect this, and the current implementation would be correct for that intent.

db.go Outdated
Comment on lines 92 to 106
alterUsersTableSQL = `ALTER TABLE users ADD COLUMN google_contacts_auth_token TEXT;`

// Check if column exists to avoid error on re-run with older SQLite
var columnName string
query := "SELECT name FROM pragma_table_info('users') WHERE name = 'google_contacts_auth_token';"
err := db.Get(&columnName, query)
if err == nil && columnName == "google_contacts_auth_token" {
// Column already exists, no need to alter
alterUsersTableSQL = ""
} else if err != nil && err.Error() != "sql: no rows in result set" {
// An actual error occurred querying pragma_table_info
return fmt.Errorf("failed to check users table schema: %w", err)
}
// If err is "sql: no rows in result set", column doesn't exist, proceed with ALTER.
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current logic for adding the google_contacts_auth_token column to the users table in SQLite (lines 92-106) executes the ALTER TABLE users ADD COLUMN google_contacts_auth_token TEXT; statement (line 92) before checking if the column already exists using the PRAGMA table_info query (lines 96-105).

If the column already exists and you're using an older SQLite version that doesn't make ADD COLUMN idempotent, the initial ALTER TABLE statement might fail. The subsequent PRAGMA check then attempts to set alterUsersTableSQL = "", but this occurs after the potential failure. While there's error handling later (lines 112-114) to catch "duplicate column name", it might be more robust to prevent this error.

Would you consider restructuring this to check for the column's existence with PRAGMA first, and only then conditionally execute the ALTER TABLE statement if the column is found to be missing? This could make the migration logic cleaner and less reliant on specific error message strings.

For example, the logic could be:

	alterUsersTableSQL := ""
	if dbType == "postgres" {
		alterUsersTableSQL = `ALTER TABLE users ADD COLUMN IF NOT EXISTS google_contacts_auth_token TEXT;`
	} else { // sqlite
		var columnName string
		query := "SELECT name FROM pragma_table_info('users') WHERE name = 'google_contacts_auth_token';"
		err := db.Get(&columnName, query)
		// If err is sql.ErrNoRows, column doesn't exist. If err is nil and columnName matches, it exists.
		if err == sql.ErrNoRows || (err == nil && columnName != "google_contacts_auth_token") {
			// Column does not exist, or a different error occurred during the check (which is handled if err != sql.ErrNoRows below)
			alterUsersTableSQL = `ALTER TABLE users ADD COLUMN google_contacts_auth_token TEXT;`
		} else if err != nil && err.Error() != "sql: no rows in result set" {
			// An actual error occurred querying pragma_table_info, not just 'no rows'
			return fmt.Errorf("failed to check users table schema: %w", err)
		}
		// If err is nil and columnName matched, column exists, alterUsersTableSQL remains ""
	}

	if alterUsersTableSQL != "" {
		if _, err := db.Exec(alterUsersTableSQL); err != nil {
			// Handle potential errors, e.g., if SQLite version is very old and ADD COLUMN still fails
			// The specific "duplicate column name" check might still be a fallback if needed,
			// but the pre-check should reduce its necessity.
			if !(dbType == "sqlite" && strings.Contains(err.Error(), "duplicate column name")) {
				return fmt.Errorf("failed to alter users table to add google_contacts_auth_token: %w", err)
			}
		}
	}

Comment on lines 1533 to 1678
AutoReplyEntry: # Existing definition, ensure new ones are placed correctly relative to it.
type: object
properties:
phone:
type: string
description: "The phone number for which the auto-reply is configured."
example: "1234567890"
body:
type: string
description: "The message body of the auto-reply."
example: "I'm currently unavailable. I'll get back to you soon."
last_sent_at:
type: string
format: date-time
description: "The timestamp when this auto-reply was last sent. Omitted if never sent or if the value is null."
example: "2023-10-27T10:30:00Z"
nullable: true
required:
- phone
- body
ContactGroupDeleteRequest:
type: object
required:
- ModeName
- GroupName
properties:
ModeName:
type: string
description: "Name of the mode (alphanumeric)."
example: "work"
GroupName:
type: string
description: "Name of the Google Contacts group whose members will be removed from the mode."
example: "Old Colleagues"
ContactGroupRequest:
type: object
required:
- ModeName
- GroupName
- Message
properties:
ModeName:
type: string
description: "Name of the mode to associate contacts with (alphanumeric)."
example: "work"
GroupName:
type: string
description: "Name of the Google Contacts group to fetch contacts from."
example: "Colleagues"
Message:
type: string
description: "Autoreply message for these contacts within the specified mode."
example: "Currently busy with work tasks."
ModeAutoreplyRequest: # Existing definition
type: object
required:
- ModeName
- Phone
- Message
properties:
ModeName:
type: string
description: "Name of the mode (alphanumeric)."
example: "work"
Phone:
type: string
description: "Target phone number for the autoreply."
example: "1234567890"
Message:
type: string
description: "Message to be sent as autoreply."
example: "I am currently in a meeting."
ModeAutoreplyDeleteRequest:
type: object
required:
- ModeName
properties:
ModeName:
type: string
description: "Name of the mode to delete entries from (alphanumeric)."
example: "work"
Phone:
type: string
description: "Optional. Target phone number. If provided, only this specific entry for the mode is deleted."
example: "1234567890"
EnableModeRequest:
type: object
required:
- ModeName
properties:
ModeName:
type: string
description: "Name of the mode to enable (alphanumeric)."
example: "vacation"
DisableModeRequest:
type: object
required:
- ModeName
properties:
ModeName:
type: string
description: "Name of the mode to disable (alphanumeric)."
example: "vacation"
ModeAutoreplyEntry:
type: object
properties:
ModeName:
type: string
description: "Name of the mode."
example: "office"
Phone:
type: string
description: "Target phone number."
example: "1234567890"
Message:
type: string
description: "Autoreply message content."
example: "Currently at the office."
CurrentModeResponse:
type: object
properties:
current_mode_name:
type: string
nullable: true
description: "Name of the currently active mode. Null if no mode is active."
example: "vacation"
AutoReplyEntry:
type: object
properties:
phone:
type: string
description: "The phone number for which the auto-reply is configured."
example: "1234567890"
body:
type: string
description: "The message body of the auto-reply."
example: "I'm currently unavailable. I'll get back to you soon."
last_sent_at:
type: string
format: date-time
description: "The timestamp when this auto-reply was last sent. Omitted if never sent or if the value is null."
example: "2023-10-27T10:30:00Z"
nullable: true
required:
- phone
- body
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

It appears the AutoReplyEntry definition is duplicated within the definitions section of the OpenAPI specification. One instance is around line 1533 and another around line 1659.

Could one of these duplicate definitions be removed to ensure the API specification is clean, consistent, and less prone to confusion or maintenance issues?

The db.go file used strings.Contains without importing the "strings"
package, leading to an "undefined: strings" compilation error.

This commit adds "strings" to the import block in db.go to resolve
the issue.
This commit replaces the placeholder implementation of
`fetchContactsFromGoogleGroup` with live HTTP calls to the Google
People API.

Key changes:
- `fetchContactsFromGoogleGroup` now:
  - Fetches all contact groups for the authenticated user to find the
    `resourceName` of the target group by its display name.
  - Fetches all connections (contacts) for `people/me`, requesting
    names, phone numbers, and memberships.
  - Filters connections to find members of the target group.
  - Extracts names and phone numbers of group members.
  - Handles pagination for API calls.
  - Includes the OAuth2 token in `Authorization: Bearer` headers.
  - Defines structs for parsing Google People API JSON responses.
- Enhanced error handling:
  - `fetchContactsFromGoogleGroup` attempts to parse Google's JSON
    error responses to return more specific error messages.
  - Calling handlers (`AddContactGroupToMode`, `DeleteContactGroupFromMode`)
    inspect these errors to return appropriate HTTP status codes
    (403, 404, 500) and user-friendly messages.
  - Improved handling of cases where no contacts are found in a group.
- Unit tests in `handlers_mode_test.go` updated:
  - `fetchContactsFromGoogleGroupFunc` is now a package-level variable,
    allowing it to be mocked effectively during tests.
  - Tests now cover the new error handling paths and ensure correct
    handler behavior based on various (mocked) outcomes from
    `fetchContactsFromGoogleGroupFunc`.
This commit refactors the API paths for mode management and consolidates Swagger tags for all autoreply-related features.

Changes:
- **URL Refactoring:**
  - `/mode/autoreply` (POST, GET, DELETE) moved to `/autoreply/mode`.
  - `/mode/enablemode` moved to `/autoreply/enablemode`.
  - `/mode/disablemode` moved to `/autoreply/disablemode`.
  - `/mode/currentmode` moved to `/autoreply/currentmode`.
  - `/mode/clear` moved to `/autoreply/clearmode`.
  These changes are reflected in `routes.go`.

- **Swagger Spec Updates (`static/api/spec.yml`):**
  - Paths updated to match the new URL structure.
  - Removed `Mode` and `Autoreply Contacts` global tag definitions.
  - Added/updated a single `Autoreply` global tag.
  - All mode management endpoints (now under `/autoreply/...`),
    Google Contacts integration endpoints (`/autoreply/contactgroup...`),
    and simple autoreply endpoints (`/chat/autoreply...`) are now
    grouped under the `Autoreply` tag in Swagger.

- **Unit Tests:**
  - Endpoint URLs in `handlers_mode_test.go` updated to match the
    new `/autoreply/...` paths.
The handlers.go file used io.ReadAll without importing the "io"
package, specifically within the fetchContactsFromGoogleGroupFunc
when handling HTTP error responses from the Google People API.
This caused an "undefined: io" compilation error.

This commit adds "io" to the import block in handlers.go to resolve
the issue.
This commit resolves an issue where ALTER TABLE and CREATE TABLE statements
for custom features (google_contacts_auth_token, autoreply_modes, active_mode)
were attempted before the core `users` table was created by migrations.
This led to "no such table: users" errors.

Changes:
- Moved the addition of the `google_contacts_auth_token` column to the
  `users` table into a new migration (ID 5) in `migrations.go`.
- Moved the creation of the `autoreply_modes` table into a new
  migration (ID 6) in `migrations.go`.
- Moved the creation of the `active_mode` table into a new
  migration (ID 7) in `migrations.go`.
- Removed these DDL operations from the `createTables` function in `db.go`.
  The `createTables` function is now a no-op for these specific tables.
- Ensured platform-specific SQL (PostgreSQL and SQLite) is used within
  the migrations for conditional table/column creation (IF NOT EXISTS).

This ensures that all schema setup, including these new tables and columns,
is handled by the migrations system in the correct order, after the
`users` table is guaranteed to exist.
The "strings" package was previously imported in db.go to support
`strings.Contains` for conditional ALTER TABLE logic for SQLite.
This logic has since been moved into the migrations system (`migrations.go`).

As a result, the "strings" import is no longer used in `db.go` and
has been removed to prevent "imported and not used" build errors.
…Info()` without the necessary import for the logging package ("github.com/rs/zerolog/log"), which led to an "undefined: log" compilation error. I've now added the "log" import to migrations.go to fix this.
A duplicate mapping key for "AutoReplyEntry" existed in the
`definitions` section of `static/api/spec.yml`, causing a YAML
parsing error.

This commit removes the second, redundant definition of
`AutoReplyEntry`, leaving the first occurrence intact.
This commit further modularizes the codebase by separating
Google Contacts integration logic from the more general autoreply
handlers and types.

Key changes:

- **`google_contacts_types.go`:**
  - New file created.
  - Contains struct definitions specifically for Google Contacts API
    interaction and the `/autoreply/contactgroup...` endpoints.
    These include `AuthTokenRequest`, `ContactGroupRequest`,
    `ContactGroupDeleteRequest`, and all `Google...` API response
    parsing structs (e.g., `GooglePerson`, `GoogleApiError`).
    These were moved from `autoreply_types.go`.

- **`google_contacts_integration.go`:**
  - New file created.
  - Contains handler functions for Google Contacts:
    `SetGoogleContactsAuthToken`, `AddContactGroupToMode`,
    `DeleteContactGroupFromMode`.
  - Helper functions specific to this integration:
    `fetchContactsFromGoogleGroupFunc` (and its implementation) and
    `normalizePhoneNumber` also moved here.
    These were moved from `autoreply_handlers.go`.

- **`autoreply_types.go`:**
  - Cleaned up by removing the Google Contacts specific structs. Now
    primarily contains types for mode management and simple autoreply.

- **`autoreply_handlers.go`:**
  - Cleaned up by removing the Google Contacts specific handlers and
    helpers. Now contains handlers for mode management and simple
    autoreply, and the `isValidModeName` helper.

- **`autoreply_routes.go`:**
  - No changes were needed in this file, as the method signatures
    on the `*server` type remain consistent, and the router correctly
    locates them regardless of the source file within `package main`.

All new and modified files maintain `package main` and have updated
imports as necessary. This refactoring enhances modularity and aims
to reduce potential merge conflicts.
This commit addresses syntax errors in `autoreply_handlers.go` and
`handlers.go` that were reported as "non-declaration statement
outside function body".

These errors were due to file content duplication or extraneous code
likely introduced during previous refactoring steps. I have corrected the files to contain only their intended, valid Go code.

- `autoreply_handlers.go`: I ensured the file contains only one set of its
  definitions, removing any duplicated content.
- `handlers.go`: I rewrote this file to ensure it only contains the core handlers
  and helpers after other functionalities were moved to separate files.
  This also resolves any potential duplication or extraneous code.
This commit addresses several build errors related to function redeclarations
and incorrect function/method usage.

Consolidations:
- `Find` function: Canonical version (returning int, bool) now resides in
  `helpers.go`. Removed from `handlers.go`.
- `updateUserInfo` function and `Values` struct: Moved to `helpers.go`.
  Removed from `handlers.go`. The version in `helpers.go` is type-safe.
- `respondWithJSON` server method: Ensured it is solely defined in
  `handlers.go`. Removed duplicate from `helpers.go`.
- `parseJID` function: Canonical version now resides in `wmiau.go`.
  Removed from `handlers.go`.

Usage Corrections in `handlers.go`:
- `Find` function calls: Corrected calls in the `Connect` handler (lines
  ~152, 157, 161) to properly handle the (int, bool) return type,
  resolving "multiple-value in single-value context" errors.
- `updateUserInfo` calls: Corrected type assertions for the `userinfo`
  context value when calling `updateUserInfo` (lines ~172, 226, 301),
  changing `r.Context().Value("userinfo")` to
  `r.Context().Value("userinfo").(Values)`.

Import Cleaning:
- Unused imports that resulted from these consolidations (e.g., in
  `helpers.go`) were removed.

Note on Code Structure:
This commit focuses on resolving the immediate build errors. You provided feedback requesting that `handlers.go` be aligned as closely as
possible with the upstream repository (github.com/asternic/wuzapi/blob/main/handlers.go)
to minimize future merge conflicts. The refactoring work that moved
autoreply and Google Contacts related handlers out of `handlers.go` into
separate files (`autoreply_handlers.go`, `google_contacts_integration.go`,
etc.) may need to be revisited or reverted in future work to achieve this
alignment goal. The helper function consolidations in this commit are a
step towards reducing direct conflicts but the broader structure of `handlers.go`
(i.e., which functions it contains) is the main point of your feedback
for future consideration.
… done so far and provide feedback for Jules to continue.
…nment

This commit reverts previous modularization efforts (splitting autoreply
and Google Contacts features into separate files) and consolidates all
handler logic, type definitions, and relevant helpers back into
`handlers.go`. This is done to align the codebase structure more
closely with the upstream repository (asternic/wuzapi) and minimize
future merge conflicts, as per your feedback.

Specific changes:
- All struct definitions from `autoreply_types.go` and
  `google_contacts_types.go` were moved back to `handlers.go`.
  The `_types.go` files were removed.
- All handler functions and their private helpers from
  `autoreply_handlers.go` and `google_contacts_integration.go`
  were moved back to `handlers.go`. The separate handler files
  were removed.
- Route definitions from `autoreply_routes.go` were moved back into
  the main `routes()` function in `routes.go`. The
  `autoreply_routes.go` file was removed, and the call to its
  registration function was removed from `routes.go`.

Helper function consolidation:
- `Values` struct and `updateUserInfo` function (with signature
  accepting `interface{}` for context value) are now in `handlers.go`,
  aligning with upstream. Removed from `helpers.go`.
- `Find` function (returning `int, bool`) remains in `helpers.go` as
  it's not present in upstream `handlers.go`.
- `parseJID` function remains in `wmiau.go`.
- `s.Respond` is used as the primary response method in `handlers.go`.

Error corrections:
- Corrected usage of `Find` in `handlers.go` (in `Connect`,
  `UpdateWebhook`, `SetWebhook`, `AddUser` handlers) to properly
  handle its two return values.
- Corrected calls to `updateUserInfo` in `handlers.go` to pass
  the context value directly, as the function now handles type assertion.
- Created `constants.go` for `supportedEventTypes` (formerly `messageTypes`)
  as per upstream structure, and updated references.

Imports were reviewed and cleaned in all affected files.
This set of changes aims to make the codebase structure, particularly
`handlers.go`, more similar to the upstream repository.
This commit resolves "s.respondWithJSON undefined" errors in `handlers.go`.

Changes:
- The `s.Respond` method was updated to be more versatile in handling
  different `data` payload types (error, map, pre-marshalled JSON string,
  other types, nil), and to log JSON encoding errors instead of panicking.
- All calls to the old `s.respondWithJSON` method (primarily in admin
  user handlers like `AddUser`, `DeleteUser`) were replaced with calls
  to the updated `s.Respond` method, including passing the necessary
  `r *http.Request` argument.

This ensures consistent JSON response formatting and error handling across
the application.
@asternic
Copy link
Owner

Thanks for taking the time to submit this pull request. I really appreciate it.

While the auto-reply feature is a great idea, this project is intentionally designed to be a clean, business-logic-agnostic API for sending WhatsApp messages. Features like auto-replies should be implemented by applications or services that consume this API, rather than being built into the API itself.

This separation ensures the API remains flexible, maintainable, and reusable across different use cases. You could build an auto-reply service on top of this API in a separate project or wrapper layer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants